diff --git a/.changeset/config.json b/.changeset/config.json index 4f8345f464..c47279e4c8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,19 @@ { - "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", - "changelog": "@changesets/cli/changelog", + "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", + "access": "public", + "baseBranch": "main", + "changelog": ["@changesets/changelog-github", { "repo": "wevm/wagmi" }], "commit": false, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "master", + "ignore": [ + "*-register", + "@wagmi/test", + "site", + "next-app", + "nuxt-app", + "vite-*" + ], "updateInternalDependencies": "patch", - "ignore": ["@0xsequence/wallet-primitives-cli", "docs", "web"] + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } } diff --git a/.changeset/new-elephants-travel.md b/.changeset/new-elephants-travel.md new file mode 100644 index 0000000000..ddfb37374a --- /dev/null +++ b/.changeset/new-elephants-travel.md @@ -0,0 +1,5 @@ +--- +"@wagmi/cli": patch +--- + +Updated block explorer chains. diff --git a/.changeset/nice-pandas-clap.md b/.changeset/nice-pandas-clap.md new file mode 100644 index 0000000000..7f4af53010 --- /dev/null +++ b/.changeset/nice-pandas-clap.md @@ -0,0 +1,5 @@ +--- + +--- + +Circleci project setup diff --git a/.changeset/quick-hairs-scream.md b/.changeset/quick-hairs-scream.md new file mode 100644 index 0000000000..206e94e246 --- /dev/null +++ b/.changeset/quick-hairs-scream.md @@ -0,0 +1,6 @@ +--- +"wagmi": patch +"@wagmi/core": patch +--- + +Added `chainId` parameter to `getCapabilities`/`useCapabilities`. diff --git a/.changeset/spicy-bats-juggle.md b/.changeset/spicy-bats-juggle.md new file mode 100644 index 0000000000..cf7a154229 --- /dev/null +++ b/.changeset/spicy-bats-juggle.md @@ -0,0 +1,6 @@ +--- +"@wagmi/cli": patch +"site": patch +--- + +Circleci project setup diff --git a/.changeset/tall-fans-mate.md b/.changeset/tall-fans-mate.md new file mode 100644 index 0000000000..cf7a154229 --- /dev/null +++ b/.changeset/tall-fans-mate.md @@ -0,0 +1,6 @@ +--- +"@wagmi/cli": patch +"site": patch +--- + +Circleci project setup diff --git a/.changeset/tiny-laws-dream.md b/.changeset/tiny-laws-dream.md new file mode 100644 index 0000000000..c39a3d68b9 --- /dev/null +++ b/.changeset/tiny-laws-dream.md @@ -0,0 +1,5 @@ +--- +"@fake-scope/fake-pkg": patch +--- + +Circleci project setup diff --git a/.changeset/young-guests-care.md b/.changeset/young-guests-care.md new file mode 100644 index 0000000000..8de2292dde --- /dev/null +++ b/.changeset/young-guests-care.md @@ -0,0 +1,5 @@ +--- +"site": patch +--- + +docs(readme): fix typo diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..709c9a7474 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/hardhat-project/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7f34c7a889..12451d4bc9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,5 @@ -* @0xsequence/disable-codeowners-notifications @0xsequence/core +@tmm @jxom + +/packages/connectors/src/metaMask @ecp4224 @omridan159 @abretonc7s @elefantel @BjornGunnarsson @EdouardBougon +/packages/connectors/src/safe @DaniSomoza @dasanra @mikhailxyz @yagopv +/packages/connectors/src/walletConnect @ganchoradkov @glitch-txs @ignaciosantise @tomiir diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..d3ab387e17 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +[View Contributing Guide on wagmi.sh](https://wagmi.sh/dev/contributing) \ No newline at end of file diff --git a/.github/DISCUSSION_TEMPLATE/connector-request.yml b/.github/DISCUSSION_TEMPLATE/connector-request.yml new file mode 100644 index 0000000000..c1e31b1b6b --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/connector-request.yml @@ -0,0 +1,51 @@ +title: '[Connector Request] ' +body: + - type: markdown + attributes: + value: | + Thanks for your interest in contributing a new Connector to the Wagmi! If you haven't already, please read the [Contributing Guidelines](https://wagmi.sh/dev/contributing). Once you submit the form, the Wagmi team will follow up in the discussion thread to discuss next steps. + + Please note that in order for connector requests to be accepted, the team creating the Connector must [sponsor Wagmi](https://github.com/sponsors/wevm). It takes time and effort to maintain third-party connectors. Wagmi is an OSS project that depends on sponsors and grants to continue our work. Please get in touch via [dev@wevm.dev](mailto:dev@wevm.dev) if you have questions about sponsoring. + + - type: textarea + attributes: + label: What **novel use-case** does the Connector provide? + description: | + A novel use-case is likely one that is not already covered by or not easily extended from another Connector (such as the `injected` or `walletConnect`). + + Examples of **novel** use-cases could be a connector that integrates with: + + - the injected `window.ethereum` provider (a la `injected`) + - a series of wallets via QR Codes or Mobile Deep Links (a la `walletConnect`) + - a wallet with it's own SDK (a la `coinbaseWallet`) + - hardware wallet(s) via Web USB/Bluetooth + - an Externally Owned Account via a private key or some other method + + Examples of **nonnovel** use-cases would be a connector that: + + - extends another connector (e.g. `walletConnect`) with no significant differences in functionality other than branding, etc. + placeholder: Info on what makes this connector different. + validations: + required: true + + - type: textarea + attributes: + label: Are the Connector's integrations production-ready and generally available? + description: Connectors are intended to be used by consumers in production as part of Wagmi. As such, the Connector and all dependencies must be production-ready and generally available. This means your connector should not rely on non-production software or be restricted to a limited group of users. For example, if your connector requires a wallet that has a closed beta, it is not ready for inclusion in Wagmi. + placeholder: Info about the Connector and any dependencies (e.g. browser extension, wallet app, npm package). + validations: + required: true + + - type: checkboxes + attributes: + label: Are you committed to actively maintaining the Connector? + description: It is critical connectors are updated in a timely manner and actively maintained so that users of Wagmi can rely on them in production settings. The Wagmi core team will provide as much assistance as possible to keep connectors up-to-date with breaking changes from Wagmi, but it is your responsibility to ensure that any dependencies and issues/discussions related to the Connector are handled in a timely manner. If this is not done, the Connector could be removed from the future versions. + options: + - label: Yes, my team is or I am committed to actively maintaining the Connector. + required: true + + - type: textarea + attributes: + label: Additional comments + description: Feel free to jot down any additional info you think might be helpful. + placeholder: Additional comments, questions, feedback. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..8a561abba1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,88 @@ +name: Bug Report +description: Report bugs or issues. +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! The more info you provide, the more we can help you. + + If you are a [Wagmi Sponsor](https://github.com/sponsors/wevm?metadata_campaign=gh_issue), your issues are prioritized. + + - type: checkboxes + attributes: + label: Check existing issues + description: By submitting this issue, you checked there isn't [already an issue](https://github.com/wevm/wagmi/issues) for this bug. + options: + - label: I checked there isn't [already an issue](https://github.com/wevm/wagmi/issues) for the bug I encountered. + required: true + + - type: textarea + attributes: + label: Describe the bug + description: Clear and concise description of the bug. If you intend to submit a PR for this issue, tell us in the description. Thanks! + placeholder: I am doing… What I expect is… What is actually happening… + validations: + required: true + + - type: input + id: reproduction + attributes: + label: Link to Minimal Reproducible Example + description: "Please provide a link that can reproduce the problem: [new.wagmi.sh](https://new.wagmi.sh) for runtime issues or [TypeScript Playground](https://www.typescriptlang.org/play) for type issues. For most issues, you will likely get asked to provide a minimal reproducible example so why not add one now :) If a report is vague (e.g. just snippets, generic error message, screenshot, etc.) and has no reproduction, it will receive a \"Needs Reproduction\" label and be auto-closed." + placeholder: https://new.wagmi.sh + validations: + required: false + + - type: textarea + attributes: + label: Steps To Reproduce + description: Steps or code snippets to reproduce the behavior. + validations: + required: false + + - type: dropdown + attributes: + label: What Wagmi package(s) are you using? + multiple: true + options: + - 'wagmi' + - '@wagmi/cli' + - '@wagmi/connectors' + - '@wagmi/core' + - '@wagmi/vue' + - 'create-wagmi' + validations: + required: true + + - type: input + attributes: + label: Wagmi Package(s) Version(s) + description: What version of the Wagmi packages you selected above are you using? If using multiple, separate with comma (e.g. `wagmi@x.y.z, @wagmi/cli@x.y.z`). + placeholder: x.y.z (do not write `latest`) + validations: + required: true + + - type: input + attributes: + label: Viem Version + description: What version of [Viem](https://viem.sh) are you using? + placeholder: x.y.z (do not write `latest`) + validations: + required: true + + - type: input + attributes: + label: TypeScript Version + description: What version of TypeScript are you using? Wagmi requires `typescript@>=5`. + placeholder: x.y.z (do not write `latest`) + validations: + required: false + + - type: textarea + attributes: + label: Anything else? + description: Anything that will give us more context about the issue you are encountering. Framework version (e.g. React, Vue), app framework (e.g. Next.js, Nuxt), bundler, etc. + validations: + required: false + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..fc8027c871 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Get Help + url: https://github.com/wevm/wagmi/discussions/new?category=q-a + about: Ask a question and discuss with other community members. + + - name: Feature Request + url: https://github.com/wevm/wagmi/discussions/new?category=ideas + about: Request features or brainstorm ideas for new functionality. + + - name: Connector Request + url: https://github.com/wevm/wagmi/discussions/new?category=connector-request + about: Kick off a request for a new connector + diff --git a/.github/ISSUE_TEMPLATE/docs_issue.yml b/.github/ISSUE_TEMPLATE/docs_issue.yml new file mode 100644 index 0000000000..f2d53b8a98 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_issue.yml @@ -0,0 +1,34 @@ +name: Documentation Issue +description: Tell us about missing or incorrect documentation. +labels: ['Area: Docs'] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a documentation request. It helps make Wagmi better. + + If it's a small change, like misspelling or example that needs updating, feel free to submit a PR instead of creating this issue. + + - type: dropdown + attributes: + label: What is the type of issue? + multiple: true + options: + - Documentation is missing + - Documentation is incorrect + - Documentation is confusing + - Example code is not working + - Something else + + - type: textarea + attributes: + label: What is the issue? + validations: + required: true + + - type: textarea + attributes: + label: Where did you find it? + description: Please provide the URL(s) where you found this issue. + validations: + required: true diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000000..6b5f336419 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,256 @@ + + + +
+ +

+ + + + wagmi logo + + +

+ +

+ Reactive primitives for Ethereum apps +

+ +

+ + + + Version + + + + + + MIT License + + + + + + Downloads per month + + + + + + Best of JS + + + + + + Code coverage + + +

+ +--- + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). + +## Community + +For help, discussion about best practices, or any other conversation that would benefit from being searchable: + +[Discuss Wagmi on GitHub](https://github.com/wevm/wagmi/discussions) + +For casual chit-chat with others using the framework: + +[Join the Wagmi Discord](https://discord.gg/SghfWBKexF) + +## Contributing + +Contributions to Wagmi are greatly appreciated! If you're interested in contributing to Wagmi, please read the [Contributing Guide](https://wagmi.sh/dev/contributing) **before submitting a pull request**. + +## Sponsors + +If you find Wagmi useful or use it for work, please consider [sponsoring Wagmi](https://github.com/sponsors/wevm?metadata_campaign=gh_readme_support). Thank you 🙏 + +

+ + + + paradigm logo + + + + + + ithaca logo + + +

+ +

+ + + + family logo + + + + + + context logo + + + + + + WalletConnect logo + + + + + + PartyDAO logo + + + + + + Dynamic logo + + + + + + Sushi logo + + + + + + Stripe logo + + + + + + Privy logo + + + + + + pancake logo + + + + + + celo logo + + + + + + rainbow logo + + + + + + pimlico logo + + + + + + zora logo + + + + + + lattice logo + + + + + + supa logo + + + + + + zksync logo + + + + + + syndicate logo + + + + + + reservoir logo + + + + + + linea logo + + + + + + uniswap logo + + + + + + biconomy logo + + + + + + thirdweb logo + + + + + + polymarket logo + + + + + + routescan logo + + + + + + sequence logo + + +

+ +[Sponsor Wagmi](https://github.com/sponsors/wevm?metadata_campaign=gh_readme_support_bottom) + +
+
+ + + Powered by Vercel + +
+ + Powered by QuickNode + + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000000..54f40f38df --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +## Reporting a Vulnerability + +Contact [dev@wevm.dev](mailto:dev@wevm.dev). + diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml deleted file mode 100644 index ca81d1a40a..0000000000 --- a/.github/actions/install-dependencies/action.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Setup Node and PNPM dependencies - -runs: - using: 'composite' - - steps: - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup PNPM - uses: pnpm/action-setup@v3 - with: - version: 10 - run_install: false - - - name: Get pnpm store directory - id: pnpm-cache - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: | - ${{ steps.pnpm-cache.outputs.STORE_PATH }} - node_modules - packages/*/node_modules - ~/.cache/puppeteer - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - shell: bash - run: pnpm install --frozen-lockfile - if: ${{ steps.pnpm-cache.outputs.cache-hit != 'true' }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..bc63aca35b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' diff --git a/.github/logo-dark.svg b/.github/logo-dark.svg new file mode 100644 index 0000000000..5d47cce337 --- /dev/null +++ b/.github/logo-dark.svg @@ -0,0 +1,27 @@ + + + + + + + diff --git a/.github/logo-light.svg b/.github/logo-light.svg new file mode 100644 index 0000000000..4e28590c36 --- /dev/null +++ b/.github/logo-light.svg @@ -0,0 +1,27 @@ + + + + + + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..602a32d0a8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ + + + diff --git a/.github/workflows/Vercel Preview Deployment.yml b/.github/workflows/Vercel Preview Deployment.yml new file mode 100644 index 0000000000..ca7ca97005 --- /dev/null +++ b/.github/workflows/Vercel Preview Deployment.yml @@ -0,0 +1,22 @@ +name: Playwright Tests + +on: + repository_dispatch: + types: + - 'vercel.deployment.success' +permissions: + contents: read +jobs: + run-e2es: + if: github.event_name == 'repository_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.client_payload.git.sha }} + - name: Install dependencies + run: npm ci && npx playwright install --with-deps + - name: Run tests + run: npx playwright test + env: + BASE_URL: ${{ github.event.client_payload.url }} diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml new file mode 100644 index 0000000000..745341ed97 --- /dev/null +++ b/.github/workflows/changesets.yml @@ -0,0 +1,60 @@ +name: Changesets +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Verify + uses: ./.github/workflows/verify.yml + secrets: inherit + + changesets: + name: Publish + needs: verify + permissions: + contents: write + id-token: write + pull-requests: write + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits + fetch-depth: 0 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: PR or publish + uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba + with: + title: 'chore: version packages' + commit: 'chore: version packages' + createGithubReleases: ${{ github.ref == 'refs/heads/main' }} + publish: pnpm changeset:publish + version: pnpm changeset:version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish prerelease + if: steps.changesets.outputs.published != 'true' + continue-on-error: true + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" + git reset --hard origin/main + pnpm clean + pnpm changeset version --no-git-tag --snapshot canary + pnpm changeset:prepublish + pnpm changeset publish --no-git-tag --snapshot canary --tag canary diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..d19e21b798 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,39 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency review' +on: + pull_request: + branches: [ "main" ] + +# If using a dependency submission action in this workflow this permission will need to be set to: +# +# permissions: +# contents: write +# +# https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api +permissions: + contents: read + # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. + with: + comment-summary-in-pr: always + # fail-on-severity: moderate + # deny-licenses: GPL-1.0-or-later, LGPL-2.0-or-later + # retry-on-snapshot-warnings: true diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml new file mode 100644 index 0000000000..39b98291d1 --- /dev/null +++ b/.github/workflows/issue-labeled.yml @@ -0,0 +1,19 @@ +name: Issue Labeled + +on: + issues: + types: [labeled] + +jobs: + issue-labeled: + if: ${{ github.repository_owner == 'wevm' }} + uses: wevm/actions/.github/workflows/issue-labeled.yml@main + with: + needs-reproduction-body: | + Hello @${{ github.event.issue.user.login }}. + + Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using [StackBlitz](https://new.wagmi.sh), [TypeScript Playground](https://www.typescriptlang.org/play) (for type issues), or a separate minimal GitHub repository. + + [Minimal reproductions are required](https://antfu.me/posts/why-reproductions-are-required) as they save us a lot of time reproducing your config/environment and issue, and allow us to help you faster. + + Once a minimal reproduction is added, a team member will confirm it works, then re-open the issue. diff --git a/.github/workflows/jekyll-docker.yml b/.github/workflows/jekyll-docker.yml new file mode 100644 index 0000000000..c88a4430c3 --- /dev/null +++ b/.github/workflows/jekyll-docker.yml @@ -0,0 +1,23 @@ +name: Jekyll site CI + +permissions: + contents: read + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the site in the jekyll/builder container + run: | + docker run \ + -v ${{ github.workspace }}:/srv/jekyll -v ${{ github.workspace }}/_site:/srv/jekyll/_site \ + jekyll/builder:latest /bin/bash -c "chmod -R 777 /srv/jekyll && jekyll build --future" diff --git a/.github/workflows/lock-issue.yml b/.github/workflows/lock-issue.yml new file mode 100644 index 0000000000..279452d223 --- /dev/null +++ b/.github/workflows/lock-issue.yml @@ -0,0 +1,16 @@ +name: Lock Issue + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + lock-issue: + if: ${{ github.repository_owner == 'wevm' }} + uses: wevm/actions/.github/workflows/lock-issue.yml@main + with: + issue-comment: | + This issue has been locked since it has been closed for more than 14 days. + + If you found a concrete bug or regression related to it, please open a new [bug report](https://github.com/wevm/wagmi/issues/new?template=bug_report.yml) with a reproduction against the latest Wagmi version. If you have any questions or comments you can create a new [discussion thread](https://github.com/wevm/wagmi/discussions). + diff --git a/.github/workflows/octopusdeploy.yml b/.github/workflows/octopusdeploy.yml new file mode 100644 index 0000000000..9c4403d554 --- /dev/null +++ b/.github/workflows/octopusdeploy.yml @@ -0,0 +1,112 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by separate terms of service, +# privacy policy, and support documentation. +# +# This workflow will build and publish a Docker container which is then deployed through Octopus Deploy. +# +# The build job in this workflow currently assumes that there is a Dockerfile that generates the relevant application image. +# If required, this job can be modified to generate whatever alternative build artifact is required for your deployment. +# +# This workflow assumes you have already created a Project in Octopus Deploy. +# For instructions see https://octopus.com/docs/projects/setting-up-projects +# +# To configure this workflow: +# +# 1. Decide where you are going to host your image. +# This template uses the GitHub Registry for simplicity but if required you can update the relevant DOCKER_REGISTRY variables below. +# +# 2. Create and configure an OIDC credential for a service account in Octopus. +# This allows for passwordless authentication to your Octopus instance through a trust relationship configured between Octopus, GitHub and your GitHub Repository. +# https://octopus.com/docs/octopus-rest-api/openid-connect/github-actions +# +# 3. Configure your Octopus project details below: +# OCTOPUS_URL: update to your Octopus Instance Url +# OCTOPUS_SERVICE_ACCOUNT: update to your service account Id +# OCTOPUS_SPACE: update to the name of the space your project is configured in +# OCTOPUS_PROJECT: update to the name of your Octopus project +# OCTOPUS_ENVIRONMENT: update to the name of the environment to recieve the first deployment + + +name: 'Build and Deploy to Octopus Deploy' + +on: + push: + branches: + - '"main"' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + env: + DOCKER_REGISTRY: ghcr.io # TODO: Update to your docker registry uri + DOCKER_REGISTRY_USERNAME: ${{ github.actor }} # TODO: Update to your docker registry username + DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} # TODO: Update to your docker registry password + outputs: + image_tag: ${{ steps.meta.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ env.DOCKER_REGISTRY_USERNAME }} + password: ${{ env.DOCKER_REGISTRY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.DOCKER_REGISTRY }}/${{ github.repository }} + tags: type=semver,pattern={{version}},value=v1.0.0-{{sha}} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + deploy: + name: Deploy + permissions: + id-token: write + runs-on: ubuntu-latest + needs: [ build ] + env: + OCTOPUS_URL: 'https://your-octopus-url' # TODO: update to your Octopus Instance url + OCTOPUS_SERVICE_ACCOUNT: 'your-service-account-id' # TODO: update to your service account Id + OCTOPUS_SPACE: 'your-space' # TODO: update to the name of the space your project is configured in + OCTOPUS_PROJECT: 'your-project' # TODO: update to the name of your Octopus project + OCTOPUS_ENVIRONMENT: 'your-environment' # TODO: update to the name of the environment to recieve the first deployment + + steps: + - name: Log in to Octopus Deploy + uses: OctopusDeploy/login@e485a40e4b47a154bdf59cc79e57894b0769a760 #v1.0.3 + with: + server: '${{ env.OCTOPUS_URL }}' + service_account_id: '${{ env.OCTOPUS_SERVICE_ACCOUNT }}' + + - name: Create Release + id: create_release + uses: OctopusDeploy/create-release-action@fea7e7b45c38c021b6bc5a14bd7eaa2ed5269214 #v3.2.2 + with: + project: '${{ env.OCTOPUS_PROJECT }}' + space: '${{ env.OCTOPUS_SPACE }}' + packages: '*:${{ needs.build.outputs.image_tag }}' + + - name: Deploy Release + uses: OctopusDeploy/deploy-release-action@b10a606c903b0a5bce24102af9d066638ab429ac #v3.2.1 + with: + project: '${{ env.OCTOPUS_PROJECT }}' + space: '${{ env.OCTOPUS_SPACE }}' + release_number: '${{ steps.create_release.outputs.release_number }}' + environments: ${{ env.OCTOPUS_ENVIRONMENT }} diff --git a/.github/workflows/on_pr_pnpm-format-label.yml b/.github/workflows/on_pr_pnpm-format-label.yml deleted file mode 100644 index 84fb27cb3e..0000000000 --- a/.github/workflows/on_pr_pnpm-format-label.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: pnpm-format-label - -on: - pull_request: - types: [labeled] - -jobs: - proto: - if: ${{ github.event.label.name == 'pnpm format' }} - uses: ./.github/workflows/pnpm-format.yml - secrets: inherit - - rm: - if: ${{ github.event.label.name == 'pnpm format' }} - runs-on: ubuntu-latest - steps: - - name: Remove the label - run: | - LABEL=$(echo "${{ github.event.label.name }}" | sed 's/ /%20/g') - curl -X DELETE \ - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/$LABEL diff --git a/.github/workflows/pnpm-format.yml b/.github/workflows/pnpm-format.yml deleted file mode 100644 index 1be36e1a6b..0000000000 --- a/.github/workflows/pnpm-format.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: pnpm format - -on: - workflow_call: - -jobs: - run: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - fetch-depth: 20 - - - uses: ./.github/actions/install-dependencies - - - run: pnpm format - - - name: Commit back - uses: 0xsequence/actions/git-commit@v0.0.4 - env: - API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN_GIT_COMMIT }} - with: - files: './' - branch: ${{ github.head_ref }} - commit_message: '[AUTOMATED] pnpm format' diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000000..9ff4c5bb76 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,32 @@ +name: Pull Request +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + verify: + name: Verify + uses: ./.github/workflows/verify.yml + secrets: inherit + + size: + name: Size + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: Report build size + uses: preactjs/compressed-size-action@v2 + with: + pattern: 'packages/**/dist/**' + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..297c74f635 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + + - name: Install Dependencies + run: pnpm install + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: pnpm release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Send a Slack notification if a publish happens + if: steps.changesets.outputs.published == 'true' + # You can do something when a publish happens. + run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!" diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 0000000000..39683bb684 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,32 @@ +name: Snapshot +on: + workflow_dispatch: + +jobs: + snapshot: + name: Release snapshot version + permissions: + contents: write + id-token: write + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: Publish Snapshots + continue-on-error: true + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + snapshot=$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') + npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" + pnpm clean + pnpm changeset version --no-git-tag --snapshot $snapshot + pnpm changeset:prepublish + pnpm changeset publish --no-git-tag --snapshot $snapshot --tag $snapshot diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index bb22f4c721..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,66 +0,0 @@ -on: [push] - -name: tests - -jobs: - install: - name: Install dependencies - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install-dependencies - - build: - name: Run build - runs-on: ubuntu-latest - needs: [install] - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install-dependencies - - run: pnpm clean - - run: pnpm build - - run: pnpm typecheck - - run: pnpm lint - - tests: - name: Run all tests - runs-on: ubuntu-latest - needs: [build] - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install-dependencies - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: v1.5.0 - - name: Start Anvil in background - run: anvil --fork-url https://nodes.sequence.app/arbitrum & - - run: pnpm build - - run: pnpm test - - # NOTE: if you'd like to see example of how to run - # tests per package in parallel, see 'v2' branch - # .github/workflows/tests.yml - - # coverage: - # name: Run coverage - # runs-on: ubuntu-latest - # needs: [install] - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-node@v4 - # with: - # node-version: 20 - # - uses: actions/cache@v4 - # id: pnpm-cache - # with: - # path: | - # node_modules - # */*/node_modules - # key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/pnpm.lock') }} - # - run: pnpm dev && (pnpm coverage || true) - # - uses: codecov/codecov-action@v1 - # with: - # fail_ci_if_error: true - # verbose: true - # directory: ./coverage diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000000..582eef2d55 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,127 @@ +name: Verify +on: + workflow_call: + workflow_dispatch: + +jobs: + check: + name: Check + permissions: + contents: write + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_PTOKEN }} + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: Check repo + run: pnpm check:repo + + - name: Check code + run: pnpm check + + - name: Update package versions + run: pnpm version:update + + - uses: stefanzweifel/git-auto-commit-action@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + commit_message: 'chore: format' + commit_user_name: 'github-actions[bot]' + commit_user_email: 'github-actions[bot]@users.noreply.github.com' + + build: + name: Build + needs: check + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: Build + run: pnpm build + + - name: Publint + run: pnpm test:build + + - name: Check for unused files, dependencies, and exports + run: pnpm knip --production + + types: + name: Types + needs: check + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: + typescript-version: ['5.7.3', '5.8.3', 'latest'] + viem-version: ['2.29.2', 'latest'] + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - run: pnpm add -D -w typescript@${{ matrix.typescript-version }} viem@${{ matrix.viem-version }} + + - name: Link packages + run: pnpm preconstruct + + - name: Check types + run: pnpm check:types + + # Redundant with `pnpm check:types` + # If Vitest adds special features in the future, e.g. type coverage, can add this back! + # - name: Test types + # run: pnpm test:typecheck + + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + max-parallel: 3 + matrix: + shard: [1, 2, 3] + total-shards: [3] + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install dependencies + uses: wevm/actions/.github/actions/pnpm@main + + - name: Set up foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run tests + uses: nick-fields/retry@v3 + with: + command: CI=true pnpm test:cov --shard=${{ matrix.shard }}/${{ matrix.total-shards }} --retry=3 --bail=1 + max_attempts: 3 + timeout_minutes: 5 + env: + VITE_MAINNET_FORK_URL: ${{ secrets.VITE_MAINNET_FORK_URL }} + VITE_OPTIMISM_FORK_URL: ${{ secrets.VITE_OPTIMISM_FORK_URL }} + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index e70ecd7f00..1834fe72ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,39 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# Dependencies +.DS_Store +.next +.nuxt +.pnpm-debug.log* +cache +coverage +dist node_modules -.pnp -.pnp.js +tsconfig.tsbuildinfo +*.vitest-temp.json -# Local env files +# local env files .env .env.local .env.development.local .env.test.local .env.production.local - -# Testing -coverage - -# Turbo -.turbo - -# Vercel -.vercel - -# Build Outputs -.next/ -out/ -build -dist - - -# Debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Misc -.DS_Store -*.pem - -# Husky -.husky/ \ No newline at end of file +.envrc + +# proxy packages +packages/cli/config +packages/cli/plugins +packages/core/actions +packages/core/chains +packages/core/codegen +packages/core/experimental +packages/core/internal +packages/core/query +packages/react/actions +packages/react/chains +packages/react/codegen +packages/react/connectors +packages/react/experimental +packages/react/query +packages/vue/actions +packages/vue/chains +packages/vue/connectors +packages/vue/nuxt +packages/vue/query diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..6131d73996 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/signals-implicit-mode"] + path = lib/signals-implicit-mode + url = https://github.com/0xsequence/signals-implicit-mode diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..47687565bf --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +auto-install-peers=false +enable-pre-post-scripts=true +link-workspace-packages=deep +provenance=true +strict-peer-dependencies=false diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..9cb435094a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "biomejs.biome", + "orta.vscode-twoslash-queries", + "Vue.volar" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index dc22920a87..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Launch primitives-cli server", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/packages/wallet/primitives-cli/dist/index.js", - "args": ["server"], - "runtimeArgs": ["--enable-source-maps"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": [ - "${workspaceFolder}/packages/wallet/primitives-cli/dist/**/*.js", - "${workspaceFolder}/packages/wallet/core/dist/**/*.js", - "${workspaceFolder}/packages/wallet/primitives/dist/**/*.js", - "${workspaceFolder}/packages/wallet/wdk/dist/**/*.js" - ], - "sourceMapPathOverrides": { - "../packages/wallet/primitives-cli/src/*": "${workspaceFolder}/packages/wallet/primitives-cli/src/*", - "../packages/wallet/core/src/*": "${workspaceFolder}/packages/wallet/core/src/*", - "../packages/wallet/primitives/src/*": "${workspaceFolder}/packages/wallet/primitives/src/*", - "../packages/wallet/wdk/src/*": "${workspaceFolder}/packages/wallet/wdk/src/*" - } - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 44a73ec3a9..c32e8fa4c3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,17 @@ { - "eslint.workingDirectories": [ - { - "mode": "auto" - } - ] + "editor.defaultFormatter": "biomejs.biome", + "editor.formatOnSave": true, + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.preferences.importModuleSpecifier": "shortest", + "typescript.tsdk": "node_modules/typescript/lib", + "editor.codeActionsOnSave": { + "quickfix.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "[javascript][javascriptreact][json][typescript][typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[vue]": { + "editor.defaultFormatter": "Vue.volar" + } } diff --git a/.vscode/workspace.code-workspace b/.vscode/workspace.code-workspace new file mode 100644 index 0000000000..0d626129da --- /dev/null +++ b/.vscode/workspace.code-workspace @@ -0,0 +1,16 @@ +{ + "folders": [ + { + "name": "docs", + "path": "../docs" + }, + { + "name": "packages", + "path": "../packages" + }, + { + "name": "playgrounds", + "path": "../playgrounds" + } + ] +} diff --git a/CNAME b/CNAME new file mode 100644 index 0000000000..1889d9bae7 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +sequence.app diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000000..5e01254162 --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,10 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0xd2135CfB216b74109775236E36d4b433F1DF507B" + } + }, + "opRetro": { + "projectId": "0xc0615947773148cbc340b175fb9afc98dbb4e0acd31d018b1ee41a5538785abf" + } +} diff --git a/LICENSE b/LICENSE index d645695673..650c3c1c00 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,21 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2022-present weth, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index ae41ffdbd5..15f6f79541 100644 --- a/README.md +++ b/README.md @@ -1,39 +1 @@ -## sequence.js v3 core libraries and SDK - -**NOTE: please see [v2](https://github.com/0xsequence/sequence.js/tree/v2) branch for sequence.js 2.x.x** - ---- - -Sequence v3 core libraries and [wallet-contracts-v3](https://github.com/0xsequence/wallet-contracts-v3) SDK. - -## Packages - -- `@0xsequence/wallet-primitives`: stateless low-level utilities specifically for interacting directly with sequence wallet's smart contracts -- `@0xsequence/wallet-core`: higher level utilities for creating and using sequence wallets -- `@0xsequence/wallet-wdk`: all-in-one wallet development kit for building a sequence wallet product - -## Development - -### Getting Started - -1. Install dependencies: - `pnpm install` - -2. Build all packages: - `pnpm build` - -### Development Workflow - -- Run development mode across all packages: - `pnpm dev` - -- Run tests: - `pnpm test` - - > **Note:** Tests require [anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil) and [forge](https://github.com/foundry-rs/foundry) to be installed. You can run a local anvil instance using `pnpm run test:anvil`. - -- Linting and formatting is enforced via git hooks - -## License - -Apache-2.0 +This is a [Vite](https://vitejs.dev) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..c9a2fbd4c1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +To report a vulnerability, please email us at [dev@wevm.dev](mailto:dev@wevm.dev). We aim to provide an initial response within 48 hours and will keep you updated on the status of the reported vulnerability. + diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..676233afaf --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,21 @@ +# Node.js +# Build a general Node.js project with npm. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript + +trigger: +- master + +pool: + vmImage: ubuntu-latest + +steps: +- task: NodeTool@0 + inputs: + versionSpec: '10.x' + displayName: 'Install Node.js' + +- script: | + npm install + npm run build + displayName: 'npm install and build' diff --git a/biome.json b/biome.json new file mode 100644 index 0000000000..ce99662cb0 --- /dev/null +++ b/biome.json @@ -0,0 +1,89 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "ignore": ["CHANGELOG.md", "pnpm-lock.yaml", "tsconfig.base.json"] + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "linter": { + "ignore": ["packages/create-wagmi/templates/*"], + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "useButtonType": "off" + }, + "correctness": { + "noUnusedVariables": "error", + "useExhaustiveDependencies": "error" + }, + "performance": { + "noBarrelFile": "error", + "noReExportAll": "error", + "noDelete": "off" + }, + "style": { + "noNonNullAssertion": "off", + "useShorthandArrayType": "error" + }, + "suspicious": { + "noArrayIndexKey": "off", + "noConfusingVoidType": "off", + "noConsoleLog": "error", + "noExplicitAny": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "asNeeded" + } + }, + "organizeImports": { + "enabled": true + }, + "overrides": [ + { + "include": ["*.vue"], + "linter": { + "rules": { + "correctness": { + "noUnusedVariables": "off" + } + } + } + }, + { + "include": ["./scripts/**/*.ts"], + "linter": { + "rules": { + "suspicious": { + "noConsoleLog": "off" + } + } + } + }, + { + "include": ["./playgrounds/**"], + "linter": { + "rules": { + "style": { + "useNodejsImportProtocol": "off" + } + } + } + } + ], + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + } +} diff --git a/corepack.tgz b/corepack.tgz new file mode 100644 index 0000000000..afa76b295c Binary files /dev/null and b/corepack.tgz differ diff --git a/extras/docs/.gitignore b/extras/docs/.gitignore deleted file mode 100644 index f886745c52..0000000000 --- a/extras/docs/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# 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* - -# env files (can opt-in for commiting if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/extras/docs/README.md b/extras/docs/README.md deleted file mode 100644 index a98bfa8140..0000000000 --- a/extras/docs/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/extras/docs/app/favicon.ico b/extras/docs/app/favicon.ico deleted file mode 100644 index 718d6fea48..0000000000 Binary files a/extras/docs/app/favicon.ico and /dev/null differ diff --git a/extras/docs/app/fonts/GeistMonoVF.woff b/extras/docs/app/fonts/GeistMonoVF.woff deleted file mode 100644 index f2ae185cbf..0000000000 Binary files a/extras/docs/app/fonts/GeistMonoVF.woff and /dev/null differ diff --git a/extras/docs/app/fonts/GeistVF.woff b/extras/docs/app/fonts/GeistVF.woff deleted file mode 100644 index 1b62daacff..0000000000 Binary files a/extras/docs/app/fonts/GeistVF.woff and /dev/null differ diff --git a/extras/docs/app/globals.css b/extras/docs/app/globals.css deleted file mode 100644 index 6af7ecbbb8..0000000000 --- a/extras/docs/app/globals.css +++ /dev/null @@ -1,50 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -.imgDark { - display: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - - .imgLight { - display: none; - } - .imgDark { - display: unset; - } -} diff --git a/extras/docs/app/layout.tsx b/extras/docs/app/layout.tsx deleted file mode 100644 index 2e5719345e..0000000000 --- a/extras/docs/app/layout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { Metadata } from 'next' -import localFont from 'next/font/local' -import './globals.css' - -const geistSans = localFont({ - src: './fonts/GeistVF.woff', - variable: '--font-geist-sans', -}) -const geistMono = localFont({ - src: './fonts/GeistMonoVF.woff', - variable: '--font-geist-mono', -}) - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - return ( - - {children} - - ) -} diff --git a/extras/docs/app/page.module.css b/extras/docs/app/page.module.css deleted file mode 100644 index 3630662c6f..0000000000 --- a/extras/docs/app/page.module.css +++ /dev/null @@ -1,188 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-synthesis: none; -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -button.secondary { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; - background: transparent; - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -.footer { - font-family: var(--font-geist-sans); - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/extras/docs/app/page.tsx b/extras/docs/app/page.tsx deleted file mode 100644 index 980bd5ff3e..0000000000 --- a/extras/docs/app/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import Image, { type ImageProps } from 'next/image' -import { Button } from '@repo/ui/button' -import styles from './page.module.css' - -type Props = Omit & { - srcLight: string - srcDark: string -} - -const ThemeImage = (props: Props) => { - const { srcLight, srcDark, ...rest } = props - - return ( - <> - - - - ) -} - -export default function Home() { - return ( -
-
- -
    -
  1. - Get started by editing apps/docs/app/page.tsx -
  2. -
  3. Save and see your changes instantly.
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
- -
- -
- ) -} diff --git a/extras/docs/eslint.config.js b/extras/docs/eslint.config.js deleted file mode 100644 index 0fbeffd979..0000000000 --- a/extras/docs/eslint.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { nextJsConfig } from '@repo/eslint-config/next-js' - -/** @type {import("eslint").Linter.Config} */ -export default [ - ...nextJsConfig, - { - ignores: ['next-env.d.ts'], - }, -] diff --git a/extras/docs/next.config.js b/extras/docs/next.config.js deleted file mode 100644 index 2963459c42..0000000000 --- a/extras/docs/next.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const workspaceRoot = path.join(__dirname, '..', '..') - -/** @type {import('next').NextConfig} */ -const nextConfig = { - // Anchor output tracing to the monorepo root so Next.js doesn't pick up - // sibling lockfiles and mis-detect the workspace boundary during lint/build. - outputFileTracingRoot: workspaceRoot, -} - -export default nextConfig diff --git a/extras/docs/package.json b/extras/docs/package.json deleted file mode 100644 index ccf8cc5bbf..0000000000 --- a/extras/docs/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "docs", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "dev": "next dev --turbopack --port 3001", - "build": "next build", - "start": "next start", - "lint": "eslint . --max-warnings 0", - "typecheck": "tsc --noEmit", - "clean": "rimraf .next" - }, - "dependencies": { - "@repo/ui": "workspace:^", - "next": "^15.5.14", - "react": "^19.2.3", - "react-dom": "^19.2.3" - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "eslint": "^9.39.2", - "typescript": "^5.9.3" - } -} diff --git a/extras/docs/public/file-text.svg b/extras/docs/public/file-text.svg deleted file mode 100644 index 9cfb3c9867..0000000000 --- a/extras/docs/public/file-text.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extras/docs/public/globe.svg b/extras/docs/public/globe.svg deleted file mode 100644 index 4230a3d207..0000000000 --- a/extras/docs/public/globe.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/extras/docs/public/next.svg b/extras/docs/public/next.svg deleted file mode 100644 index 5174b28c56..0000000000 --- a/extras/docs/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extras/docs/public/turborepo-dark.svg b/extras/docs/public/turborepo-dark.svg deleted file mode 100644 index dae38fed54..0000000000 --- a/extras/docs/public/turborepo-dark.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/extras/docs/public/turborepo-light.svg b/extras/docs/public/turborepo-light.svg deleted file mode 100644 index ddea915815..0000000000 --- a/extras/docs/public/turborepo-light.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/extras/docs/public/vercel.svg b/extras/docs/public/vercel.svg deleted file mode 100644 index 0164ddc5ad..0000000000 --- a/extras/docs/public/vercel.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/extras/docs/public/window.svg b/extras/docs/public/window.svg deleted file mode 100644 index bbc780069c..0000000000 --- a/extras/docs/public/window.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extras/docs/tsconfig.json b/extras/docs/tsconfig.json deleted file mode 100644 index c2fa4ee5de..0000000000 --- a/extras/docs/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@repo/typescript-config/nextjs.json", - "compilerOptions": { - "plugins": [ - { - "name": "next" - } - ] - }, - "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "next.config.js", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/extras/web/.gitignore b/extras/web/.gitignore deleted file mode 100644 index f886745c52..0000000000 --- a/extras/web/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# 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* - -# env files (can opt-in for commiting if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/extras/web/README.md b/extras/web/README.md deleted file mode 100644 index a98bfa8140..0000000000 --- a/extras/web/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/extras/web/app/favicon.ico b/extras/web/app/favicon.ico deleted file mode 100644 index 718d6fea48..0000000000 Binary files a/extras/web/app/favicon.ico and /dev/null differ diff --git a/extras/web/app/fonts/GeistMonoVF.woff b/extras/web/app/fonts/GeistMonoVF.woff deleted file mode 100644 index f2ae185cbf..0000000000 Binary files a/extras/web/app/fonts/GeistMonoVF.woff and /dev/null differ diff --git a/extras/web/app/fonts/GeistVF.woff b/extras/web/app/fonts/GeistVF.woff deleted file mode 100644 index 1b62daacff..0000000000 Binary files a/extras/web/app/fonts/GeistVF.woff and /dev/null differ diff --git a/extras/web/app/globals.css b/extras/web/app/globals.css deleted file mode 100644 index 6af7ecbbb8..0000000000 --- a/extras/web/app/globals.css +++ /dev/null @@ -1,50 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -.imgDark { - display: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - - .imgLight { - display: none; - } - .imgDark { - display: unset; - } -} diff --git a/extras/web/app/layout.tsx b/extras/web/app/layout.tsx deleted file mode 100644 index 2e5719345e..0000000000 --- a/extras/web/app/layout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { Metadata } from 'next' -import localFont from 'next/font/local' -import './globals.css' - -const geistSans = localFont({ - src: './fonts/GeistVF.woff', - variable: '--font-geist-sans', -}) -const geistMono = localFont({ - src: './fonts/GeistMonoVF.woff', - variable: '--font-geist-mono', -}) - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - return ( - - {children} - - ) -} diff --git a/extras/web/app/page.module.css b/extras/web/app/page.module.css deleted file mode 100644 index 3630662c6f..0000000000 --- a/extras/web/app/page.module.css +++ /dev/null @@ -1,188 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-synthesis: none; -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -button.secondary { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - font-family: var(--font-geist-sans); - border: 1px solid transparent; - transition: background 0.2s, color 0.2s, border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; - background: transparent; - border-color: var(--gray-alpha-200); - min-width: 180px; -} - -.footer { - font-family: var(--font-geist-sans); - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/extras/web/app/page.tsx b/extras/web/app/page.tsx deleted file mode 100644 index 4db7245678..0000000000 --- a/extras/web/app/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import Image, { type ImageProps } from 'next/image' -import { Button } from '@repo/ui/button' -import styles from './page.module.css' - -type Props = Omit & { - srcLight: string - srcDark: string -} - -const ThemeImage = (props: Props) => { - const { srcLight, srcDark, ...rest } = props - - return ( - <> - - - - ) -} - -export default function Home() { - return ( -
-
- -
    -
  1. - Get started by editing apps/web/app/page.tsx -
  2. -
  3. Save and see your changes instantly.
  4. -
- - - -
- -
- ) -} diff --git a/extras/web/eslint.config.js b/extras/web/eslint.config.js deleted file mode 100644 index 0fbeffd979..0000000000 --- a/extras/web/eslint.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { nextJsConfig } from '@repo/eslint-config/next-js' - -/** @type {import("eslint").Linter.Config} */ -export default [ - ...nextJsConfig, - { - ignores: ['next-env.d.ts'], - }, -] diff --git a/extras/web/next.config.js b/extras/web/next.config.js deleted file mode 100644 index 2963459c42..0000000000 --- a/extras/web/next.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const workspaceRoot = path.join(__dirname, '..', '..') - -/** @type {import('next').NextConfig} */ -const nextConfig = { - // Anchor output tracing to the monorepo root so Next.js doesn't pick up - // sibling lockfiles and mis-detect the workspace boundary during lint/build. - outputFileTracingRoot: workspaceRoot, -} - -export default nextConfig diff --git a/extras/web/package.json b/extras/web/package.json deleted file mode 100644 index b8fdea4997..0000000000 --- a/extras/web/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "web", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "dev": "next dev --turbopack --port 3000", - "build": "next build", - "start": "next start", - "lint": "eslint . --max-warnings 0", - "typecheck": "tsc --noEmit", - "clean": "rimraf .next" - }, - "dependencies": { - "@repo/ui": "workspace:^", - "next": "^15.5.14", - "react": "^19.2.3", - "react-dom": "^19.2.3" - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "eslint": "^9.39.2", - "typescript": "^5.9.3" - } -} diff --git a/extras/web/public/file-text.svg b/extras/web/public/file-text.svg deleted file mode 100644 index 9cfb3c9867..0000000000 --- a/extras/web/public/file-text.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extras/web/public/globe.svg b/extras/web/public/globe.svg deleted file mode 100644 index 4230a3d207..0000000000 --- a/extras/web/public/globe.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/extras/web/public/next.svg b/extras/web/public/next.svg deleted file mode 100644 index 5174b28c56..0000000000 --- a/extras/web/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/extras/web/public/turborepo-dark.svg b/extras/web/public/turborepo-dark.svg deleted file mode 100644 index dae38fed54..0000000000 --- a/extras/web/public/turborepo-dark.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/extras/web/public/turborepo-light.svg b/extras/web/public/turborepo-light.svg deleted file mode 100644 index ddea915815..0000000000 --- a/extras/web/public/turborepo-light.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/extras/web/public/vercel.svg b/extras/web/public/vercel.svg deleted file mode 100644 index 0164ddc5ad..0000000000 --- a/extras/web/public/vercel.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/extras/web/public/window.svg b/extras/web/public/window.svg deleted file mode 100644 index bbc780069c..0000000000 --- a/extras/web/public/window.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extras/web/tsconfig.json b/extras/web/tsconfig.json deleted file mode 100644 index c2fa4ee5de..0000000000 --- a/extras/web/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@repo/typescript-config/nextjs.json", - "compilerOptions": { - "plugins": [ - { - "name": "next" - } - ] - }, - "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "next.config.js", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/foundry.lock @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000..f519ce85a7 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Create Wagmi + + +
+ + + diff --git a/lefthook.yml b/lefthook.yml index 5402d7dc7d..c66bba8e98 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -3,15 +3,6 @@ pre-commit: prettier: glob: '**/*.{js,jsx,ts,tsx,json,md,yml,yaml}' run: pnpm prettier --write {staged_files} && git add {staged_files} - lint: - run: pnpm lint - typecheck: - run: pnpm typecheck - syncpack: - glob: - - "package.json" - - "packages/**/package.json" - run: pnpm deps:lint pre-push: commands: diff --git a/package.json b/package.json index 92559c5103..ddc9a6a5a4 100644 --- a/package.json +++ b/package.json @@ -1,60 +1,136 @@ { - "name": "sequence-core", - "license": "Apache-2.0", "private": true, + "type": "module", "scripts": { - "build:all": "turbo build", - "build:packages": "turbo build --filter=\"./packages/**/*\"", - "build": "pnpm build:packages", - "dev": "turbo dev", - "test": "turbo test --concurrency=1", - "lint": "turbo lint --continue", - "format": "prettier --list-different --write \"**/*.{ts,tsx,md}\"", - "typecheck": "turbo typecheck", - "postinstall": "lefthook install", - "dev:server": "node packages/wallet/primitives-cli/dist/index.js server", - "reinstall": "rimraf -g ./**/node_modules && pnpm install", - "test:anvil": "anvil --fork-url https://nodes.sequence.app/arbitrum", - "clean": "turbo clean", - "deps:lint": "syncpack lint --dependency-types prod,dev", - "deps:fix": "syncpack fix" + "build": "pnpm run --r --filter \"./packages/**\" build", + "changeset:prepublish": "pnpm version:update && pnpm build && bun scripts/formatPackageJson.ts && bun scripts/generateProxyPackages.ts", + "changeset:publish": "pnpm changeset:prepublish && changeset publish", + "changeset:version": "changeset version && pnpm version:update && pnpm format", + "check": "biome check --write", + "check:repo": "sherif -i viem", + "check:types": "pnpm run --r --parallel check:types && tsc --noEmit", + "check:unused": "pnpm clean && knip", + "clean": "pnpm run --r --parallel clean && rm -rf packages/**/*.json.tmp", + "deps": "pnpx taze -r", + "dev": "pnpm dev:react", + "dev:cli": "pnpm --filter cli dev", + "dev:core": "pnpm --filter vite-core dev", + "dev:create-wagmi": "pnpm --filter create-wagmi dev", + "dev:next": "pnpm --filter next-app dev", + "dev:nuxt": "pnpm --filter nuxt-app dev", + "dev:react": "pnpm --filter vite-react dev", + "dev:vue": "pnpm --filter vite-vue dev", + "docs:dev": "pnpm --filter site dev", + "format": "biome format --write", + "postinstall": "pnpm preconstruct", + "preconstruct": "bun scripts/preconstruct.ts", + "preinstall": "pnpx only-allow pnpm", + "prepare": "pnpm simple-git-hooks", + "test": "vitest", + "test:build": "bun scripts/generateProxyPackages.ts && pnpm run --r --parallel test:build", + "test:cli": "vitest --project @wagmi/cli", + "test:connectors": "vitest --project @wagmi/connectors", + "test:core": "vitest --project @wagmi/core", + "test:create-wagmi": "vitest --project create-wagmi", + "test:cov": "vitest run --coverage", + "test:react": "vitest --project wagmi", + "test:typecheck": "vitest typecheck", + "test:update": "vitest --update", + "test:vue": "vitest --project @wagmi/vue", + "version:update": "bun scripts/updateVersion.ts", + "version:update:viem": "bun scripts/updateViemVersion.ts" }, "devDependencies": { - "@changesets/cli": "^2.29.8", - "lefthook": "^2.1.1", - "prettier": "^3.8.1", - "rimraf": "^6.1.3", - "syncpack": "^14.0.0", - "turbo": "^2.8.10", - "typescript": "^5.9.3" + "@arethetypeswrong/cli": "^0.16.4", + "@biomejs/biome": "^1.9.4", + "@changesets/changelog-github": "0.4.6", + "@changesets/cli": "^2.27.8", + "@types/bun": "^1.1.10", + "@vitest/coverage-v8": "^3.2.4", + "@wagmi/test": "workspace:*", + "bun": "^1.1.30", + "happy-dom": "^20.0.2", + "knip": "^5.30.6", + "prool": "^0.0.23", + "publint": "^0.3.0", + "sherif": "^1.0.0", + "simple-git-hooks": "^2.11.1", + "typescript": "5.8.3", + "viem": "2.31.4", + "vitest": "^2.1.9" }, + "packageManager": "pnpm@9.11.0", "pnpm": { - "overrides": { - "ox": "^0.9.17" + "peerDependencyRules": { + "ignoreMissing": [ + "@algolia/client-search", + "react", + "react-native", + "search-insights" + ] } }, - "packageManager": "pnpm@10.24.0", "engines": { - "node": ">=18" + "node": "22.x" }, - "syncpack": { - "source": [ - "package.json", - "packages/**/package.json", - "extras/**/package.json", - "repo/**/package.json" + "simple-git-hooks": { + "pre-commit": "pnpm check" + }, + "knip": { + "ignore": ["**/templates/**", "**/hardhat.config.js"], + "ignoreBinaries": ["only-allow"], + "ignoreWorkspaces": [ + "packages/register-tests/**", + "packages/test", + "playgrounds/**" ], - "versionGroups": [ - { - "label": "Use workspace protocol when developing local packages", - "dependencyTypes": [ - "!local" + "workspaces": { + ".": { + "project": "scripts/*.ts" + }, + "packages/cli": { + "entry": [ + "src/cli.ts!", + "src/exports/{config,index,plugins}.ts!", + "types/*.d.ts!" + ], + "ignore": ["test/{constants,setup,utils}.ts"] + }, + "packages/connectors": { + "entry": "src/exports/index.ts!" + }, + "packages/core": { + "entry": "src/exports/{actions,chains,codegen,experimental,index,internal,query}.ts!", + "ignore": ["test/setup.ts"], + "ignoreDependencies": ["@tanstack/query-core"] + }, + "packages/create-wagmi": { + "entry": "src/cli.ts!" + }, + "packages/react": { + "entry": [ + "src/exports/{actions,chains,codegen,connectors,experimental,index,query}.ts!", + "src/exports/actions/experimental.ts!" ], - "dependencies": [ - "$LOCAL" + "ignore": ["test/setup.ts"] + }, + "packages/test": { + "entry": [ + "src/{globalSetup,setup}.ts!", + "src/exports/{index,react}.ts!" + ] + }, + "packages/vue": { + "entry": [ + "src/exports/{actions,chains,connectors,index,nuxt,query}.ts!", + "src/exports/actions/experimental.ts!" ], - "pinVersion": "workspace:^" + "ignore": ["src/nuxt/runtime/*", "test/setup.ts"], + "ignoreDependencies": ["nuxt"] + }, + "site": { + "project": ["**/*.ts", "**/*.tsx"] } - ] + } } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 0000000000..a2911e2304 --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1,449 @@ +# @wagmi/cli + +## 2.3.1 + +### Patch Changes + +- [#4655](https://github.com/wevm/wagmi/pull/4655) [`43241c8417f3c342036bb46ec8e507d052ae2691`](https://github.com/wevm/wagmi/commit/43241c8417f3c342036bb46ec8e507d052ae2691) Thanks [@tmm](https://github.com/tmm)! - Bumped internal deps. + +## 2.3.0 + +### Minor Changes + +- [#4629](https://github.com/wevm/wagmi/pull/4629) [`66dec7d75d580b3121ebc7e8162c1f9ae37cfd41`](https://github.com/wevm/wagmi/commit/66dec7d75d580b3121ebc7e8162c1f9ae37cfd41) Thanks [@allezxandre](https://github.com/allezxandre)! - Upgraded to Sourcify v2 API in `sourcify` plugin + +## 2.2.1 + +### Patch Changes + +- [`7b0dbe3886c1a7c6dbbdab945d7436ec20ad8f93`](https://github.com/wevm/wagmi/commit/7b0dbe3886c1a7c6dbbdab945d7436ec20ad8f93) Thanks [@tmm](https://github.com/tmm)! - Updated block explorer chains. + +## 2.2.0 + +### Minor Changes + +- [#4503](https://github.com/wevm/wagmi/pull/4503) [`8fce8a6f97aa2ee5fd1bda6a3ece422b10324b5a`](https://github.com/wevm/wagmi/commit/8fce8a6f97aa2ee5fd1bda6a3ece422b10324b5a) Thanks [@tmm](https://github.com/tmm)! - Updated Etherscan Plugin to use Etherscan API v2. + +- [#4507](https://github.com/wevm/wagmi/pull/4507) [`6f09cc57935891e1c67d6df3459f6998985c69dc`](https://github.com/wevm/wagmi/commit/6f09cc57935891e1c67d6df3459f6998985c69dc) Thanks [@tmm](https://github.com/tmm)! - Added `tryFetchProxyImplementation` flag to Etherscan Plugin to enable fetching the implementation ABI instead of the proxy ABI. + +## 2.1.22 + +### Patch Changes + +- [#4462](https://github.com/wevm/wagmi/pull/4462) [`0b2238d27cecbcd33aee64fb0e30ddc18b6ddf74`](https://github.com/wevm/wagmi/commit/0b2238d27cecbcd33aee64fb0e30ddc18b6ddf74) Thanks [@groninge01](https://github.com/groninge01)! - Added Sonic to Etherscan plugin. + +## 2.1.21 + +### Patch Changes + +- [#4457](https://github.com/wevm/wagmi/pull/4457) [`21ec74da7f93fc13e253d7b35ddeddc23422a6c1`](https://github.com/wevm/wagmi/commit/21ec74da7f93fc13e253d7b35ddeddc23422a6c1) Thanks [@tmm](https://github.com/tmm)! - Removed internal dependency. + +## 2.1.20 + +### Patch Changes + +- [#4450](https://github.com/wevm/wagmi/pull/4450) [`7b9a6bb35881b657a00bdd7ccd7edea32660f5bf`](https://github.com/wevm/wagmi/commit/7b9a6bb35881b657a00bdd7ccd7edea32660f5bf) Thanks [@tmm](https://github.com/tmm)! - Removed internal usage of `fs-extra`. + +## 2.1.19 + +### Patch Changes + +- [#4449](https://github.com/wevm/wagmi/pull/4449) [`3fa5c238baa13d948e89974b0bb8530f8fa264fd`](https://github.com/wevm/wagmi/commit/3fa5c238baa13d948e89974b0bb8530f8fa264fd) Thanks [@tmm](https://github.com/tmm)! - Removed `ora` for `nanospinner`. + +## 2.1.18 + +### Patch Changes + +- [#4399](https://github.com/wevm/wagmi/pull/4399) [`bc18673e4c272e3b60a1b6016934fe3fbeb6d93a`](https://github.com/wevm/wagmi/commit/bc18673e4c272e3b60a1b6016934fe3fbeb6d93a) Thanks [@tmm](https://github.com/tmm)! - Added Polygon Amoy to Sourcify and Etherscan plugins. + +## 2.1.17 + +### Patch Changes + +- [#4370](https://github.com/wevm/wagmi/pull/4370) [`cb58b1ea3ad40e77210f24eb598f9d2306db998c`](https://github.com/wevm/wagmi/commit/cb58b1ea3ad40e77210f24eb598f9d2306db998c) Thanks [@talentlessguy](https://github.com/talentlessguy)! - Bumped internal dependencies. + +## 2.1.16 + +### Patch Changes + +- [#4224](https://github.com/wevm/wagmi/pull/4224) [`b0eb89c2a0781bb3434996fa53ee7ceb3bb44db9`](https://github.com/wevm/wagmi/commit/b0eb89c2a0781bb3434996fa53ee7ceb3bb44db9) Thanks [@roderik](https://github.com/roderik)! - Fixed package detection for Bun. + +## 2.1.15 + +### Patch Changes + +- [`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e) Thanks [@tmm](https://github.com/tmm)! - Improved TypeScript `'exactOptionalPropertyTypes'` support. + +## 2.1.14 + +### Patch Changes + +- [#4120](https://github.com/wevm/wagmi/pull/4120) [`59407bf1276a46e6f1f22a370dde71c92524cd0f`](https://github.com/wevm/wagmi/commit/59407bf1276a46e6f1f22a370dde71c92524cd0f) Thanks [@tmm](https://github.com/tmm)! - Fixed an issue where the Foundry and Hardhat plugins' `exclude` option was ignored. + +## 2.1.13 + +### Patch Changes + +- [`7264d1f450727f6ba0cbea8aa1c7a83e22a5bf20`](https://github.com/wevm/wagmi/commit/7264d1f450727f6ba0cbea8aa1c7a83e22a5bf20) Thanks [@tmm](https://github.com/tmm)! - Fixed generate not exiting for long-running processes. + +## 2.1.12 + +### Patch Changes + +- [`ac038b29623ccb0d2fee40d9f943c8df28138dac`](https://github.com/wevm/wagmi/commit/ac038b29623ccb0d2fee40d9f943c8df28138dac) Thanks [@tmm](https://github.com/tmm)! - Updated Foundry default excludes. + +## 2.1.11 + +### Patch Changes + +- [#4084](https://github.com/wevm/wagmi/pull/4084) [`b54203bf8fa911e6f14b9675980cf38fb95d7d3e`](https://github.com/wevm/wagmi/commit/b54203bf8fa911e6f14b9675980cf38fb95d7d3e) Thanks [@tmm](https://github.com/tmm)! - Reduced internal dependencies. + +## 2.1.10 + +### Patch Changes + +- [#4051](https://github.com/wevm/wagmi/pull/4051) [`275e78b0e585f0ec9da2f9661ce9990aed18e9f4`](https://github.com/wevm/wagmi/commit/275e78b0e585f0ec9da2f9661ce9990aed18e9f4) Thanks [@tmm](https://github.com/tmm)! - Updated Sourcify plugin internals. + +## 2.1.9 + +### Patch Changes + +- [`f9346dbcffaf57a8949cb96e43df111a89d733b1`](https://github.com/wevm/wagmi/commit/f9346dbcffaf57a8949cb96e43df111a89d733b1) Thanks [@tmm](https://github.com/tmm)! - Updated Foundry plugin default excludes. + +## 2.1.8 + +### Patch Changes + +- [#3957](https://github.com/wevm/wagmi/pull/3957) [`7d00680f73b090eb34af928ae74277bec1973953`](https://github.com/wevm/wagmi/commit/7d00680f73b090eb34af928ae74277bec1973953) Thanks [@cstoneham](https://github.com/cstoneham)! - Added Blast to Etherscan plugin + +## 2.1.7 + +### Patch Changes + +- [`1122678bbad0232590bd4060a73752de2c84982d`](https://github.com/wevm/wagmi/commit/1122678bbad0232590bd4060a73752de2c84982d) Thanks [@tmm](https://github.com/tmm)! - Published unpublished changes in [#3756](https://github.com/wevm/wagmi/pull/3756). + +## 2.1.6 + +### Patch Changes + +- [#3756](https://github.com/wevm/wagmi/pull/3756) [`c7d6f467`](https://github.com/wevm/wagmi/commit/c7d6f4679125fd2f6cca5b5ef362abf47e37f934) Thanks [@jrfrantz](https://github.com/jrfrantz)! - Added basescan to etherscan cli plugin + +## 2.1.5 + +### Patch Changes + +- [`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d) Thanks [@tmm](https://github.com/tmm)! - Added title to CLI process. + +- [#3723](https://github.com/wevm/wagmi/pull/3723) [`d6bc98ca`](https://github.com/wevm/wagmi/commit/d6bc98ca0ce9081f192f62e0b0fcfea3cb07a2bb) Thanks [@leecobaby](https://github.com/leecobaby)! - Broadened TypeScript detection. + +## 2.1.4 + +### Patch Changes + +- [#3737](https://github.com/wevm/wagmi/pull/3737) [`11020fed`](https://github.com/wevm/wagmi/commit/11020fedfc68639eace241e328331cff43bf91af) Thanks [@oskarvu](https://github.com/oskarvu)! - Added Gnosis to Etherscan plugin. + +## 2.1.3 + +### Patch Changes + +- [#3660](https://github.com/wevm/wagmi/pull/3660) [`11a22a23`](https://github.com/wevm/wagmi/commit/11a22a23d88c025cde9c91610e9ddf62cd4fa650) Thanks [@JazzBashara](https://github.com/JazzBashara)! - Replaced SnowTrace with SnowScan for the Etherscan plugin + +## 2.1.2 + +### Patch Changes + +- [#3641](https://github.com/wevm/wagmi/pull/3641) [`0a866403`](https://github.com/wevm/wagmi/commit/0a866403182ea6b8ba7f976c45be294e48fb7de8) Thanks [@cmwhited](https://github.com/cmwhited)! - Added Arbitrum Sepolia testnet to Etherscan plugin + +- [#3633](https://github.com/wevm/wagmi/pull/3633) [`a1d3d1ab`](https://github.com/wevm/wagmi/commit/a1d3d1ab2b023c61c0dbb5d7bf867a9fca673630) Thanks [@pegahcarter](https://github.com/pegahcarter)! - Added Fraxtal to Etherscan plugin + +- [#3616](https://github.com/wevm/wagmi/pull/3616) [`2a9f4473`](https://github.com/wevm/wagmi/commit/2a9f4473adc5bcdddf388389387ed5459583769e) Thanks [@petermazzocco](https://github.com/petermazzocco)! - Added Holesky Testnet to Etherscan Plugin + +## 2.1.1 + +### Patch Changes + +- [#3579](https://github.com/wevm/wagmi/pull/3579) [`a057919c`](https://github.com/wevm/wagmi/commit/a057919ca3942adeed90af2e343403dc5274e84c) Thanks [@FaisalAli19](https://github.com/FaisalAli19)! - Added Optimism Sepolia Etherscan support + +## 2.1.0 + +### Minor Changes + +- [#3506](https://github.com/wevm/wagmi/pull/3506) [`134eb4a1`](https://github.com/wevm/wagmi/commit/134eb4a1e0e29aab87bd5c7cdf05b06dfd7c4fc4) Thanks [@vmaark](https://github.com/vmaark)! - Added resolution of TypeScript Wagmi CLI config to determine if TypeScript generated output is allowed. + +## 2.0.4 + +### Patch Changes + +- [#3462](https://github.com/wevm/wagmi/pull/3462) [`d25573ea`](https://github.com/wevm/wagmi/commit/d25573ea03358f967953e37c176b220a7b341769) Thanks [@cruzdanilo](https://github.com/cruzdanilo)! - Upgraded dependencies + +## 2.0.3 + +### Patch Changes + +- [#3410](https://github.com/wevm/wagmi/pull/3410) [`55e31c3e`](https://github.com/wevm/wagmi/commit/55e31c3e96c2cbd1d9eb44e5a89f4365489c8310) Thanks [@o-az](https://github.com/o-az)! - Fixed actions plugin issue where `functionName` was used instead of `eventName` for generated contract event actions. + +## 2.0.2 + +### Patch Changes + +- [#3371](https://github.com/wevm/wagmi/pull/3371) [`8294d9e5`](https://github.com/wevm/wagmi/commit/8294d9e5b358018ba869b2018cd7ed95462e021f) Thanks [@iceanddust](https://github.com/iceanddust)! - Fixed prop name when generating contract event watch hooks + +## 2.0.1 + +### Major Changes + +- [#3333](https://github.com/wevm/wagmi/pull/3333) [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a) Thanks [@tmm](https://github.com/tmm)! - Wagmi CLI 2.0. + + [Breaking Changes & Migration Guide](https://wagmi.sh/cli/guides/migrate-from-v1-to-v2) + +## 1.5.2 + +### Patch Changes + +- [#3051](https://github.com/wagmi-dev/wagmi/pull/3051) [`4704d351`](https://github.com/wagmi-dev/wagmi/commit/4704d351164d39704a4e375c06525554fcc8340e) Thanks [@oxSaturn](https://github.com/oxSaturn)! - Fixed ESM require issue for prettier + +## 1.5.1 + +### Patch Changes + +- [#3035](https://github.com/wagmi-dev/wagmi/pull/3035) [`187bf96c`](https://github.com/wagmi-dev/wagmi/commit/187bf96c9fd31675b9d17a7cb4d4e24eea3fa777) Thanks [@cruzdanilo](https://github.com/cruzdanilo)! - ignore foundry invariant lib + +## 1.5.0 + +### Minor Changes + +- [#2956](https://github.com/wevm/wagmi/pull/2956) [`2abeb285`](https://github.com/wevm/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895) Thanks [@tmm](https://github.com/tmm)! - Replaced `@wagmi/chains` with `viem/chains`. + +## 1.4.1 + +### Patch Changes + +- [#2962](https://github.com/wevm/wagmi/pull/2962) [`8ac5b572`](https://github.com/wevm/wagmi/commit/8ac5b57254f77eeb0e07dd83f7d49f396d4581d8) Thanks [@tmm](https://github.com/tmm)! - Fixed esbuild version + +## 1.4.0 + +### Minor Changes + +- [#2946](https://github.com/wevm/wagmi/pull/2946) [`1c3228bf`](https://github.com/wevm/wagmi/commit/1c3228bf3fe99b0900b2c9a223c9b81c70bdcd90) Thanks [@tomquirk](https://github.com/tomquirk)! - Added default chain ID to generated `useContractRead` hook. + +### Patch Changes + +- [#2547](https://github.com/wevm/wagmi/pull/2547) [`8c3889fe`](https://github.com/wevm/wagmi/commit/8c3889fe82c5a1ddb29e74e3863ea6f4917b777a) Thanks [@Iamshankhadeep](https://github.com/Iamshankhadeep)! - Deterministic CLI output + +- [#2958](https://github.com/wevm/wagmi/pull/2958) [`b31f36d5`](https://github.com/wevm/wagmi/commit/b31f36d522a634f53d44349d6a9ea47f59d84d7a) Thanks [@tmm](https://github.com/tmm)! - Removed generated file header + +- [#2960](https://github.com/wevm/wagmi/pull/2960) [`5d4c4592`](https://github.com/wevm/wagmi/commit/5d4c4592009568cd0b096906a424f27469721a42) Thanks [@tmm](https://github.com/tmm)! - Updated esbuild version + +## 1.3.0 + +### Minor Changes + +- [#2616](https://github.com/wevm/wagmi/pull/2616) [`c282a8f7`](https://github.com/wevm/wagmi/commit/c282a8f786d57fec77c931fe99dc20220e843bc8) Thanks [@portdeveloper](https://github.com/portdeveloper)! - Added sepolia chain id + +## 1.2.1 + +### Patch Changes + +- [#2607](https://github.com/wevm/wagmi/pull/2607) [`79335b4c`](https://github.com/wevm/wagmi/commit/79335b4c0fcd5e8152a2a1d28314c634db9d9cbf) Thanks [@roninjin10](https://github.com/roninjin10)! - Fixed opitmism goerli chain id + +## 1.2.0 + +### Minor Changes + +- [#2536](https://github.com/wevm/wagmi/pull/2536) [`85e9760a`](https://github.com/wevm/wagmi/commit/85e9760a140cb169ac6236d9466b96e2105dd193) Thanks [@tmm](https://github.com/tmm)! - Changed `Address` type import from ABIType to viem. + +## 1.1.0 + +### Minor Changes + +- [#2482](https://github.com/wevm/wagmi/pull/2482) [`8764b54a`](https://github.com/wevm/wagmi/commit/8764b54aab68020063946112e8fe52aff650c99c) Thanks [@tmm](https://github.com/tmm)! - Bumped minimum TypeScript version to v5.0.4. + +### Patch Changes + +- [#2484](https://github.com/wevm/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wevm/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated `abitype` to 0.8.7 + +- [#2484](https://github.com/wevm/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wevm/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.0.3 + +### Patch Changes + +- [#2441](https://github.com/wevm/wagmi/pull/2441) [`326edee4`](https://github.com/wevm/wagmi/commit/326edee4bc85db84a7a4e3768e33785849ab8d8e) Thanks [@tmm](https://github.com/tmm)! - Fixed internal type issue + +## 1.0.2 + +### Patch Changes + +- [#2430](https://github.com/wevm/wagmi/pull/2430) [`71d92029`](https://github.com/wevm/wagmi/commit/71d92029ee4344842cd41698858a330fee95b6e0) Thanks [@tmm](https://github.com/tmm)! - Added message when command is not found. + +## 1.0.1 + +### Patch Changes + +- [`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af) Thanks [@jxom](https://github.com/jxom)! - Downgraded abitype. + +## 1.0.0 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`5be0655c`](https://github.com/wevm/wagmi/commit/5be0655c8e48b25d38009022461fbf611af54349) Thanks [@jxom](https://github.com/jxom)! - Released v1. Read [Migration Guide](https://next.wagmi.sh/react/migration-guide#1xx-breaking-changes). + +## 1.0.0-next.7 + +### Patch Changes + +- Fixed react plugin generic. + +## 1.0.0-next.6 + +### Major Changes + +- Updated references. + +## 1.0.0-next.5 + +### Major Changes + +- Added `config.setConnectors` + +## 1.0.0-next.4 + +### Major Changes + +- Updated viem. + Removed `goerli` export from main entrypoint. + +## 1.0.0-next.3 + +### Major Changes + +- Updated references. + +## 1.0.0-next.2 + +### Major Changes + +- Updated dependencies + +### Patch Changes + +- Updated dependencies []: + - @wagmi/chains@1.0.0-next.0 + +## 1.0.0-next.1 + +### Major Changes + +- updated viem + +## 1.0.0-next.0 + +### Major Changes + +- [`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb) Thanks [@jxom](https://github.com/jxom)! - Released v1. + +### Patch Changes + +- Updated dependencies [[`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb)]: + - @wagmi/core@1.0.0-next.0 + - wagmi@1.0.0-next.0 + +## 0.1.15 + +### Patch Changes + +- [#2145](https://github.com/wevm/wagmi/pull/2145) [`2520743c`](https://github.com/wevm/wagmi/commit/2520743c417a158a00d5edca13a9aa92cefb0cfd) Thanks [@tmm](https://github.com/tmm)! - Fixed issue using Hardhat Plugin with npm. + +## 0.1.14 + +### Patch Changes + +- [#2039](https://github.com/wevm/wagmi/pull/2039) [`bac893ab`](https://github.com/wevm/wagmi/commit/bac893ab26012d4d8741c4f80e8b8813aee26f0c) Thanks [@tmm](https://github.com/tmm)! - Updated references. + +- [#2039](https://github.com/wevm/wagmi/pull/2039) [`bac893ab`](https://github.com/wevm/wagmi/commit/bac893ab26012d4d8741c4f80e8b8813aee26f0c) Thanks [@tmm](https://github.com/tmm)! - Fixed Actions plugin `overridePackageName` option. + +## 0.1.13 + +### Patch Changes + +- [#2000](https://github.com/wevm/wagmi/pull/2000) [`01254765`](https://github.com/wevm/wagmi/commit/01254765eb37b77aca26500c00c721f08a260912) Thanks [@tmm](https://github.com/tmm)! - Fixed React plugin name conflict. + +## 0.1.12 + +### Patch Changes + +- [#1992](https://github.com/wevm/wagmi/pull/1992) [`efc93cad`](https://github.com/wevm/wagmi/commit/efc93cadacdb9c9960644dabe4ae837d384df52b) Thanks [@tmm](https://github.com/tmm)! - Refactored internals from ethers to viem. + +## 0.1.11 + +### Patch Changes + +- [#1916](https://github.com/wevm/wagmi/pull/1916) [`950490fd`](https://github.com/wevm/wagmi/commit/950490fd132b3fb5b3455e77b58d70f134b8e5c9) Thanks [@technophile-04](https://github.com/technophile-04)! - Updated React plugin to use `Address` type instead of hardcoding `` `0x{string}` ``. + +## 0.1.10 + +### Patch Changes + +- [#1892](https://github.com/wevm/wagmi/pull/1892) [`d3d6973b`](https://github.com/wevm/wagmi/commit/d3d6973ba9407e490140d2434eb83aad88d6e10d) Thanks [@greg-schrammel](https://github.com/greg-schrammel)! - Fixed generated read hooks `select` type. + +## 0.1.9 + +### Patch Changes + +- [#1886](https://github.com/wevm/wagmi/pull/1886) [`36e119c6`](https://github.com/wevm/wagmi/commit/36e119c6d4bc28a7ae15c9602d0c613bc9681356) Thanks [@roninjin10](https://github.com/roninjin10)! - Fixed package detection for yarn^3 + +## 0.1.8 + +### Patch Changes + +- [#1884](https://github.com/wevm/wagmi/pull/1884) [`cc03bb44`](https://github.com/wevm/wagmi/commit/cc03bb44268874f95203de67f6d32586e34c0857) Thanks [@roninjin10](https://github.com/roninjin10)! - Added better compatibility for yarn@^3 in `@wagmi/cli`. + +## 0.1.7 + +### Patch Changes + +- [#1841](https://github.com/wevm/wagmi/pull/1841) [`cb707f01`](https://github.com/wevm/wagmi/commit/cb707f01cbdcc62a70cf5c8a162d77948d6b6a56) Thanks [@tmm](https://github.com/tmm)! - Added [Sourcify](https://sourcify.dev) CLI plugin. + +## 0.1.6 + +### Patch Changes + +- [#1803](https://github.com/wevm/wagmi/pull/1803) [`09b13538`](https://github.com/wevm/wagmi/commit/09b13538abcde879034293cae39551c30cc81445) Thanks [@shotaronowhere](https://github.com/shotaronowhere)! - Swapped deprecated Arbitrum Rinkeby for Arbitrum Goerli URL for Etherscan Plugin. + +## 0.1.5 + +### Patch Changes + +- [#1788](https://github.com/wevm/wagmi/pull/1788) [`c3e16d82`](https://github.com/wevm/wagmi/commit/c3e16d82c9c39b8b1c2f3c51037e11d642a20cd6) Thanks [@tmm](https://github.com/tmm)! - Fixed CLI import + +## 0.1.4 + +### Patch Changes + +- [#1779](https://github.com/wevm/wagmi/pull/1779) [`97346750`](https://github.com/wevm/wagmi/commit/973467505dc2bb46198a3e9fe6072306170d24c0) Thanks [@tmm](https://github.com/tmm)! - Made `project` optional for Foundry plugin + +## 0.1.3 + +### Patch Changes + +- [#1754](https://github.com/wevm/wagmi/pull/1754) [`298728b5`](https://github.com/wevm/wagmi/commit/298728b5918fa15b6b5b082597204a268d4b01f1) Thanks [@tmm](https://github.com/tmm)! - Updated project resolution for Foundry and Hardhat plugins. + +- [#1738](https://github.com/wevm/wagmi/pull/1738) [`37c221d0`](https://github.com/wevm/wagmi/commit/37c221d0f4d175084e23a6b172d72f177bfa0c81) Thanks [@roninjin10](https://github.com/roninjin10)! - Added automatic Foundry config detection for artifacts directory. + +## 0.1.2 + +### Patch Changes + +- [#1743](https://github.com/wevm/wagmi/pull/1743) [`379315fa`](https://github.com/wevm/wagmi/commit/379315fa359c3118b5d200ec50db3812b0cdd984) Thanks [@kyscott18](https://github.com/kyscott18)! - Add celoscan to `etherscan` plugin + +## 0.1.1 + +### Patch Changes + +- [#1736](https://github.com/wevm/wagmi/pull/1736) [`7c43e431`](https://github.com/wevm/wagmi/commit/7c43e431e2eb970610cc6490cee6a4093655a683) Thanks [@tmm](https://github.com/tmm)! - Fixed generated address object key type. + +## 0.1.0 + +### Minor Changes + +- [#1732](https://github.com/wevm/wagmi/pull/1732) [`01e21897`](https://github.com/wevm/wagmi/commit/01e2189747a5c22dc758c6d719b4145adc2a643c) Thanks [@tmm](https://github.com/tmm)! - Initial release diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000000..640acb22d2 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,13 @@ +# @wagmi/cli + +Manage and generate code from Ethereum ABIs + +## Installation + +```bash +pnpm add @wagmi/cli +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000000..f3848081e5 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,94 @@ +{ + "name": "@wagmi/cli", + "description": "Manage and generate code from Ethereum ABIs", + "version": "2.3.1", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/cli" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo config plugins", + "dev": "bun src/cli.ts", + "test:build": "publint --strict && attw --pack --ignore-rules cjs-resolves-to-esm" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "/config", + "/plugins" + ], + "bin": { + "wagmi": "./dist/esm/cli.js" + }, + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./config": { + "types": "./dist/types/exports/config.d.ts", + "default": "./dist/esm/exports/config.js" + }, + "./plugins": { + "types": "./dist/types/exports/plugins.d.ts", + "default": "./dist/esm/exports/plugins.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "config": ["./dist/types/exports/config.d.ts"], + "plugins": ["./dist/types/exports/plugins.d.ts"] + } + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "dependencies": { + "abitype": "^1.0.4", + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "change-case": "^5.4.4", + "chokidar": "4.0.3", + "dedent": "^1.5.3", + "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0", + "esbuild": "~0.27.0", + "escalade": "3.2.0", + "fdir": "^6.1.1", + "nanospinner": "1.2.2", + "pathe": "^2.0.3", + "picocolors": "^1.0.0", + "picomatch": "^4.0.2", + "prettier": "^3.0.3", + "viem": "2.x", + "zod": "^3.22.3" + }, + "devDependencies": { + "@types/dedent": "^0.7.2", + "@types/node": "^22.14.0", + "fixturez": "^1.1.0", + "msw": "^2.4.9" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": ["wagmi", "eth", "ethereum", "dapps", "wallet", "web3", "cli"] +} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts new file mode 100644 index 0000000000..551543bb2b --- /dev/null +++ b/packages/cli/src/cli.ts @@ -0,0 +1,53 @@ +#!/usr/bin/env node +import { cac } from 'cac' + +import { type Generate, generate } from './commands/generate.js' +import { type Init, init } from './commands/init.js' +import * as logger from './logger.js' +import { version } from './version.js' + +const cli = cac('wagmi') + +cli + .command('generate', 'generate code based on configuration') + .option('-c, --config ', '[string] path to config file') + .option('-r, --root ', '[string] root path to resolve config from') + .option('-w, --watch', '[boolean] watch for changes') + .example((name) => `${name} generate`) + .action(async (options: Generate) => { + await generate(options) + if (!options.watch) process.exit(0) + }) + +cli + .command('init', 'create configuration file') + .option('-c, --config ', '[string] path to config file') + .option('-r, --root ', '[string] root path to resolve config from') + .example((name) => `${name} init`) + .action(async (options: Init) => { + await init(options) + process.exit(0) + }) + +cli.help() +cli.version(version) + +void (async () => { + try { + process.title = 'node (wagmi)' + } catch {} + + try { + // Parse CLI args without running command + cli.parse(process.argv, { run: false }) + if (!cli.matchedCommand) { + if (cli.args.length === 0) { + if (!cli.options.help && !cli.options.version) cli.outputHelp() + } else throw new Error(`Unknown command: ${cli.args.join(' ')}`) + } + await cli.runMatchedCommand() + } catch (error) { + logger.error(`\n${(error as Error).message}`) + process.exit(1) + } +})() diff --git a/packages/cli/src/commands/generate.test.ts b/packages/cli/src/commands/generate.test.ts new file mode 100644 index 0000000000..91e4265216 --- /dev/null +++ b/packages/cli/src/commands/generate.test.ts @@ -0,0 +1,409 @@ +import { readFile } from 'node:fs/promises' +import dedent from 'dedent' +import { resolve } from 'pathe' +import { afterEach, beforeEach, expect, test, vi } from 'vitest' + +import { createFixture, typecheck, watchConsole } from '../../test/utils.js' +import { generate } from './generate.js' + +let console: ReturnType +beforeEach(() => { + console = watchConsole() + vi.useFakeTimers() + + const date = new Date(2023, 0, 30, 12) + vi.setSystemTime(date) +}) + +afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() +}) + +test('generates output', async () => { + const { dir } = await createFixture({ + files: { + tsconfig: true, + 'wagmi.config.js': dedent` + export default { + out: 'generated.js', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await generate() + + expect(console.formatted).toMatchInlineSnapshot(` + "- Validating plugins + √ Validating plugins + - Resolving contracts + √ Resolving contracts + - Running plugins + √ Running plugins + - Writing to generated.js + √ Writing to generated.js" + `) +}) + +test('generates typescript output', async () => { + const { dir, paths } = await createFixture({ + files: { + tsconfig: true, + 'wagmi.config.ts': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await generate() + + expect(console.formatted).toMatchInlineSnapshot(` + "- Validating plugins + √ Validating plugins + - Resolving contracts + √ Resolving contracts + - Running plugins + √ Running plugins + - Writing to generated.ts + √ Writing to generated.ts" + `) + await expect(typecheck(paths.tsconfig)).resolves.toMatchInlineSnapshot('""') +}) + +test('generates output with plugin', async () => { + const { dir } = await createFixture({ + files: { + tsconfig: true, + 'wagmi.config.ts': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ], + plugins: [ + { + name: 'Test', + async run({ contracts, isTypeScript, outputs }) { + return { + imports: '/* imports test */', + prepend: '/* prepend test */', + content: '/* content test */', + } + }, + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await generate() + + expect(console.formatted).toMatchInlineSnapshot(` + "- Validating plugins + √ Validating plugins + - Resolving contracts + √ Resolving contracts + - Running plugins + √ Running plugins + - Writing to generated.ts + √ Writing to generated.ts" + `) + /* eslint-disable no-irregular-whitespace */ + await expect( + readFile(resolve(dir, 'generated.ts'), 'utf8'), + ).resolves.toMatchInlineSnapshot(` + "/* imports test */ + + /* prepend test */ + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Foo + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + export const fooAbi = [] as const + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Test + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /* content test */ + " + `) + /* eslint-enable no-irregular-whitespace */ +}) + +test('behavior: invalid cli options', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect( + generate({ + // @ts-expect-error possible to pass untyped options through from cli + config: 1, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid option + - Expected string, received number at \`config\`] + `) +}) + +test('behavior: config not found', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: Config not found]', + ) +}) + +test('behavior: config not found for path', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + try { + await generate({ config: 'wagmi.config.js' }) + } catch (error) { + expect( + (error as Error).message.replace(dir, 'path/to/project'), + ).toMatchInlineSnapshot('"Config not found at wagmi.config.js"') + } +}) + +test('behavior: config out not unique', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default [ + { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ] + }, + { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ], + }, + ] + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: out "generated.ts" must be unique.]`, + ) +}) + +test('behavior: config contract names not unique', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + { + abi: [], + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Contract name "Foo" must be unique.]`, + ) +}) + +test('behavior: displays message if no contracts found', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': "export default { out: 'generated.ts' }", + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await generate() + + expect(console.formatted).toMatchInlineSnapshot( + ` + "- Validating plugins + √ Validating plugins + - Resolving contracts + × Resolving contracts + No contracts found." + `, + ) +}) + +test('behavior: throws when abi is invalid', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [{ + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ type: 'address' }], + }], + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid ABI for contract "Foo" + - Invalid input at \`[0]\`] + `) +}) + +test('behavior: throws when address is invalid', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + address: '0xfoo', + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid address for contract "Foo" + - Invalid address] + `) +}) + +test('behavior: throws when multichain address is invalid', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + address: { + 1: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 5: '0xfoo', + }, + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(generate()).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Invalid address for contract "Foo" + - Invalid address at \`5\`] + `) +}) + +test('behavior: displays message if using --watch flag without watchers configured', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.js': dedent` + export default { + out: 'generated.ts', + contracts: [ + { + abi: [], + name: 'Foo', + }, + ], + } + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await generate({ watch: true }) + + expect(console.formatted).toMatchInlineSnapshot(` + "- Validating plugins + √ Validating plugins + - Resolving contracts + √ Resolving contracts + - Running plugins + √ Running plugins + - Writing to generated.ts + √ Writing to generated.ts + Used --watch flag, but no plugins are watching." + `) +}) + +test.todo('behavior: save config file logs change') +test.todo('behavior: updates on add file') +test.todo('behavior: updates on change file') +test.todo('behavior: updates on unlink file') +test.todo('behavior: runs watch command') +test.todo('behavior: shuts down watch on SIGINT/SIGTERM') diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts new file mode 100644 index 0000000000..909c0c34fa --- /dev/null +++ b/packages/cli/src/commands/generate.ts @@ -0,0 +1,409 @@ +import { mkdir, writeFile } from 'node:fs/promises' +import { Abi as AbiSchema } from 'abitype/zod' +import { camelCase } from 'change-case' +import type { ChokidarOptions, FSWatcher } from 'chokidar' +import { watch } from 'chokidar' +import { default as dedent } from 'dedent' +import { basename, dirname, resolve } from 'pathe' +import pc from 'picocolors' +import { type Abi, type Address, getAddress } from 'viem' +import { z } from 'zod' + +import type { Contract, ContractConfig, Plugin, Watch } from '../config.js' +import { fromZodError } from '../errors.js' +import * as logger from '../logger.js' +import { findConfig } from '../utils/findConfig.js' +import { format } from '../utils/format.js' +import { getAddressDocString } from '../utils/getAddressDocString.js' +import { getIsUsingTypeScript } from '../utils/getIsUsingTypeScript.js' +import { resolveConfig } from '../utils/resolveConfig.js' + +const Generate = z.object({ + /** Path to config file */ + config: z.string().optional(), + /** Directory to search for config file */ + root: z.string().optional(), + /** Watch for file system changes to config and plugins */ + watch: z.boolean().optional(), +}) +export type Generate = z.infer + +export async function generate(options: Generate = {}) { + // Validate command line options + try { + await Generate.parseAsync(options) + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { prefix: 'Invalid option' }) + throw error + } + + // Get cli config file + const configPath = await findConfig(options) + if (!configPath) { + if (options.config) + throw new Error(`Config not found at ${pc.gray(options.config)}`) + throw new Error('Config not found') + } + + const resolvedConfigs = await resolveConfig({ configPath }) + const isTypeScript = await getIsUsingTypeScript() + + type Watcher = FSWatcher & { config?: Watch } + const watchers: Watcher[] = [] + const watchWriteDelay = 100 + const watchOptions = { + atomic: true, + // awaitWriteFinish: true, + ignoreInitial: true, + persistent: true, + } satisfies ChokidarOptions + + const outNames = new Set() + const isArrayConfig = Array.isArray(resolvedConfigs) + const configs = isArrayConfig ? resolvedConfigs : [resolvedConfigs] + for (const config of configs) { + if (isArrayConfig) + logger.log(`Using config ${pc.gray(basename(configPath))}`) + if (!config.out) throw new Error('out is required.') + if (outNames.has(config.out)) + throw new Error(`out "${config.out}" must be unique.`) + outNames.add(config.out) + + // Collect contracts and watch configs from plugins + const plugins = (config.plugins ?? []).map((x, i) => ({ + ...x, + id: `${x.name}-${i}`, + })) + const spinner = logger.spinner('Validating plugins') + spinner.start() + for (const plugin of plugins) { + await plugin.validate?.() + } + spinner.success() + + // Add plugin contracts to config contracts + const contractConfigs = config.contracts ?? [] + const watchConfigs: Watch[] = [] + spinner.start('Resolving contracts') + for (const plugin of plugins) { + if (plugin.watch) watchConfigs.push(plugin.watch) + if (plugin.contracts) { + const contracts = await plugin.contracts() + contractConfigs.push(...contracts) + } + } + + // Get contracts from config + const contractNames = new Set() + const contractMap = new Map() + for (const contractConfig of contractConfigs) { + if (contractNames.has(contractConfig.name)) + throw new Error( + `Contract name "${contractConfig.name}" must be unique.`, + ) + const contract = await getContract({ ...contractConfig, isTypeScript }) + contractMap.set(contract.name, contract) + + contractNames.add(contractConfig.name) + } + + // Sort contracts by name Ascending (low to high) as the key is `String` + const sortedAscContractMap = new Map([...contractMap].sort((a, b) => a[0].localeCompare(b[0]))) + const contracts = [...sortedAscContractMap.values()] + if (!contracts.length && !options.watch) { + spinner.error() + logger.warn('No contracts found.') + return + } + spinner.success() + + // Run plugins + const imports = [] + const prepend = [] + const content = [] + type Output = { + plugin: Pick + } & Awaited>> + const outputs: Output[] = [] + spinner.start('Running plugins') + for (const plugin of plugins) { + if (!plugin.run) continue + const result = await plugin.run({ + contracts, + isTypeScript, + outputs, + }) + outputs.push({ + plugin: { name: plugin.name }, + ...result, + }) + if (!result.imports && !result.prepend && !result.content) continue + content.push(getBannerContent({ name: plugin.name }), result.content) + result.imports && imports.push(result.imports) + result.prepend && prepend.push(result.prepend) + } + spinner.success() + + // Write output to file + spinner.start(`Writing to ${pc.gray(config.out)}`) + await writeContracts({ + content, + contracts, + imports, + prepend, + filename: config.out, + }) + spinner.success() + + if (options.watch) { + if (!watchConfigs.length) { + logger.log(pc.gray('Used --watch flag, but no plugins are watching.')) + continue + } + logger.log() + logger.log('Setting up watch process') + + // Watch for changes + let timeout: NodeJS.Timeout | null + for (const watchConfig of watchConfigs) { + const paths = + typeof watchConfig.paths === 'function' + ? await watchConfig.paths() + : watchConfig.paths + const watcher = watch(paths, watchOptions) + // Watch for changes to files, new files, and deleted files + watcher.on('all', async (event, path) => { + if (event !== 'change' && event !== 'add' && event !== 'unlink') + return + + let needsWrite = false + if (event === 'change' || event === 'add') { + const eventFn = + event === 'change' ? watchConfig.onChange : watchConfig.onAdd + const config = await eventFn?.(path) + if (!config) return + const contract = await getContract({ ...config, isTypeScript }) + contractMap.set(contract.name, contract) + needsWrite = true + } else if (event === 'unlink') { + const name = await watchConfig.onRemove?.(path) + if (!name) return + contractMap.delete(name) + needsWrite = true + } + + // Debounce writes + if (needsWrite) { + if (timeout) clearTimeout(timeout) + timeout = setTimeout(async () => { + timeout = null + // Sort contracts by name Ascending (low to high) as the key is `String` + const sortedAscContractMap = new Map([...contractMap].sort()) + const contracts = [...sortedAscContractMap.values()] + const imports = [] + const prepend = [] + const content = [] + const outputs: Output[] = [] + for (const plugin of plugins) { + if (!plugin.run) continue + const result = await plugin.run({ + contracts, + isTypeScript, + outputs, + }) + outputs.push({ + plugin: { name: plugin.name }, + ...result, + }) + if (!result.imports && !result.prepend && !result.content) + continue + content.push( + getBannerContent({ name: plugin.name }), + result.content, + ) + result.imports && imports.push(result.imports) + result.prepend && prepend.push(result.prepend) + } + + const spinner = logger.spinner( + `Writing to ${pc.gray(config.out)}`, + ) + spinner.start() + await writeContracts({ + content, + contracts, + imports, + prepend, + filename: config.out, + }) + spinner.success() + }, watchWriteDelay) + needsWrite = false + } + }) + + // Run parallel command on ready + if (watchConfig.command) + watcher.on('ready', async () => { + await watchConfig.command?.() + }) + ;(watcher as Watcher).config = watchConfig + watchers.push(watcher) + } + } + } + + if (!watchers.length) return + + // Watch `@wagmi/cli` config file for changes + const watcher = watch(configPath).on('change', async (path) => { + logger.log( + `> Found a change to config ${pc.gray( + basename(path), + )}. Restart process for changes to take effect.`, + ) + }) + watchers.push(watcher) + + // Display message and close watchers on exit + process.once('SIGINT', shutdown) + process.once('SIGTERM', shutdown) + async function shutdown() { + logger.log() + logger.log('Shutting down watch process') + const promises = [] + for (const watcher of watchers) { + if (watcher.config?.onClose) promises.push(watcher.config?.onClose?.()) + promises.push(watcher.close()) + } + await Promise.allSettled(promises) + process.exit(0) + } +} + +async function getContract({ + abi, + address, + name, + isTypeScript, +}: ContractConfig & { isTypeScript: boolean }): Promise { + const constAssertion = isTypeScript ? ' as const' : '' + const abiName = `${camelCase(name)}Abi` + try { + abi = (await AbiSchema.parseAsync(abi)) as Abi + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { + prefix: `Invalid ABI for contract "${name}"`, + }) + throw error + } + const docString = + typeof address === 'object' + ? dedent`\n + /** + ${getAddressDocString({ address })} + */ + ` + : '' + let content = dedent` + ${getBannerContent({ name })} + + ${docString} + export const ${abiName} = ${JSON.stringify(abi)}${constAssertion} + ` + + let meta: Contract['meta'] = { abiName } + if (address) { + let resolvedAddress: Address | Record + try { + const Address = z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, { message: 'Invalid address' }) + .transform((val) => getAddress(val)) as z.ZodType
+ const MultiChainAddress = z.record(z.string(), Address) + const AddressSchema = z.union([Address, MultiChainAddress]) + resolvedAddress = await AddressSchema.parseAsync(address) + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { + prefix: `Invalid address for contract "${name}"`, + }) + throw error + } + + const addressName = `${camelCase(name)}Address` + const configName = `${camelCase(name)}Config` + meta = { + ...meta, + addressName, + configName, + } + + const addressContent = + typeof resolvedAddress === 'string' + ? JSON.stringify(resolvedAddress) + : // Remove quotes from chain id key + JSON.stringify(resolvedAddress, null, 2).replace(/"(\d*)":/gm, '$1:') + content = dedent` + ${content} + + ${docString} + export const ${addressName} = ${addressContent}${constAssertion} + + ${docString} + export const ${configName} = { address: ${addressName}, abi: ${abiName} }${constAssertion} + ` + } + + return { abi, address, content, meta, name } +} + +async function writeContracts({ + content, + contracts, + imports, + prepend, + filename, +}: { + content: string[] + contracts: Contract[] + imports: string[] + prepend: string[] + filename: string +}) { + // Assemble code + let code = dedent` + ${imports.join('\n\n') ?? ''} + + ${prepend.join('\n\n') ?? ''} + ` + for (const contract of contracts) { + code = dedent` + ${code} + + ${contract.content} + ` + } + code = dedent` + ${code} + + ${content.join('\n\n') ?? ''} + ` + + // Format and write output + const cwd = process.cwd() + const outPath = resolve(cwd, filename) + await mkdir(dirname(outPath), { recursive: true }) + const formatted = await format(code) + await writeFile(outPath, formatted) +} + +function getBannerContent({ name }: { name: string }) { + return dedent` + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ${name} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ` +} diff --git a/packages/cli/src/commands/init.test.ts b/packages/cli/src/commands/init.test.ts new file mode 100644 index 0000000000..ed4a1d1644 --- /dev/null +++ b/packages/cli/src/commands/init.test.ts @@ -0,0 +1,189 @@ +import { existsSync } from 'node:fs' +import { mkdir, readFile } from 'node:fs/promises' +import { resolve } from 'pathe' +import { afterEach, beforeEach, expect, test, vi } from 'vitest' + +import { createFixture, watchConsole } from '../../test/utils.js' +import { defaultConfig } from '../config.js' +import { init } from './init.js' + +let console: ReturnType +beforeEach(() => { + console = watchConsole() +}) + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('creates config file', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + const configFile = await init() + + expect(existsSync(configFile)).toBeTruthy() + expect(await readFile(configFile, 'utf-8')).toMatchInlineSnapshot(` + "// @ts-check + + /** @type {import('@wagmi/cli').Config} */ + export default { + out: 'src/generated.js', + contracts: [], + plugins: [], + } + " + `) + expect( + console.formatted.replaceAll(dir, 'path/to/project'), + ).toMatchInlineSnapshot(` + "- Creating config + √ Creating config + Config created at wagmi.config.js" + `) +}) + +test('parameters: config', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + const configFile = await init({ + config: 'foo.config.ts', + }) + + expect(existsSync(configFile)).toBeTruthy() + expect(await readFile(configFile, 'utf-8')).toMatchInlineSnapshot(` + "// @ts-check + + /** @type {import('@wagmi/cli').Config} */ + export default { + out: 'src/generated.js', + contracts: [], + plugins: [], + } + " + `) + expect( + console.formatted.replaceAll(dir, 'path/to/project'), + ).toMatchInlineSnapshot(` + "- Creating config + √ Creating config + Config created at foo.config.ts" + `) +}) + +test('parameters: content', async () => { + const { dir } = await createFixture({ + files: { + 'tsconfig.json': '{}', + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + const configFile = await init({ + content: { + ...defaultConfig, + out: 'foo/bar/baz.ts', + }, + }) + + expect(existsSync(configFile)).toBeTruthy() + expect(await readFile(configFile, 'utf-8')).toMatchInlineSnapshot(` + "import { defineConfig } from '@wagmi/cli' + + export default defineConfig({ + out: 'foo/bar/baz.ts', + contracts: [], + plugins: [], + }) + " + `) + expect( + console.formatted.replaceAll(dir, 'path/to/project'), + ).toMatchInlineSnapshot(` + "- Creating config + √ Creating config + Config created at wagmi.config.ts" + `) +}) + +test('parameters: root', async () => { + const { dir } = await createFixture() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + mkdir(resolve(dir, 'foo')) + + const configFile = await init({ + root: 'foo/', + }) + + expect(existsSync(configFile)).toBeTruthy() + expect(await readFile(configFile, 'utf-8')).toMatchInlineSnapshot(` + "// @ts-check + + /** @type {import('@wagmi/cli').Config} */ + export default { + out: 'src/generated.js', + contracts: [], + plugins: [], + } + " + `) + expect( + console.formatted.replaceAll(dir, 'path/to/project'), + ).toMatchInlineSnapshot(` + "- Creating config + √ Creating config + Config created at foo/wagmi.config.js" + `) +}) + +test('behavior: creates config file in TypeScript format', async () => { + const { dir } = await createFixture({ + files: { + 'tsconfig.json': '{}', + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + const configFile = await init() + + expect(existsSync(configFile)).toBeTruthy() + expect(await readFile(configFile, 'utf-8')).toMatchInlineSnapshot(` + "import { defineConfig } from '@wagmi/cli' + + export default defineConfig({ + out: 'src/generated.ts', + contracts: [], + plugins: [], + }) + " + `) + expect( + console.formatted.replaceAll(dir, 'path/to/project'), + ).toMatchInlineSnapshot(` + "- Creating config + √ Creating config + Config created at wagmi.config.ts" + `) +}) + +test('behavior: displays config file location when config exists', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.ts': '', + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + const configFile = await init() + + expect( + console.formatted.replaceAll(configFile, 'path/to/project/wagmi.config.ts'), + ).toMatchInlineSnapshot('"Config already exists at wagmi.config.ts"') +}) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts new file mode 100644 index 0000000000..ce4e5b32d9 --- /dev/null +++ b/packages/cli/src/commands/init.ts @@ -0,0 +1,95 @@ +import { writeFile } from 'node:fs/promises' +import dedent from 'dedent' +import { relative, resolve } from 'pathe' +import pc from 'picocolors' +import { z } from 'zod' + +import { type Config, defaultConfig } from '../config.js' +import { fromZodError } from '../errors.js' +import * as logger from '../logger.js' +import { findConfig } from '../utils/findConfig.js' +import { format } from '../utils/format.js' +import { getIsUsingTypeScript } from '../utils/getIsUsingTypeScript.js' + +export type Init = { + /** Path to config file */ + config?: string + /** Watch for file system changes to config and plugins */ + content?: Config + /** Directory to init config file */ + root?: string +} + +const Init = z.object({ + config: z.string().optional(), + content: z.object({}).optional(), + root: z.string().optional(), +}) + +export async function init(options: Init = {}) { + // Validate command line options + try { + await Init.parseAsync(options) + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { prefix: 'Invalid option' }) + throw error + } + + // Check for existing config file + const configPath = await findConfig(options) + if (configPath) { + logger.info( + `Config already exists at ${pc.gray( + relative(process.cwd(), configPath), + )}`, + ) + return configPath + } + + const spinner = logger.spinner('Creating config') + spinner.start() + // Check if project is using TypeScript + const isUsingTypeScript = await getIsUsingTypeScript() + const rootDir = resolve(options.root || process.cwd()) + let outPath: string + if (options.config) { + outPath = resolve(rootDir, options.config) + } else { + const extension = isUsingTypeScript ? 'ts' : 'js' + outPath = resolve(rootDir, `wagmi.config.${extension}`) + } + + let content: string + if (isUsingTypeScript) { + const config = options.content ?? defaultConfig + content = dedent(` + import { defineConfig } from '@wagmi/cli' + + export default defineConfig(${JSON.stringify(config)}) + `) + } else { + const config = options.content ?? { + ...defaultConfig, + out: defaultConfig.out.replace('.ts', '.js'), + } + content = dedent(` + // @ts-check + + /** @type {import('@wagmi/cli').Config} */ + export default ${JSON.stringify(config, null, 2).replace( + /"(\d*)":/gm, + '$1:', + )} + `) + } + + const formatted = await format(content) + await writeFile(outPath, formatted) + spinner.success() + logger.success( + `Config created at ${pc.gray(relative(process.cwd(), outPath))}`, + ) + + return outPath +} diff --git a/packages/cli/src/config.test.ts b/packages/cli/src/config.test.ts new file mode 100644 index 0000000000..f95d7cb6d3 --- /dev/null +++ b/packages/cli/src/config.test.ts @@ -0,0 +1,39 @@ +import { expect, test, vi } from 'vitest' + +import { type Config, defineConfig } from './config.js' + +test('object', () => { + const config: Config = { + contracts: [], + out: 'wagmi.ts', + plugins: [], + } + expect(defineConfig(config)).toEqual(config) +}) + +test('array', () => { + const config: Config = { + contracts: [], + out: 'wagmi.ts', + plugins: [], + } + expect(defineConfig([config, config])).toEqual([config, config]) +}) + +test('function', () => { + const config = vi.fn().mockImplementation(() => ({ + contracts: [], + out: 'wagmi.ts', + plugins: [], + })) + expect(defineConfig(config)).toEqual(config) +}) + +test('async function', () => { + const config = vi.fn().mockImplementation(async () => ({ + contracts: [], + out: 'wagmi.ts', + plugins: [], + })) + expect(defineConfig(config)).toEqual(config) +}) diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts new file mode 100644 index 0000000000..146a1e3424 --- /dev/null +++ b/packages/cli/src/config.ts @@ -0,0 +1,121 @@ +import type { Abi, Address } from 'viem' + +import type { Compute, MaybeArray, MaybePromise } from './types.js' + +export type ContractConfig< + chainId extends number = number, + requiredChainId extends number | undefined = undefined, +> = { + /** + * Contract ABI + */ + abi: Abi + /** + * Contract address or addresses. + * + * Accepts an object `{ [chainId]: address }` to support multiple chains. + * + * @example + * '0x314159265dd8dbb310642f98f50c066173c1259b' + * + * @example + * { + * 1: '0x314159265dd8dbb310642f98f50c066173c1259b', + * 5: '0x112234455c3a32fd11230c42e7bccd4a84e02010', + * } + */ + address?: + | Address + | (requiredChainId extends number + ? Record & Partial> + : Record) + | undefined + /** + * Name of contract. + */ + name: string +} + +export type Contract = Compute< + ContractConfig & { + /** Generated string content */ + content: string + /** Meta info about contract */ + meta: { + abiName: string + addressName?: string | undefined + configName?: string | undefined + } + } +> + +export type Watch = { + /** Command to run along with watch process */ + command?: (() => MaybePromise) | undefined + /** Paths to watch for changes. */ + paths: string[] | (() => MaybePromise) + /** Callback that fires when file is added */ + onAdd?: + | ((path: string) => MaybePromise) + | undefined + /** Callback that fires when file changes */ + onChange: (path: string) => MaybePromise + /** Callback that fires when watcher is shutdown */ + onClose?: (() => MaybePromise) | undefined + /** Callback that fires when file is removed */ + onRemove?: ((path: string) => MaybePromise) | undefined +} + +export type Plugin = { + /** Contracts provided by plugin */ + contracts?: (() => MaybePromise) | undefined + /** Plugin name */ + name: string + /** Run plugin logic */ + run?: + | ((config: { + /** All resolved contracts from config and plugins */ + contracts: Contract[] + /** Whether TypeScript is detected in project */ + isTypeScript: boolean + /** Previous plugin outputs */ + outputs: readonly { + plugin: Pick + imports?: string + prepend?: string + content: string + }[] + }) => MaybePromise<{ + imports?: string + prepend?: string + content: string + }>) + | undefined + /** + * Validate plugin configuration or other @wagmi/cli settings require for plugin. + */ + validate?: (() => MaybePromise) | undefined + /** File system watch config */ + watch?: Watch | undefined +} + +export type Config = { + /** Contracts to use in commands */ + contracts?: ContractConfig[] | undefined + /** Output file path */ + out: string + /** Plugins to run */ + plugins?: Plugin[] | undefined +} + +export function defineConfig( + config: MaybeArray | (() => MaybePromise>), +) { + return config +} + +export const defaultConfig = { + out: 'src/generated.ts', + contracts: [], + plugins: [], +} satisfies Config diff --git a/packages/cli/src/errors.ts b/packages/cli/src/errors.ts new file mode 100644 index 0000000000..6ef37093fc --- /dev/null +++ b/packages/cli/src/errors.ts @@ -0,0 +1,57 @@ +import type { z } from 'zod' + +class ValidationError extends Error { + details: Zod.ZodIssue[] + + constructor( + message: string, + options: { + details: Zod.ZodIssue[] + }, + ) { + super(message) + this.details = options.details + } +} + +// From https://github.com/causaly/zod-validation-error +export function fromZodError( + zError: z.ZodError, + { + maxIssuesInMessage = 99, + issueSeparator = '\n- ', + prefixSeparator = '\n- ', + prefix = 'Validation Error', + }: { + maxIssuesInMessage?: number + issueSeparator?: string + prefixSeparator?: string + prefix?: string + } = {}, +): ValidationError { + function joinPath(arr: Array): string { + return arr.reduce((acc, value) => { + if (typeof value === 'number') return `${acc}[${value}]` + const separator = acc === '' ? '' : '.' + return acc + separator + value + }, '') + } + + const reason = zError.errors + // limit max number of issues printed in the reason section + .slice(0, maxIssuesInMessage) + // format error message + .map((issue) => { + const { message, path } = issue + if (path.length > 0) return `${message} at \`${joinPath(path)}\`` + return message + }) + // concat as string + .join(issueSeparator) + + const message = reason ? [prefix, reason].join(prefixSeparator) : prefix + + return new ValidationError(message, { + details: zError.errors, + }) +} diff --git a/packages/cli/src/exports/config.test.ts b/packages/cli/src/exports/config.test.ts new file mode 100644 index 0000000000..c833780ffc --- /dev/null +++ b/packages/cli/src/exports/config.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from 'vitest' + +import * as Exports from './config.js' + +test('exports', () => { + expect(Object.keys(Exports)).toMatchInlineSnapshot(` + [ + "defineConfig", + "defaultConfig", + ] + `) +}) diff --git a/packages/cli/src/exports/config.ts b/packages/cli/src/exports/config.ts new file mode 100644 index 0000000000..b3c4a83ba4 --- /dev/null +++ b/packages/cli/src/exports/config.ts @@ -0,0 +1,10 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type ContractConfig, + type Contract, + type Watch, + type Plugin, + type Config, + defineConfig, + defaultConfig, +} from '../config.js' diff --git a/packages/cli/src/exports/index.test-d.ts b/packages/cli/src/exports/index.test-d.ts new file mode 100644 index 0000000000..b056d56358 --- /dev/null +++ b/packages/cli/src/exports/index.test-d.ts @@ -0,0 +1,4 @@ +import { expectTypeOf } from 'vitest' + +// noop test because vitest typecheck fails unless each workspace project has type test +expectTypeOf(1).toEqualTypeOf() diff --git a/packages/cli/src/exports/index.test.ts b/packages/cli/src/exports/index.test.ts new file mode 100644 index 0000000000..2da78e8da1 --- /dev/null +++ b/packages/cli/src/exports/index.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from 'vitest' + +import * as Exports from './index.js' + +test('exports', () => { + expect(Object.keys(Exports)).toMatchInlineSnapshot(` + [ + "defineConfig", + "logger", + "loadEnv", + "version", + ] + `) +}) diff --git a/packages/cli/src/exports/index.ts b/packages/cli/src/exports/index.ts new file mode 100644 index 0000000000..1c5e624df6 --- /dev/null +++ b/packages/cli/src/exports/index.ts @@ -0,0 +1,14 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + defineConfig, + type Config, + type ContractConfig, + type Plugin, +} from '../config.js' + +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * as logger from '../logger.js' + +export { loadEnv } from '../utils/loadEnv.js' + +export { version } from '../version.js' diff --git a/packages/cli/src/exports/plugins.test.ts b/packages/cli/src/exports/plugins.test.ts new file mode 100644 index 0000000000..4d7b5a97cd --- /dev/null +++ b/packages/cli/src/exports/plugins.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from 'vitest' + +import * as Exports from './plugins.js' + +test('exports', () => { + expect(Object.keys(Exports)).toMatchInlineSnapshot(` + [ + "actions", + "blockExplorer", + "etherscan", + "fetch", + "foundry", + "foundryDefaultExcludes", + "hardhat", + "hardhatDefaultExcludes", + "react", + "sourcify", + ] + `) +}) diff --git a/packages/cli/src/exports/plugins.ts b/packages/cli/src/exports/plugins.ts new file mode 100644 index 0000000000..a289b5c576 --- /dev/null +++ b/packages/cli/src/exports/plugins.ts @@ -0,0 +1,27 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { actions, type ActionsConfig } from '../plugins/actions.js' + +export { + blockExplorer, + type BlockExplorerConfig, +} from '../plugins/blockExplorer.js' + +export { etherscan, type EtherscanConfig } from '../plugins/etherscan.js' + +export { fetch, type FetchConfig } from '../plugins/fetch.js' + +export { + foundry, + foundryDefaultExcludes, + type FoundryConfig, +} from '../plugins/foundry.js' + +export { + hardhat, + hardhatDefaultExcludes, + type HardhatConfig, +} from '../plugins/hardhat.js' + +export { react, type ReactConfig } from '../plugins/react.js' + +export { sourcify, type SourcifyConfig } from '../plugins/sourcify.js' diff --git a/packages/cli/src/logger.test.ts b/packages/cli/src/logger.test.ts new file mode 100644 index 0000000000..7338c3bb14 --- /dev/null +++ b/packages/cli/src/logger.test.ts @@ -0,0 +1,32 @@ +import { afterEach, expect, test, vi } from 'vitest' + +import { watchConsole } from '../test/utils.js' + +import * as logger from './logger.js' + +const mockLog = vi.fn() + +afterEach(() => { + vi.restoreAllMocks() +}) + +test.each(['success', 'info', 'log', 'warn', 'error'])('%s()', (level) => { + const spy = vi.spyOn(logger, level as any) + spy.mockImplementation(mockLog) + const loggerFn = (logger as any)[level] + loggerFn(level) + expect(spy).toHaveBeenCalledWith(level) +}) + +test('spinner', () => { + const console = watchConsole() + const spinner = logger.spinner('start') + spinner.start() + spinner.success('success') + spinner.error('error') + expect(console.formatted).toMatchInlineSnapshot(` + "- start + √ success + × error" + `) +}) diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts new file mode 100644 index 0000000000..b56fb9728b --- /dev/null +++ b/packages/cli/src/logger.ts @@ -0,0 +1,37 @@ +import { format as utilFormat } from 'node:util' +import { createSpinner } from 'nanospinner' +import pc from 'picocolors' + +function format(args: any[]) { + return utilFormat(...args) + .split('\n') + .join('\n') +} + +export function success(...args: any[]) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(pc.green(format(args))) +} + +export function info(...args: any[]) { + console.info(pc.blue(format(args))) +} + +export function log(...args: any[]) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(pc.white(format(args))) +} + +export function warn(...args: any[]) { + console.warn(pc.yellow(format(args))) +} + +export function error(...args: any[]) { + console.error(pc.red(format(args))) +} + +export function spinner(text: string) { + return createSpinner(text, { + color: 'yellow', + }) +} diff --git a/packages/cli/src/plugins/__fixtures__/foundry/.gitignore b/packages/cli/src/plugins/__fixtures__/foundry/.gitignore new file mode 100644 index 0000000000..3269660cc7 --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/foundry/.gitignore @@ -0,0 +1,11 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Dotenv file +.env diff --git a/packages/cli/src/plugins/__fixtures__/foundry/foundry.toml b/packages/cli/src/plugins/__fixtures__/foundry/foundry.toml new file mode 100644 index 0000000000..59374b16cf --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/foundry/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +libs = ['lib'] +out = 'out' +solc = '0.8.13' +src = 'src' + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/packages/cli/src/plugins/__fixtures__/foundry/src/Counter.sol b/packages/cli/src/plugins/__fixtures__/foundry/src/Counter.sol new file mode 100644 index 0000000000..5242caa433 --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/foundry/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/packages/cli/src/plugins/__fixtures__/foundry/src/Foo.sol b/packages/cli/src/plugins/__fixtures__/foundry/src/Foo.sol new file mode 100644 index 0000000000..f478736520 --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/foundry/src/Foo.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Foo { + string public bar; + + function setFoo(string memory baz) public { + bar = baz; + } +} + diff --git a/packages/cli/src/plugins/__fixtures__/hardhat/.gitignore b/packages/cli/src/plugins/__fixtures__/hardhat/.gitignore new file mode 100644 index 0000000000..85d361b914 --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/hardhat/.gitignore @@ -0,0 +1,10 @@ +node_modules +.env +coverage +coverage.json +typechain +typechain-types + +# Hardhat files +cache +artifacts \ No newline at end of file diff --git a/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Counter.sol b/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Counter.sol new file mode 100644 index 0000000000..5242caa433 --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Foo.sol b/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Foo.sol new file mode 100644 index 0000000000..699a63ce0f --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/hardhat/contracts/Foo.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Foo { + string public bar; + + function setFoo(string memory baz) public { + bar = baz; + } +} diff --git a/packages/cli/src/plugins/__fixtures__/hardhat/hardhat.config.js b/packages/cli/src/plugins/__fixtures__/hardhat/hardhat.config.js new file mode 100644 index 0000000000..c8126eedfa --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/hardhat/hardhat.config.js @@ -0,0 +1,3 @@ +module.exports = { + solidity: '0.8.17', +} diff --git a/packages/cli/src/plugins/__fixtures__/hardhat/package.json b/packages/cli/src/plugins/__fixtures__/hardhat/package.json new file mode 100644 index 0000000000..85c9ffb7bd --- /dev/null +++ b/packages/cli/src/plugins/__fixtures__/hardhat/package.json @@ -0,0 +1,7 @@ +{ + "name": "hardhat-fixture", + "private": true, + "devDependencies": { + "hardhat": "^2.22.3" + } +} diff --git a/packages/cli/src/plugins/__snapshots__/blockExplorer.test.ts.snap b/packages/cli/src/plugins/__snapshots__/blockExplorer.test.ts.snap new file mode 100644 index 0000000000..2abd351741 --- /dev/null +++ b/packages/cli/src/plugins/__snapshots__/blockExplorer.test.ts.snap @@ -0,0 +1,736 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fetches ABI 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": "0xaf0326d92b97df1221759476b072abfd8084f9be", + "name": "WagmiMintExample", + }, +] +`; + +exports[`fetches ABI with multichain deployment 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": { + "1": "0xaf0326d92b97df1221759476b072abfd8084f9be", + "10": "0xaf0326d92b97df1221759476b072abfd8084f9be", + }, + "name": "WagmiMintExample", + }, +] +`; diff --git a/packages/cli/src/plugins/__snapshots__/etherscan.test.ts.snap b/packages/cli/src/plugins/__snapshots__/etherscan.test.ts.snap new file mode 100644 index 0000000000..e03ee30f81 --- /dev/null +++ b/packages/cli/src/plugins/__snapshots__/etherscan.test.ts.snap @@ -0,0 +1,1238 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fetches ABI 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": { + "1": "0xaf0326d92b97df1221759476b072abfd8084f9be", + }, + "name": "WagmiMintExample", + }, +] +`; + +exports[`fetches ABI with multichain deployment 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": { + "1": "0xaf0326d92b97df1221759476b072abfd8084f9be", + "10": "0xaf0326d92b97df1221759476b072abfd8084f9be", + }, + "name": "WagmiMintExample", + }, +] +`; + +exports[`tryFetchProxyImplementation: fetches ABI 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": { + "1": "0xaf0326d92b97df1221759476b072abfd8084f9be", + }, + "name": "WagmiMintExample", + }, +] +`; + +exports[`tryFetchProxyImplementation: fetches implementation ABI 1`] = ` +[ + { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "newImplementation", + "type": "address", + }, + ], + "name": "upgradeTo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": false, + "inputs": [ + { + "name": "newImplementation", + "type": "address", + }, + { + "name": "data", + "type": "bytes", + }, + ], + "name": "upgradeToAndCall", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function", + }, + { + "constant": true, + "inputs": [], + "name": "implementation", + "outputs": [ + { + "name": "", + "type": "address", + }, + ], + "payable": false, + "stateMutability": "view", + "type": "function", + }, + { + "constant": false, + "inputs": [ + { + "name": "newAdmin", + "type": "address", + }, + ], + "name": "changeAdmin", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + }, + { + "constant": true, + "inputs": [], + "name": "admin", + "outputs": [ + { + "name": "", + "type": "address", + }, + ], + "payable": false, + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "name": "_implementation", + "type": "address", + }, + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "previousAdmin", + "type": "address", + }, + { + "indexed": false, + "name": "newAdmin", + "type": "address", + }, + ], + "name": "AdminChanged", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "implementation", + "type": "address", + }, + ], + "name": "Upgraded", + "type": "event", + }, + ], + "address": { + "1": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + }, + "name": "FiatToken", + }, +] +`; diff --git a/packages/cli/src/plugins/__snapshots__/fetch.test.ts.snap b/packages/cli/src/plugins/__snapshots__/fetch.test.ts.snap new file mode 100644 index 0000000000..83c4e81f53 --- /dev/null +++ b/packages/cli/src/plugins/__snapshots__/fetch.test.ts.snap @@ -0,0 +1,367 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fetches ABI 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Approval", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "ApprovalForAll", + "type": "event", + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "Transfer", + "type": "event", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + }, + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes", + }, + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address", + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool", + }, + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + }, + { + "internalType": "address", + "name": "to", + "type": "address", + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256", + }, + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": "0xaf0326d92b97df1221759476b072abfd8084f9be", + "name": "WagmiMintExample", + }, +] +`; diff --git a/packages/cli/src/plugins/__snapshots__/sourcify.test.ts.snap b/packages/cli/src/plugins/__snapshots__/sourcify.test.ts.snap new file mode 100644 index 0000000000..77e82fecde --- /dev/null +++ b/packages/cli/src/plugins/__snapshots__/sourcify.test.ts.snap @@ -0,0 +1,214 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`fetches ABI 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "inputs": [ + { + "name": "pubkey", + "type": "bytes", + }, + { + "name": "withdrawal_credentials", + "type": "bytes", + }, + { + "name": "amount", + "type": "bytes", + }, + { + "name": "signature", + "type": "bytes", + }, + { + "name": "index", + "type": "bytes", + }, + ], + "name": "DepositEvent", + "type": "event", + }, + { + "inputs": [ + { + "name": "pubkey", + "type": "bytes", + }, + { + "name": "withdrawal_credentials", + "type": "bytes", + }, + { + "name": "signature", + "type": "bytes", + }, + { + "name": "deposit_data_root", + "type": "bytes32", + }, + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function", + }, + { + "inputs": [], + "name": "get_deposit_count", + "outputs": [ + { + "type": "bytes", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "get_deposit_root", + "outputs": [ + { + "type": "bytes32", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "type": "bool", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + ], + "address": { + "1": "0x00000000219ab540356cbb839cbe05303d7705fa", + }, + "name": "DepositContract", + }, +] +`; + +exports[`fetches ABI with multichain deployment 1`] = ` +[ + { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor", + }, + { + "inputs": [ + { + "name": "pubkey", + "type": "bytes", + }, + { + "name": "withdrawal_credentials", + "type": "bytes", + }, + { + "name": "amount", + "type": "bytes", + }, + { + "name": "signature", + "type": "bytes", + }, + { + "name": "index", + "type": "bytes", + }, + ], + "name": "DepositEvent", + "type": "event", + }, + { + "inputs": [ + { + "name": "pubkey", + "type": "bytes", + }, + { + "name": "withdrawal_credentials", + "type": "bytes", + }, + { + "name": "signature", + "type": "bytes", + }, + { + "name": "deposit_data_root", + "type": "bytes32", + }, + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function", + }, + { + "inputs": [], + "name": "get_deposit_count", + "outputs": [ + { + "type": "bytes", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "get_deposit_root", + "outputs": [ + { + "type": "bytes32", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + }, + ], + "name": "supportsInterface", + "outputs": [ + { + "type": "bool", + }, + ], + "stateMutability": "pure", + "type": "function", + }, + ], + "address": { + "100": "0xC4c622862a8F548997699bE24EA4bc504e5cA865", + "137": "0xC4c622862a8F548997699bE24EA4bc504e5cA865", + }, + "name": "Community", + }, +] +`; diff --git a/packages/cli/src/plugins/actions.test.ts b/packages/cli/src/plugins/actions.test.ts new file mode 100644 index 0000000000..51b445b616 --- /dev/null +++ b/packages/cli/src/plugins/actions.test.ts @@ -0,0 +1,359 @@ +import { erc20Abi } from 'viem' +import { expect, test } from 'vitest' + +import { actions } from './actions.js' + +test('default', async () => { + const result = await actions().run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.imports).toMatchInlineSnapshot(` + "import { createReadContract, createWriteContract, createSimulateContract, createWatchContractEvent } from '@wagmi/core/codegen' + " + `) + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const readErc20 = /*#__PURE__*/ createReadContract({ abi: erc20Abi }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const readErc20Allowance = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'allowance' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const readErc20BalanceOf = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'balanceOf' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const readErc20Decimals = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'decimals' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const readErc20Name = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'name' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const readErc20Symbol = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'symbol' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const readErc20TotalSupply = /*#__PURE__*/ createReadContract({ abi: erc20Abi, functionName: 'totalSupply' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const writeErc20 = /*#__PURE__*/ createWriteContract({ abi: erc20Abi }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const writeErc20Approve = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, functionName: 'approve' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const writeErc20Transfer = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, functionName: 'transfer' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const writeErc20TransferFrom = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, functionName: 'transferFrom' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const simulateErc20 = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const simulateErc20Approve = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, functionName: 'approve' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const simulateErc20Transfer = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, functionName: 'transfer' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const simulateErc20TransferFrom = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, functionName: 'transferFrom' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const watchErc20Event = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const watchErc20ApprovalEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, eventName: 'Approval' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const watchErc20TransferEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, eventName: 'Transfer' })" + `) +}) + +test('address', async () => { + const result = await actions().run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + addressName: 'erc20Address', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const readErc20 = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const readErc20Allowance = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'allowance' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const readErc20BalanceOf = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'balanceOf' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const readErc20Decimals = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'decimals' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const readErc20Name = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'name' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const readErc20Symbol = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'symbol' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const readErc20TotalSupply = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'totalSupply' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const writeErc20 = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const writeErc20Approve = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const writeErc20Transfer = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const writeErc20TransferFrom = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const simulateErc20 = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const simulateErc20Approve = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const simulateErc20Transfer = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const simulateErc20TransferFrom = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const watchErc20Event = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const watchErc20ApprovalEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Approval' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const watchErc20TransferEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Transfer' })" + `) +}) + +test('legacy hook names', async () => { + const result = await actions({ getActionName: 'legacy' }).run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + addressName: 'erc20Address', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const readErc20 = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const readErc20Allowance = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'allowance' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const readErc20BalanceOf = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'balanceOf' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const readErc20Decimals = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'decimals' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const readErc20Name = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'name' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const readErc20Symbol = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'symbol' }) + + /** + * Wraps __{@link readContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const readErc20TotalSupply = /*#__PURE__*/ createReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'totalSupply' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const writeErc20 = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const writeErc20Approve = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const writeErc20Transfer = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link writeContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const writeErc20TransferFrom = /*#__PURE__*/ createWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const prepareWriteErc20 = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const prepareWriteErc20Approve = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const prepareWriteErc20Transfer = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link simulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const prepareWriteErc20TransferFrom = /*#__PURE__*/ createSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const watchErc20Event = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const watchErc20ApprovalEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Approval' }) + + /** + * Wraps __{@link watchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const watchErc20TransferEvent = /*#__PURE__*/ createWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Transfer' })" + `) +}) + +test('override package name', async () => { + const result = await actions({ overridePackageName: 'wagmi' }).run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.imports).toMatchInlineSnapshot(` + "import { createReadContract, createWriteContract, createSimulateContract, createWatchContractEvent } from 'wagmi/codegen' + " + `) +}) diff --git a/packages/cli/src/plugins/actions.ts b/packages/cli/src/plugins/actions.ts new file mode 100644 index 0000000000..01c804fd91 --- /dev/null +++ b/packages/cli/src/plugins/actions.ts @@ -0,0 +1,321 @@ +import { pascalCase } from 'change-case' + +import type { Contract, Plugin } from '../config.js' +import type { Compute, RequiredBy } from '../types.js' +import { getAddressDocString } from '../utils/getAddressDocString.js' +import { getIsPackageInstalled } from '../utils/packages.js' + +export type ActionsConfig = { + getActionName?: + | 'legacy' // TODO: Deprecate `'legacy'` option + | ((options: { + contractName: string + itemName?: string | undefined + type: 'read' | 'simulate' | 'watch' | 'write' + }) => string) + overridePackageName?: '@wagmi/core' | 'wagmi' | undefined +} + +type ActionsResult = Compute> + +export function actions(config: ActionsConfig = {}): ActionsResult { + return { + name: 'Action', + async run({ contracts }) { + const imports = new Set([]) + const content: string[] = [] + const pure = '/*#__PURE__*/' + + const actionNames = new Set() + for (const contract of contracts) { + let hasReadFunction = false + let hasWriteFunction = false + let hasEvent = false + const readItems = [] + const writeItems = [] + const eventItems = [] + for (const item of contract.abi) { + if (item.type === 'function') + if ( + item.stateMutability === 'view' || + item.stateMutability === 'pure' + ) { + hasReadFunction = true + readItems.push(item) + } else { + hasWriteFunction = true + writeItems.push(item) + } + else if (item.type === 'event') { + hasEvent = true + eventItems.push(item) + } + } + + let innerContent: string + if (contract.meta.addressName) + innerContent = `abi: ${contract.meta.abiName}, address: ${contract.meta.addressName}` + else innerContent = `abi: ${contract.meta.abiName}` + + if (hasReadFunction) { + const actionName = getActionName( + config, + actionNames, + 'read', + contract.name, + ) + const docString = genDocString('readContract', contract) + const functionName = 'createReadContract' + imports.add(functionName) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of readItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'pure' && + item.stateMutability !== 'view' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const hookName = getActionName( + config, + actionNames, + 'read', + contract.name, + item.name, + ) + const docString = genDocString('readContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + + if (hasWriteFunction) { + { + const actionName = getActionName( + config, + actionNames, + 'write', + contract.name, + ) + const docString = genDocString('writeContract', contract) + const functionName = 'createWriteContract' + imports.add(functionName) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of writeItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'nonpayable' && + item.stateMutability !== 'payable' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const actionName = getActionName( + config, + actionNames, + 'write', + contract.name, + item.name, + ) + const docString = genDocString('writeContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + + { + const actionName = getActionName( + config, + actionNames, + 'simulate', + contract.name, + ) + const docString = genDocString('simulateContract', contract) + const functionName = 'createSimulateContract' + imports.add(functionName) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of writeItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'nonpayable' && + item.stateMutability !== 'payable' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const actionName = getActionName( + config, + actionNames, + 'simulate', + contract.name, + item.name, + ) + const docString = genDocString('simulateContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + } + + if (hasEvent) { + const actionName = getActionName( + config, + actionNames, + 'watch', + contract.name, + ) + const docString = genDocString('watchContractEvent', contract) + const functionName = 'createWatchContractEvent' + imports.add(functionName) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of eventItems) { + if (item.type !== 'event') continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const actionName = getActionName( + config, + actionNames, + 'watch', + contract.name, + item.name, + ) + const docString = genDocString('watchContractEvent', contract, { + name: 'eventName', + value: item.name, + }) + content.push( + `${docString} +export const ${actionName} = ${pure} ${functionName}({ ${innerContent}, eventName: '${item.name}' })`, + ) + } + } + } + + const importValues = [...imports.values()] + + let packageName = '@wagmi/core/codegen' + if (config.overridePackageName) { + switch (config.overridePackageName) { + case '@wagmi/core': + packageName = '@wagmi/core/codegen' + break + case 'wagmi': + packageName = 'wagmi/codegen' + break + } + } else if (await getIsPackageInstalled({ packageName: 'wagmi' })) + packageName = 'wagmi/codegen' + else if (await getIsPackageInstalled({ packageName: '@wagmi/core' })) + packageName = '@wagmi/core/codegen' + + return { + imports: importValues.length + ? `import { ${importValues.join(', ')} } from '${packageName}'\n` + : '', + content: content.join('\n\n'), + } + }, + } +} + +function genDocString( + actionName: string, + contract: Contract, + item?: { name: string; value: string }, +) { + let description = `Wraps __{@link ${actionName}}__ with \`abi\` set to __{@link ${contract.meta.abiName}}__` + if (item) description += ` and \`${item.name}\` set to \`"${item.value}"\`` + + const docString = getAddressDocString({ address: contract.address }) + if (docString) + return `/** + * ${description} + * + ${docString} + */` + + return `/** + * ${description} + */` +} + +function getActionName( + config: ActionsConfig, + actionNames: Set, + type: 'read' | 'simulate' | 'watch' | 'write', + contractName: string, + itemName?: string | undefined, +) { + const ContractName = pascalCase(contractName) + const ItemName = itemName ? pascalCase(itemName) : undefined + + let actionName: string + if (typeof config.getActionName === 'function') + actionName = config.getActionName({ + type, + contractName: ContractName, + itemName: ItemName, + }) + else if (typeof config.getActionName === 'string' && type === 'simulate') { + actionName = `prepareWrite${ContractName}${ItemName ?? ''}` + } else { + actionName = `${type}${ContractName}${ItemName ?? ''}` + if (type === 'watch') actionName = `${actionName}Event` + } + + if (actionNames.has(actionName)) + throw new Error( + `Action name "${actionName}" must be unique for contract "${contractName}". Try using \`getActionName\` to create a unique name.`, + ) + + actionNames.add(actionName) + return actionName +} diff --git a/packages/cli/src/plugins/blockExplorer.test.ts b/packages/cli/src/plugins/blockExplorer.test.ts new file mode 100644 index 0000000000..13372f53ec --- /dev/null +++ b/packages/cli/src/plugins/blockExplorer.test.ts @@ -0,0 +1,53 @@ +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest' + +import { + address, + apiKey, + baseUrl, + handlers, + unverifiedContractAddress, +} from '../../test/utils.js' +import { blockExplorer } from './blockExplorer.js' + +const server = setupServer(...handlers) + +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +test('fetches ABI', async () => { + await expect( + blockExplorer({ + apiKey, + baseUrl, + contracts: [{ name: 'WagmiMintExample', address }], + }).contracts!(), + ).resolves.toMatchSnapshot() +}) + +test('fetches ABI with multichain deployment', async () => { + await expect( + blockExplorer({ + apiKey, + baseUrl, + contracts: [ + { name: 'WagmiMintExample', address: { 1: address, 10: address } }, + ], + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fails to fetch for unverified contract', async () => { + await expect( + blockExplorer({ + apiKey, + baseUrl, + contracts: [ + { name: 'WagmiMintExample', address: unverifiedContractAddress }, + ], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: Contract source code not verified]', + ) +}) diff --git a/packages/cli/src/plugins/blockExplorer.ts b/packages/cli/src/plugins/blockExplorer.ts new file mode 100644 index 0000000000..2518b6e936 --- /dev/null +++ b/packages/cli/src/plugins/blockExplorer.ts @@ -0,0 +1,107 @@ +import { camelCase } from 'change-case' +import type { Address } from 'viem' +import { z } from 'zod' + +import type { ContractConfig } from '../config.js' +import { fromZodError } from '../errors.js' +import type { Compute } from '../types.js' +import { fetch } from './fetch.js' + +export type BlockExplorerConfig = { + /** + * API key for block explorer. Appended to the request URL as query param `&apikey=${apiKey}`. + */ + apiKey?: string | undefined + /** + * Base URL for block explorer. + */ + baseUrl: string + /** + * Duration in milliseconds to cache ABIs. + * + * @default 1_800_000 // 30m in ms + */ + cacheDuration?: number | undefined + /** + * Chain ID for block explorer. Appended to the request URL as query param `&chainId=${chainId}`. + */ + chainId?: number | undefined + /** + * Contracts to fetch ABIs for. + */ + contracts: Compute>[] + /** + * Function to get address from contract config. + */ + getAddress?: + | ((config: { + address: NonNullable + }) => Address) + | undefined + /** + * Name of source. + */ + name?: ContractConfig['name'] | undefined +} + +const BlockExplorerResponse = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('1'), + message: z.literal('OK'), + result: z + .string() + .transform((val) => JSON.parse(val) as ContractConfig['abi']), + }), + z.object({ + status: z.literal('0'), + message: z.literal('NOTOK'), + result: z.string(), + }), +]) + +/** + * Fetches contract ABIs from block explorers, supporting `?module=contract&action=getabi` requests. + */ +export function blockExplorer(config: BlockExplorerConfig) { + const { + apiKey, + baseUrl, + cacheDuration, + chainId, + contracts, + getAddress = ({ address }) => { + if (typeof address === 'string') return address + return Object.values(address)[0]! + }, + name = 'Block Explorer', + } = config + + return fetch({ + cacheDuration, + contracts, + name, + getCacheKey({ contract }) { + if (typeof contract.address === 'string') + return `${camelCase(name)}:${contract.address}` + return `${camelCase(name)}:${JSON.stringify(contract.address)}` + }, + async parse({ response }) { + const json = await response.json() + const parsed = await BlockExplorerResponse.safeParseAsync(json) + if (!parsed.success) + throw fromZodError(parsed.error, { prefix: 'Invalid response' }) + if (parsed.data.status === '0') throw new Error(parsed.data.result) + return parsed.data.result + }, + request({ address }) { + if (!address) throw new Error('address is required') + return { + url: `${baseUrl}?${chainId ? `chainId=${chainId}&` : ''}module=contract&action=getabi&address=${getAddress( + { + address, + }, + )}${apiKey ? `&apikey=${apiKey}` : ''}`, + } + }, + }) +} diff --git a/packages/cli/src/plugins/etherscan.test.ts b/packages/cli/src/plugins/etherscan.test.ts new file mode 100644 index 0000000000..dc496f4630 --- /dev/null +++ b/packages/cli/src/plugins/etherscan.test.ts @@ -0,0 +1,112 @@ +import { mkdir, rm } from 'node:fs/promises' +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest' + +import { + address, + apiKey, + handlers, + invalidApiKey, + proxyAddress, + timeoutAddress, + unverifiedContractAddress, +} from '../../test/utils.js' +import { etherscan } from './etherscan.js' +import { getCacheDir } from './fetch.js' + +const server = setupServer(...handlers) + +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +test('fetches ABI', async () => { + await expect( + etherscan({ + apiKey, + chainId: 1, + contracts: [{ name: 'WagmiMintExample', address }], + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fetches ABI with multichain deployment', async () => { + await expect( + etherscan({ + apiKey, + chainId: 1, + contracts: [ + { name: 'WagmiMintExample', address: { 1: address, 10: address } }, + ], + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fails to fetch for unverified contract', async () => { + await expect( + etherscan({ + apiKey, + chainId: 1, + contracts: [ + { name: 'WagmiMintExample', address: unverifiedContractAddress }, + ], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: Contract source code not verified]', + ) +}) + +test('missing address for chainId', async () => { + await expect( + etherscan({ + apiKey, + chainId: 1, + // @ts-expect-error `chainId` and `keyof typeof contracts[number].address` mismatch + contracts: [{ name: 'WagmiMintExample', address: { 10: address } }], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: No address found for chainId "1". Make sure chainId "1" is set as an address.]`, + ) +}) + +test('invalid api key', async () => { + await expect( + etherscan({ + apiKey: invalidApiKey, + chainId: 1, + contracts: [{ name: 'WagmiMintExample', address: timeoutAddress }], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot('[Error: Invalid API Key]') +}) + +test('tryFetchProxyImplementation: fetches ABI', async () => { + const cacheDir = getCacheDir() + await mkdir(cacheDir, { recursive: true }) + + await expect( + etherscan({ + apiKey, + chainId: 1, + contracts: [{ name: 'WagmiMintExample', address }], + tryFetchProxyImplementation: true, + }).contracts?.(), + ).resolves.toMatchSnapshot() + + await rm(cacheDir, { recursive: true }) +}) + +test('tryFetchProxyImplementation: fetches implementation ABI', async () => { + const cacheDir = getCacheDir() + await mkdir(cacheDir, { recursive: true }) + + await expect( + etherscan({ + apiKey, + chainId: 1, + contracts: [{ name: 'FiatToken', address: proxyAddress }], + tryFetchProxyImplementation: true, + }).contracts?.(), + ).resolves.toMatchSnapshot() + + await rm(cacheDir, { recursive: true }) +}) diff --git a/packages/cli/src/plugins/etherscan.ts b/packages/cli/src/plugins/etherscan.ts new file mode 100644 index 0000000000..fda375c240 --- /dev/null +++ b/packages/cli/src/plugins/etherscan.ts @@ -0,0 +1,268 @@ +import { mkdir, writeFile } from 'node:fs/promises' +import { Address as AddressSchema } from 'abitype/zod' +import { camelCase } from 'change-case' +import { join } from 'pathe' +import type { Abi, Address } from 'viem' +import { z } from 'zod' + +import type { ContractConfig } from '../config.js' +import { fromZodError } from '../errors.js' +import type { Compute } from '../types.js' +import { fetch, getCacheDir } from './fetch.js' + +export type EtherscanConfig = { + /** + * Etherscan API key. + * + * Create or manage keys at https://etherscan.io/myapikey + */ + apiKey: string + /** + * Duration in milliseconds to cache ABIs. + * + * @default 1_800_000 // 30m in ms + */ + cacheDuration?: number | undefined + /** + * Chain ID to use for fetching ABI. + * + * If `address` is an object, `chainId` is used to select the address. + * + * View supported chains on the [Etherscan docs](https://docs.etherscan.io/etherscan-v2/getting-started/supported-chains). + */ + chainId: (chainId extends ChainId ? chainId : never) | (ChainId & {}) + /** + * Contracts to fetch ABIs for. + */ + contracts: Compute, 'abi'>>[] + /** + * Whether to try fetching proxy implementation address of the contract + * + * @default false + */ + tryFetchProxyImplementation?: boolean | undefined +} + +/** + * Fetches contract ABIs from Etherscan. + */ +export function etherscan( + config: EtherscanConfig, +) { + const { + apiKey, + cacheDuration = 1_800_000, + chainId, + tryFetchProxyImplementation = false, + } = config + + const contracts = config.contracts.map((x) => ({ + ...x, + address: + typeof x.address === 'string' ? { [chainId]: x.address } : x.address, + })) as Omit[] + + const name = 'Etherscan' + + const getCacheKey: Parameters[0]['getCacheKey'] = ({ + contract, + }) => { + if (typeof contract.address === 'string') + return `${camelCase(name)}:${contract.address}` + return `${camelCase(name)}:${JSON.stringify(contract.address)}` + } + + return fetch({ + cacheDuration, + contracts, + name, + getCacheKey, + async parse({ response }) { + const json = await response.json() + const parsed = await GetAbiResponse.safeParseAsync(json) + if (!parsed.success) + throw fromZodError(parsed.error, { prefix: 'Invalid response' }) + if (parsed.data.status === '0') throw new Error(parsed.data.result) + return parsed.data.result + }, + async request(contract) { + if (!contract.address) throw new Error('address is required') + + const resolvedAddress = (() => { + if (!contract.address) throw new Error('address is required') + if (typeof contract.address === 'string') return contract.address + const contractAddress = contract.address[chainId] + if (!contractAddress) + throw new Error( + `No address found for chainId "${chainId}". Make sure chainId "${chainId}" is set as an address.`, + ) + return contractAddress + })() + + const options = { + address: resolvedAddress, + apiKey, + chainId, + } + + let abi: Abi | undefined + const implementationAddress = await (async () => { + if (!tryFetchProxyImplementation) return + const json = await globalThis + .fetch(buildUrl({ ...options, action: 'getsourcecode' })) + .then((res) => res.json()) + const parsed = await GetSourceCodeResponse.safeParseAsync(json) + if (!parsed.success) + throw fromZodError(parsed.error, { prefix: 'Invalid response' }) + if (parsed.data.status === '0') throw new Error(parsed.data.result) + if (!parsed.data.result[0]) return + abi = parsed.data.result[0].ABI + return parsed.data.result[0].Implementation as Address + })() + + if (abi) { + const cacheDir = getCacheDir() + await mkdir(cacheDir, { recursive: true }) + const cacheKey = getCacheKey({ contract }) + const cacheFilePath = join(cacheDir, `${cacheKey}.json`) + await writeFile( + cacheFilePath, + `${JSON.stringify({ abi, timestamp: Date.now() + cacheDuration }, undefined, 2)}\n`, + ) + } + + return { + url: buildUrl({ + ...options, + action: 'getabi', + address: implementationAddress || resolvedAddress, + }), + } + }, + }) +} + +function buildUrl(options: { + action: 'getabi' | 'getsourcecode' + address: Address + apiKey: string + chainId: ChainId | undefined +}) { + const baseUrl = 'https://api.etherscan.io/v2/api' + const { action, address, apiKey, chainId } = options + return `${baseUrl}?${chainId ? `chainId=${chainId}&` : ''}module=contract&action=${action}&address=${address}${apiKey ? `&apikey=${apiKey}` : ''}` +} + +const GetAbiResponse = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('1'), + message: z.literal('OK'), + result: z.string().transform((val) => JSON.parse(val) as Abi), + }), + z.object({ + status: z.literal('0'), + message: z.literal('NOTOK'), + result: z.string(), + }), +]) + +const GetSourceCodeResponse = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('1'), + message: z.literal('OK'), + result: z.array( + z.discriminatedUnion('Proxy', [ + z.object({ + ABI: z.string().transform((val) => JSON.parse(val) as Abi), + Implementation: AddressSchema, + Proxy: z.literal('1'), + }), + z.object({ + ABI: z.string().transform((val) => JSON.parse(val) as Abi), + Implementation: z.string(), + Proxy: z.literal('0'), + }), + ]), + ), + }), + z.object({ + status: z.literal('0'), + message: z.literal('NOTOK'), + result: z.string(), + }), +]) + +// Supported chains +// https://docs.etherscan.io/etherscan-v2/getting-started/supported-chains +type ChainId = + | 1 // Ethereum Mainnet + | 11155111 // Sepolia Testnet + | 17000 // Holesky Testnet + | 560048 // Hoodi Testnet + | 56 // BNB Smart Chain Mainnet + | 97 // BNB Smart Chain Testnet + | 137 // Polygon Mainnet + | 80002 // Polygon Amoy Testnet + | 1101 // Polygon zkEVM Mainnet + | 2442 // Polygon zkEVM Cardona Testnet + | 8453 // Base Mainnet + | 84532 // Base Sepolia Testnet + | 42161 // Arbitrum One Mainnet + | 42170 // Arbitrum Nova Mainnet + | 421614 // Arbitrum Sepolia Testnet + | 59144 // Linea Mainnet + | 59141 // Linea Sepolia Testnet + | 250 // Fantom Opera Mainnet + | 4002 // Fantom Testnet + | 81457 // Blast Mainnet + | 168587773 // Blast Sepolia Testnet + | 10 // OP Mainnet + | 11155420 // OP Sepolia Testnet + | 43114 // Avalanche C-Chain + | 43113 // Avalanche Fuji Testnet + | 199 // BitTorrent Chain Mainnet + | 1028 // BitTorrent Chain Testnet + | 42220 // Celo Mainnet + | 44787 // Celo Alfajores Testnet + | 25 // Cronos Mainnet + | 252 // Fraxtal Mainnet + | 2522 // Fraxtal Testnet + | 100 // Gnosis + | 255 // Kroma Mainnet + | 2358 // Kroma Sepolia Testnet + | 5000 // Mantle Mainnet + | 5003 // Mantle Sepolia Testnet + | 1284 // Moonbeam Mainnet + | 1285 // Moonriver Mainnet + | 1287 // Moonbase Alpha Testnet + | 204 // opBNB Mainnet + | 5611 // opBNB Testnet + | 534352 // Scroll Mainnet + | 534351 // Scroll Sepolia Testnet + | 167000 // Taiko Mainnet + | 167009 // Taiko Hekla L2 Testnet + | 1111 // WEMIX3.0 Mainnet + | 1112 // WEMIX3.0 Testnet + | 324 // zkSync Mainnet + | 300 // zkSync Sepolia Testnet + | 660279 // Xai Mainnet + | 37714555429 // Xai Sepolia Testnet + | 50 // XDC Mainnet + | 51 // XDC Apothem Testnet + | 33139 // ApeChain Mainnet + | 33111 // ApeChain Curtis Testnet + | 480 // World Mainnet + | 4801 // World Sepolia Testnet + | 50104 // Sophon Mainnet + | 531050104 // Sophon Sepolia Testnet + | 146 // Sonic Mainnet + | 57054 // Sonic Blaze Testnet + | 130 // Unichain Mainnet + | 1301 // Unichain Sepolia Testnet + | 2741 // Abstract Mainnet + | 11124 // Abstract Sepolia Testnet + | 80094 // Berachain Mainnet + | 80069 // Berachain Bepolia Testnet + | 1923 // Swellchain Mainnet + | 1924 // Swellchain Testnet + | 10143 // Monad Testnet diff --git a/packages/cli/src/plugins/fetch.test.ts b/packages/cli/src/plugins/fetch.test.ts new file mode 100644 index 0000000000..600cbfeda7 --- /dev/null +++ b/packages/cli/src/plugins/fetch.test.ts @@ -0,0 +1,186 @@ +import { mkdir, rm, writeFile } from 'node:fs/promises' +import { homedir } from 'node:os' +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest' + +import { + address, + apiKey, + baseUrl, + handlers, + timeoutAddress, + unverifiedContractAddress, +} from '../../test/utils.js' +import { fetch, getCacheDir } from './fetch.js' + +const server = setupServer(...handlers) + +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +type Fetch = Parameters[0] +const request: Fetch['request'] = ({ address }) => { + return { + url: `${baseUrl}?module=contract&action=getabi&address=${address}&apikey=${apiKey}`, + } +} +const parse: Fetch['parse'] = async ({ response }) => { + const data = (await response.json()) as + | { status: '1'; message: 'OK'; result: string } + | { status: '0'; message: 'NOTOK'; result: string } + if (data.status === '0') throw new Error(data.result) + return JSON.parse(data.result) +} + +test('fetches ABI', async () => { + await expect( + fetch({ + contracts: [{ name: 'WagmiMintExample', address }], + request, + parse, + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fails to fetch for unverified contract', async () => { + await expect( + fetch({ + contracts: [ + { name: 'WagmiMintExample', address: unverifiedContractAddress }, + ], + request, + parse, + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: Contract source code not verified]', + ) +}) + +test('aborts request', async () => { + await expect( + fetch({ + contracts: [{ name: 'WagmiMintExample', address: timeoutAddress }], + request, + parse, + timeoutDuration: 1_000, + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[AbortError: This operation was aborted]', + ) +}) + +test('reads from cache', async () => { + const cacheDir = `${homedir}/.wagmi-cli/plugins/fetch/cache` + await mkdir(cacheDir, { recursive: true }) + + const contract = { + name: 'WagmiMintExample', + address: timeoutAddress, + } as const + const cacheKey = JSON.stringify(contract) + const cacheFilePath = `${cacheDir}/${cacheKey}.json` + await writeFile( + cacheFilePath, + JSON.stringify( + { + abi: [ + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + timestamp: Date.now() + 30_000, + }, + null, + 2, + ), + ) + + await expect( + fetch({ + contracts: [contract], + request, + parse, + }).contracts?.(), + ).resolves.toMatchInlineSnapshot(` + [ + { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "name": "WagmiMintExample", + }, + ] + `) + + await rm(cacheDir, { recursive: true }) +}) + +test('fails and reads from cache', async () => { + const cacheDir = getCacheDir() + await mkdir(cacheDir, { recursive: true }) + + const contract = { + name: 'WagmiMintExample', + address: timeoutAddress, + } as const + const cacheKey = JSON.stringify(contract) + const cacheFilePath = `${cacheDir}/${cacheKey}.json` + await writeFile( + cacheFilePath, + JSON.stringify( + { + abi: [ + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + timestamp: Date.now() - 30_000, + }, + null, + 2, + ), + ) + + await expect( + fetch({ + contracts: [contract], + request, + parse, + timeoutDuration: 1, + }).contracts?.(), + ).resolves.toMatchInlineSnapshot(` + [ + { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "name": "WagmiMintExample", + }, + ] + `) + + await rm(cacheDir, { recursive: true }) +}) diff --git a/packages/cli/src/plugins/fetch.ts b/packages/cli/src/plugins/fetch.ts new file mode 100644 index 0000000000..778d4a8162 --- /dev/null +++ b/packages/cli/src/plugins/fetch.ts @@ -0,0 +1,127 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { homedir } from 'node:os' +import { join } from 'pathe' + +import type { Abi } from 'viem' +import type { ContractConfig, Plugin } from '../config.js' +import type { Compute, RequiredBy } from '../types.js' + +export type FetchConfig = { + /** + * Duration in milliseconds to cache ABIs from request. + * + * @default 1_800_000 // 30m in ms + */ + cacheDuration?: number | undefined + /** + * Contracts to fetch ABIs for. + */ + contracts: Compute>[] + /** + * Function for creating a cache key for contract. + */ + getCacheKey?: + | ((config: { contract: Compute> }) => string) + | undefined + /** + * Name of source. + */ + name?: ContractConfig['name'] | undefined + /** + * Function for parsing ABI from fetch response. + * + * @default ({ response }) => response.json() + */ + parse?: + | ((config: { + response: Response + }) => ContractConfig['abi'] | Promise) + | undefined + /** + * Function for returning a request to fetch ABI from. + */ + request: (config: { + address?: ContractConfig['address'] | undefined + name: ContractConfig['name'] + }) => + | { url: RequestInfo; init?: RequestInit | undefined } + | Promise<{ url: RequestInfo; init?: RequestInit | undefined }> + /** + * Duration in milliseconds before request times out. + * + * @default 5_000 // 5s in ms + */ + timeoutDuration?: number | undefined +} + +type FetchResult = Compute> + +/** Fetches and parses contract ABIs from network resource with `fetch`. */ +export function fetch(config: FetchConfig): FetchResult { + const { + cacheDuration = 1_800_000, + contracts: contractConfigs, + getCacheKey = ({ contract }) => JSON.stringify(contract), + name = 'Fetch', + parse = ({ response }) => response.json(), + request, + timeoutDuration = 5_000, + } = config + + return { + async contracts() { + const cacheDir = getCacheDir() + await mkdir(cacheDir, { recursive: true }) + + const timestamp = Date.now() + cacheDuration + const contracts = [] + for (const contract of contractConfigs) { + const cacheKey = getCacheKey({ contract }) + const cacheFilePath = join(cacheDir, `${cacheKey}.json`) + const cachedFile = JSON.parse( + await readFile(cacheFilePath, 'utf8').catch(() => 'null'), + ) + + let abi: Abi | undefined + if (cachedFile?.timestamp > Date.now()) abi = cachedFile.abi + else { + try { + const controller = new globalThis.AbortController() + const timeout = setTimeout( + () => controller.abort(), + timeoutDuration, + ) + + const { url, init } = await request(contract) + const response = await globalThis.fetch(url, { + ...init, + signal: controller.signal, + }) + clearTimeout(timeout) + + abi = await parse({ response }) + await writeFile( + cacheFilePath, + `${JSON.stringify({ abi, timestamp }, undefined, 2)}\n`, + ) + } catch (error) { + try { + // Attempt to read from cache if fetch fails. + abi = JSON.parse(await readFile(cacheFilePath, 'utf8')).abi + } catch {} + if (!abi) throw error + } + } + + if (!abi) throw Error('Failed to fetch ABI for contract.') + contracts.push({ abi, address: contract.address, name: contract.name }) + } + return contracts + }, + name, + } +} + +export function getCacheDir() { + return join(homedir(), '.wagmi-cli/plugins/fetch/cache') +} diff --git a/packages/cli/src/plugins/foundry.test.ts b/packages/cli/src/plugins/foundry.test.ts new file mode 100644 index 0000000000..75e5ec73ee --- /dev/null +++ b/packages/cli/src/plugins/foundry.test.ts @@ -0,0 +1,153 @@ +import fixtures from 'fixturez' +import { dirname, resolve } from 'pathe' +import { afterEach, expect, test, vi } from 'vitest' + +import { foundry } from './foundry.js' + +const f = fixtures(__dirname) + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('forge not installed', async () => { + const dir = f.temp() + expect( + foundry({ + project: dir, + forge: { + path: '/path/to/forge', + }, + }).validate?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: forge must be installed to use Foundry plugin. + To install, follow the instructions at https://book.getfoundry.sh/getting-started/installation] + `) +}) + +test('project does not exist', async () => { + const dir = f.temp() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + try { + await foundry({ project: '../path/to/project' }).validate?.() + } catch (error) { + expect( + (error as Error).message.replace(dirname(dir), '..'), + ).toMatchInlineSnapshot('"Foundry project ../path/to/project not found."') + } +}) + +test('validates without project', async () => { + const dir = resolve(__dirname, '__fixtures__/foundry/') + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(foundry().validate?.()).resolves.toBeUndefined() +}) + +test('contracts', async () => { + await expect( + foundry({ + project: resolve(__dirname, '__fixtures__/foundry/'), + exclude: ['Foo.sol/**'], + }).contracts?.(), + ).resolves.toMatchInlineSnapshot(` + [ + { + "abi": [ + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "number", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256", + }, + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": undefined, + "name": "Counter", + }, + ] + `) +}) + +test('contracts without project', async () => { + const dir = resolve(__dirname, '__fixtures__/foundry/') + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect( + foundry({ + exclude: ['Foo.sol/**'], + }).contracts?.(), + ).resolves.toMatchInlineSnapshot(` + [ + { + "abi": [ + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "number", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256", + }, + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": undefined, + "name": "Counter", + }, + ] + `) +}) diff --git a/packages/cli/src/plugins/foundry.ts b/packages/cli/src/plugins/foundry.ts new file mode 100644 index 0000000000..dab307a58f --- /dev/null +++ b/packages/cli/src/plugins/foundry.ts @@ -0,0 +1,263 @@ +import { execSync, spawn, spawnSync } from 'node:child_process' +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import dedent from 'dedent' +import { fdir } from 'fdir' +import { basename, extname, join, resolve } from 'pathe' +import pc from 'picocolors' +import { z } from 'zod' + +import type { ContractConfig, Plugin } from '../config.js' +import * as logger from '../logger.js' +import type { Compute, RequiredBy } from '../types.js' + +export const foundryDefaultExcludes = [ + 'Base.sol/**', + 'Common.sol/**', + 'Components.sol/**', + 'IERC165.sol/**', + 'IERC20.sol/**', + 'IERC721.sol/**', + 'IMulticall2.sol/**', + 'MockERC20.sol/**', + 'MockERC721.sol/**', + 'Script.sol/**', + 'StdAssertions.sol/**', + 'StdChains.sol/**', + 'StdCheats.sol/**', + 'StdError.sol/**', + 'StdInvariant.sol/**', + 'StdJson.sol/**', + 'StdMath.sol/**', + 'StdStorage.sol/**', + 'StdStyle.sol/**', + 'StdToml.sol/**', + 'StdUtils.sol/**', + 'Test.sol/**', + 'Vm.sol/**', + 'build-info/**', + 'console.sol/**', + 'console2.sol/**', + 'safeconsole.sol/**', + '**.s.sol/*.json', + '**.t.sol/*.json', +] + +export type FoundryConfig = { + /** + * Project's artifacts directory. + * + * Same as your project's `--out` (`-o`) option. + * + * @default foundry.config#out | 'out' + */ + artifacts?: string | undefined + /** Mapping of addresses to attach to artifacts. */ + deployments?: { [key: string]: ContractConfig['address'] } | undefined + /** Artifact files to exclude. */ + exclude?: string[] | undefined + /** [Forge](https://book.getfoundry.sh/forge) configuration */ + forge?: + | { + /** + * Remove build artifacts and cache directories on start up. + * + * @default false + */ + clean?: boolean | undefined + /** + * Build Foundry project before fetching artifacts. + * + * @default true + */ + build?: boolean | undefined + /** + * Path to `forge` executable command + * + * @default "forge" + */ + path?: string | undefined + /** + * Rebuild every time a watched file or directory is changed. + * + * @default true + */ + rebuild?: boolean | undefined + } + | undefined + /** Artifact files to include. */ + include?: string[] | undefined + /** Optional prefix to prepend to artifact names. */ + namePrefix?: string | undefined + /** Path to foundry project. */ + project?: string | undefined +} + +type FoundryResult = Compute< + RequiredBy +> + +const FoundryConfigSchema = z.object({ + out: z.string().default('out'), + src: z.string().default('src'), +}) + +/** Resolves ABIs from [Foundry](https://github.com/foundry-rs/foundry) project. */ +export function foundry(config: FoundryConfig = {}): FoundryResult { + const { + artifacts, + deployments = {}, + exclude = foundryDefaultExcludes, + forge: { + clean = false, + build = true, + path: forgeExecutable = 'forge', + rebuild = true, + } = {}, + include = ['*.json'], + namePrefix = '', + } = config + + function getContractName(artifactPath: string, usePrefix = true) { + const filename = basename(artifactPath) + const extension = extname(artifactPath) + return `${usePrefix ? namePrefix : ''}${filename.replace(extension, '')}` + } + + async function getContract(artifactPath: string) { + const artifact = await JSON.parse(await readFile(artifactPath, 'utf8')) + return { + abi: artifact.abi, + address: (deployments as Record)[ + getContractName(artifactPath, false) + ], + name: getContractName(artifactPath), + } + } + + function getArtifactPaths(artifactsDirectory: string) { + const crawler = new fdir().withBasePath().globWithOptions( + include.map((x) => `${artifactsDirectory}/**/${x}`), + { + dot: true, + ignore: exclude.map((x) => `${artifactsDirectory}/**/${x}`), + }, + ) + return crawler.crawl(artifactsDirectory).withPromise() + } + + const project = resolve(process.cwd(), config.project ?? '') + + let foundryConfig: z.infer = { + out: 'out', + src: 'src', + } + try { + const result = spawnSync( + forgeExecutable, + ['config', '--json', '--root', project], + { + encoding: 'utf-8', + shell: true, + }, + ) + if (result.error) throw result.error + if (result.status !== 0) + throw new Error(`Failed with code ${result.status}`) + if (result.signal) throw new Error('Process terminated by signal') + foundryConfig = FoundryConfigSchema.parse(JSON.parse(result.stdout)) + } catch { + } finally { + foundryConfig = { + ...foundryConfig, + out: artifacts ?? foundryConfig.out, + } + } + + const artifactsDirectory = join(project, foundryConfig.out) + + return { + async contracts() { + if (clean) + execSync(`${forgeExecutable} clean --root ${project}`, { + encoding: 'utf-8', + stdio: 'pipe', + }) + if (build) + execSync(`${forgeExecutable} build --root ${project}`, { + encoding: 'utf-8', + stdio: 'pipe', + }) + if (!existsSync(artifactsDirectory)) + throw new Error('Artifacts not found.') + + const artifactPaths = await getArtifactPaths(artifactsDirectory) + const contracts = [] + for (const artifactPath of artifactPaths) { + const contract = await getContract(artifactPath) + if (!contract.abi?.length) continue + contracts.push(contract) + } + return contracts + }, + name: 'Foundry', + async validate() { + // Check that project directory exists + if (!existsSync(project)) + throw new Error(`Foundry project ${pc.gray(config.project)} not found.`) + + // Ensure forge is installed + if (clean || build || rebuild) + try { + execSync(`${forgeExecutable} --version`, { + encoding: 'utf-8', + stdio: 'pipe', + }) + } catch (_error) { + throw new Error(dedent` + forge must be installed to use Foundry plugin. + To install, follow the instructions at https://book.getfoundry.sh/getting-started/installation + `) + } + }, + watch: { + command: rebuild + ? async () => { + logger.log( + `${pc.magenta('Foundry')} Watching project at ${pc.gray( + project, + )}`, + ) + const subprocess = spawn(forgeExecutable, [ + 'build', + '--watch', + '--root', + project, + ]) + subprocess.stdout?.on('data', (data) => { + process.stdout.write(`${pc.magenta('Foundry')} ${data}`) + }) + + process.once('SIGINT', shutdown) + process.once('SIGTERM', shutdown) + function shutdown() { + subprocess?.kill() + } + } + : undefined, + paths: [ + ...include.map((x) => `${artifactsDirectory}/**/${x}`), + ...exclude.map((x) => `!${artifactsDirectory}/**/${x}`), + ], + async onAdd(path) { + return getContract(path) + }, + async onChange(path) { + return getContract(path) + }, + async onRemove(path) { + return getContractName(path) + }, + }, + } +} diff --git a/packages/cli/src/plugins/hardhat.test.ts b/packages/cli/src/plugins/hardhat.test.ts new file mode 100644 index 0000000000..efb416c5e6 --- /dev/null +++ b/packages/cli/src/plugins/hardhat.test.ts @@ -0,0 +1,85 @@ +import fixtures from 'fixturez' +import { dirname, resolve } from 'pathe' +import { afterEach, expect, test, vi } from 'vitest' + +import { hardhat } from './hardhat.js' + +const f = fixtures(__dirname) + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('validate', async () => { + const temp = f.temp() + expect( + hardhat({ project: temp }).validate?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: hardhat must be installed to use Hardhat plugin.]', + ) +}) + +test('project does not exist', async () => { + const dir = f.temp() + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + try { + await hardhat({ project: '../path/to/project' }).validate?.() + } catch (error) { + expect( + (error as Error).message.replace(dirname(dir), '..'), + ).toMatchInlineSnapshot('"Hardhat project ../path/to/project not found."') + } +}) + +test('contracts', async () => { + expect( + hardhat({ + project: resolve(__dirname, '__fixtures__/hardhat/'), + exclude: ['Foo.sol/**'], + }).contracts?.(), + ).resolves.toMatchInlineSnapshot(` + [ + { + "abi": [ + { + "inputs": [], + "name": "increment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + { + "inputs": [], + "name": "number", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256", + }, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newNumber", + "type": "uint256", + }, + ], + "name": "setNumber", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "address": undefined, + "name": "Counter", + }, + ] + `) +}, 10_000) diff --git a/packages/cli/src/plugins/hardhat.ts b/packages/cli/src/plugins/hardhat.ts new file mode 100644 index 0000000000..a4feb6efdf --- /dev/null +++ b/packages/cli/src/plugins/hardhat.ts @@ -0,0 +1,235 @@ +import { execSync, spawn } from 'node:child_process' +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { fdir } from 'fdir' +import { basename, extname, join, resolve } from 'pathe' +import pc from 'picocolors' + +import type { ContractConfig, Plugin } from '../config.js' +import * as logger from '../logger.js' +import type { Compute, RequiredBy } from '../types.js' +import { getIsPackageInstalled, getPackageManager } from '../utils/packages.js' + +export const hardhatDefaultExcludes = ['build-info/**', '*.dbg.json'] + +export type HardhatConfig = { + /** + * Project's artifacts directory. + * + * Same as your project's `artifacts` [path configuration](https://hardhat.org/hardhat-runner/docs/config#path-configuration) option. + * + * @default 'artifacts/' + */ + artifacts?: string | undefined + /** Mapping of addresses to attach to artifacts. */ + deployments?: { [key: string]: ContractConfig['address'] } | undefined + /** Artifact files to exclude. */ + exclude?: string[] | undefined + /** Commands to run */ + commands?: + | { + /** + * Remove build artifacts and cache directories on start up. + * + * @default `${packageManger} hardhat clean` + */ + clean?: string | boolean | undefined + /** + * Build Hardhat project before fetching artifacts. + * + * @default `${packageManger} hardhat compile` + */ + build?: string | boolean | undefined + /** + * Command to run when watched file or directory is changed. + * + * @default `${packageManger} hardhat compile` + */ + rebuild?: string | boolean | undefined + } + | undefined + /** Artifact files to include. */ + include?: string[] | undefined + /** Optional prefix to prepend to artifact names. */ + namePrefix?: string | undefined + /** Path to Hardhat project. */ + project: string + /** + * Project's artifacts directory. + * + * Same as your project's `sources` [path configuration](https://hardhat.org/hardhat-runner/docs/config#path-configuration) option. + * + * @default 'contracts/' + */ + sources?: string | undefined +} + +type HardhatResult = Compute< + RequiredBy +> + +/** Resolves ABIs from [Hardhat](https://github.com/NomicFoundation/hardhat) project. */ +export function hardhat(config: HardhatConfig): HardhatResult { + const { + artifacts = 'artifacts', + deployments = {}, + exclude = hardhatDefaultExcludes, + commands = {}, + include = ['*.json'], + namePrefix = '', + sources = 'contracts', + } = config + + function getContractName(artifact: { contractName: string }) { + return `${namePrefix}${artifact.contractName}` + } + + async function getContract(artifactPath: string) { + const artifact = await JSON.parse(await readFile(artifactPath, 'utf8')) + return { + abi: artifact.abi, + address: deployments[artifact.contractName], + name: getContractName(artifact), + } + } + + function getArtifactPaths(artifactsDirectory: string) { + const crawler = new fdir().withBasePath().globWithOptions( + include.map((x) => `${artifactsDirectory}/**/${x}`), + { + dot: true, + ignore: exclude.map((x) => `${artifactsDirectory}/**/${x}`), + }, + ) + return crawler.crawl(artifactsDirectory).withPromise() + } + + const project = resolve(process.cwd(), config.project) + const artifactsDirectory = join(project, artifacts) + const sourcesDirectory = join(project, sources) + + const { build = true, clean = false, rebuild = true } = commands + return { + async contracts() { + if (clean) { + const packageManager = await getPackageManager(true) + const [command, ...options] = ( + typeof clean === 'boolean' ? `${packageManager} hardhat clean` : clean + ).split(' ') + execSync(`${command!} ${options.join(' ')}`, { + cwd: project, + encoding: 'utf-8', + stdio: 'pipe', + }) + } + if (build) { + const packageManager = await getPackageManager(true) + const [command, ...options] = ( + typeof build === 'boolean' + ? `${packageManager} hardhat compile` + : build + ).split(' ') + execSync(`${command!} ${options.join(' ')}`, { + cwd: project, + encoding: 'utf-8', + stdio: 'pipe', + }) + } + if (!existsSync(artifactsDirectory)) + throw new Error('Artifacts not found.') + + const artifactPaths = await getArtifactPaths(artifactsDirectory) + const contracts = [] + for (const artifactPath of artifactPaths) { + const contract = await getContract(artifactPath) + if (!contract.abi?.length) continue + contracts.push(contract) + } + return contracts + }, + name: 'Hardhat', + async validate() { + // Check that project directory exists + if (!existsSync(project)) + throw new Error(`Hardhat project ${pc.gray(project)} not found.`) + + // Check that `hardhat` is installed + const packageName = 'hardhat' + const isPackageInstalled = await getIsPackageInstalled({ + packageName, + cwd: project, + }) + if (isPackageInstalled) return + throw new Error(`${packageName} must be installed to use Hardhat plugin.`) + }, + watch: { + command: rebuild + ? async () => { + logger.log( + `${pc.blue('Hardhat')} Watching project at ${pc.gray(project)}`, + ) + + const [command, ...options] = ( + typeof rebuild === 'boolean' + ? `${await getPackageManager(true)} hardhat compile` + : rebuild + ).split(' ') + + const { watch } = await import('chokidar') + const watcher = watch(sourcesDirectory, { + atomic: true, + awaitWriteFinish: true, + ignoreInitial: true, + persistent: true, + }) + watcher.on('all', async (event, path) => { + if (event !== 'change' && event !== 'add' && event !== 'unlink') + return + logger.log( + `${pc.blue('Hardhat')} Detected ${event} at ${basename(path)}`, + ) + const subprocess = spawn(command!, options, { + cwd: project, + }) + subprocess.stdout?.on('data', (data) => { + process.stdout.write(`${pc.blue('Hardhat')} ${data}`) + }) + }) + + process.once('SIGINT', shutdown) + process.once('SIGTERM', shutdown) + async function shutdown() { + await watcher.close() + } + } + : undefined, + paths: [ + artifactsDirectory, + ...include.map((x) => `${artifactsDirectory}/**/${x}`), + ...exclude.map((x) => `!${artifactsDirectory}/**/${x}`), + ], + async onAdd(path) { + return getContract(path) + }, + async onChange(path) { + return getContract(path) + }, + async onRemove(path) { + const filename = basename(path) + const extension = extname(path) + // Since we can't use `getContractName`, guess from path + const removedContractName = `${namePrefix}${filename.replace( + extension, + '', + )}` + const artifactPaths = await getArtifactPaths(artifactsDirectory) + for (const artifactPath of artifactPaths) { + const contract = await getContract(artifactPath) + // If contract with same name exists, don't remove + if (contract.name === removedContractName) return + } + return removedContractName + }, + }, + } +} diff --git a/packages/cli/src/plugins/react.test.ts b/packages/cli/src/plugins/react.test.ts new file mode 100644 index 0000000000..939a5299ae --- /dev/null +++ b/packages/cli/src/plugins/react.test.ts @@ -0,0 +1,337 @@ +import { erc20Abi } from 'viem' +import { expect, test } from 'vitest' + +import { react } from './react.js' + +test('default', async () => { + const result = await react().run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.imports).toMatchInlineSnapshot(` + "import { createUseReadContract, createUseWriteContract, createUseSimulateContract, createUseWatchContractEvent } from 'wagmi/codegen' + " + `) + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useReadErc20 = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const useReadErc20Allowance = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'allowance' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const useReadErc20BalanceOf = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'balanceOf' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const useReadErc20Decimals = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'decimals' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const useReadErc20Name = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'name' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const useReadErc20Symbol = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'symbol' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const useReadErc20TotalSupply = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, functionName: 'totalSupply' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useWriteErc20 = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const useWriteErc20Approve = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, functionName: 'approve' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const useWriteErc20Transfer = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, functionName: 'transfer' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const useWriteErc20TransferFrom = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useSimulateErc20 = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const useSimulateErc20Approve = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, functionName: 'approve' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const useSimulateErc20Transfer = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, functionName: 'transfer' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const useSimulateErc20TransferFrom = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useWatchErc20Event = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const useWatchErc20ApprovalEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, eventName: 'Approval' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const useWatchErc20TransferEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, eventName: 'Transfer' })" + `) +}) + +test('address', async () => { + const result = await react().run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + addressName: 'erc20Address', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useReadErc20 = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const useReadErc20Allowance = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'allowance' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const useReadErc20BalanceOf = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'balanceOf' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const useReadErc20Decimals = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'decimals' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const useReadErc20Name = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'name' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const useReadErc20Symbol = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'symbol' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const useReadErc20TotalSupply = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'totalSupply' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useWriteErc20 = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const useWriteErc20Approve = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const useWriteErc20Transfer = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const useWriteErc20TransferFrom = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useSimulateErc20 = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const useSimulateErc20Approve = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const useSimulateErc20Transfer = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const useSimulateErc20TransferFrom = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useWatchErc20Event = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const useWatchErc20ApprovalEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Approval' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const useWatchErc20TransferEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Transfer' })" + `) +}) + +test('legacy hook names', async () => { + const result = await react({ getHookName: 'legacy' }).run?.({ + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + content: '', + meta: { + abiName: 'erc20Abi', + addressName: 'erc20Address', + }, + }, + ], + isTypeScript: true, + outputs: [], + }) + + expect(result?.content).toMatchInlineSnapshot(` + "/** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useErc20Read = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"allowance"\` + */ + export const useErc20Allowance = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'allowance' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"balanceOf"\` + */ + export const useErc20BalanceOf = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'balanceOf' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"decimals"\` + */ + export const useErc20Decimals = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'decimals' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"name"\` + */ + export const useErc20Name = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'name' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"symbol"\` + */ + export const useErc20Symbol = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'symbol' }) + + /** + * Wraps __{@link useReadContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"totalSupply"\` + */ + export const useErc20TotalSupply = /*#__PURE__*/ createUseReadContract({ abi: erc20Abi, address: erc20Address, functionName: 'totalSupply' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useErc20Write = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const useErc20Approve = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const useErc20Transfer = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link useWriteContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const useErc20TransferFrom = /*#__PURE__*/ createUseWriteContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const usePrepareErc20Write = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"approve"\` + */ + export const usePrepareErc20Approve = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'approve' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transfer"\` + */ + export const usePrepareErc20Transfer = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transfer' }) + + /** + * Wraps __{@link useSimulateContract}__ with \`abi\` set to __{@link erc20Abi}__ and \`functionName\` set to \`"transferFrom"\` + */ + export const usePrepareErc20TransferFrom = /*#__PURE__*/ createUseSimulateContract({ abi: erc20Abi, address: erc20Address, functionName: 'transferFrom' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ + */ + export const useErc20Event = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Approval"\` + */ + export const useErc20ApprovalEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Approval' }) + + /** + * Wraps __{@link useWatchContractEvent}__ with \`abi\` set to __{@link erc20Abi}__ and \`eventName\` set to \`"Transfer"\` + */ + export const useErc20TransferEvent = /*#__PURE__*/ createUseWatchContractEvent({ abi: erc20Abi, address: erc20Address, eventName: 'Transfer' })" + `) +}) diff --git a/packages/cli/src/plugins/react.ts b/packages/cli/src/plugins/react.ts new file mode 100644 index 0000000000..b76ea006a5 --- /dev/null +++ b/packages/cli/src/plugins/react.ts @@ -0,0 +1,312 @@ +import { pascalCase } from 'change-case' + +import type { Contract, Plugin } from '../config.js' +import type { Compute, RequiredBy } from '../types.js' +import { getAddressDocString } from '../utils/getAddressDocString.js' + +export type ReactConfig = { + getHookName?: + | 'legacy' // TODO: Deprecate `'legacy'` option + | ((options: { + contractName: string + itemName?: string | undefined + type: 'read' | 'simulate' | 'watch' | 'write' + }) => `use${string}`) +} + +type ReactResult = Compute> + +export function react(config: ReactConfig = {}): ReactResult { + return { + name: 'React', + async run({ contracts }) { + const imports = new Set([]) + const content: string[] = [] + const pure = '/*#__PURE__*/' + + const hookNames = new Set() + for (const contract of contracts) { + let hasReadFunction = false + let hasWriteFunction = false + let hasEvent = false + const readItems = [] + const writeItems = [] + const eventItems = [] + for (const item of contract.abi) { + if (item.type === 'function') + if ( + item.stateMutability === 'view' || + item.stateMutability === 'pure' + ) { + hasReadFunction = true + readItems.push(item) + } else { + hasWriteFunction = true + writeItems.push(item) + } + else if (item.type === 'event') { + hasEvent = true + eventItems.push(item) + } + } + + let innerContent: string + if (contract.meta.addressName) + innerContent = `abi: ${contract.meta.abiName}, address: ${contract.meta.addressName}` + else innerContent = `abi: ${contract.meta.abiName}` + + if (hasReadFunction) { + const hookName = getHookName(config, hookNames, 'read', contract.name) + const docString = genDocString('useReadContract', contract) + const functionName = 'createUseReadContract' + imports.add(functionName) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of readItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'pure' && + item.stateMutability !== 'view' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const hookName = getHookName( + config, + hookNames, + 'read', + contract.name, + item.name, + ) + const docString = genDocString('useReadContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + + if (hasWriteFunction) { + { + const hookName = getHookName( + config, + hookNames, + 'write', + contract.name, + ) + const docString = genDocString('useWriteContract', contract) + const functionName = 'createUseWriteContract' + imports.add(functionName) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of writeItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'nonpayable' && + item.stateMutability !== 'payable' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const hookName = getHookName( + config, + hookNames, + 'write', + contract.name, + item.name, + ) + const docString = genDocString('useWriteContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + + { + const hookName = getHookName( + config, + hookNames, + 'simulate', + contract.name, + ) + const docString = genDocString('useSimulateContract', contract) + const functionName = 'createUseSimulateContract' + imports.add(functionName) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of writeItems) { + if (item.type !== 'function') continue + if ( + item.stateMutability !== 'nonpayable' && + item.stateMutability !== 'payable' + ) + continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const hookName = getHookName( + config, + hookNames, + 'simulate', + contract.name, + item.name, + ) + const docString = genDocString('useSimulateContract', contract, { + name: 'functionName', + value: item.name, + }) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent}, functionName: '${item.name}' })`, + ) + } + } + } + + if (hasEvent) { + const hookName = getHookName( + config, + hookNames, + 'watch', + contract.name, + ) + const docString = genDocString('useWatchContractEvent', contract) + const functionName = 'createUseWatchContractEvent' + imports.add(functionName) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent} })`, + ) + + const names = new Set() + for (const item of eventItems) { + if (item.type !== 'event') continue + + // Skip overrides since they are captured by same hook + if (names.has(item.name)) continue + names.add(item.name) + + const hookName = getHookName( + config, + hookNames, + 'watch', + contract.name, + item.name, + ) + const docString = genDocString('useWatchContractEvent', contract, { + name: 'eventName', + value: item.name, + }) + content.push( + `${docString} +export const ${hookName} = ${pure} ${functionName}({ ${innerContent}, eventName: '${item.name}' })`, + ) + } + } + } + + const importValues = [...imports.values()] + + return { + imports: importValues.length + ? `import { ${importValues.join(', ')} } from 'wagmi/codegen'\n` + : '', + content: content.join('\n\n'), + } + }, + } +} + +function genDocString( + hookName: string, + contract: Contract, + item?: { name: string; value: string }, +) { + let description = `Wraps __{@link ${hookName}}__ with \`abi\` set to __{@link ${contract.meta.abiName}}__` + if (item) description += ` and \`${item.name}\` set to \`"${item.value}"\`` + + const docString = getAddressDocString({ address: contract.address }) + if (docString) + return `/** + * ${description} + * + ${docString} + */` + + return `/** + * ${description} + */` +} + +function getHookName( + config: ReactConfig, + hookNames: Set, + type: 'read' | 'simulate' | 'watch' | 'write', + contractName: string, + itemName?: string | undefined, +) { + const ContractName = pascalCase(contractName) + const ItemName = itemName ? pascalCase(itemName) : undefined + + let hookName: string + if (typeof config.getHookName === 'function') + hookName = config.getHookName({ + type, + contractName: ContractName, + itemName: ItemName, + }) + else if (typeof config.getHookName === 'string') { + switch (type) { + case 'read': + hookName = `use${ContractName}${ItemName ?? 'Read'}` + break + case 'simulate': + hookName = `usePrepare${ContractName}${ItemName ?? 'Write'}` + break + case 'watch': + hookName = `use${ContractName}${ItemName ?? ''}Event` + break + case 'write': + hookName = `use${ContractName}${ItemName ?? 'Write'}` + break + } + } else { + hookName = `use${pascalCase(type)}${ContractName}${ItemName ?? ''}` + if (type === 'watch') hookName = `${hookName}Event` + } + + if (hookNames.has(hookName)) + throw new Error( + `Hook name "${hookName}" must be unique for contract "${contractName}". Try using \`getHookName\` to create a unique name.`, + ) + + hookNames.add(hookName) + return hookName +} diff --git a/packages/cli/src/plugins/sourcify.test.ts b/packages/cli/src/plugins/sourcify.test.ts new file mode 100644 index 0000000000..842a291147 --- /dev/null +++ b/packages/cli/src/plugins/sourcify.test.ts @@ -0,0 +1,83 @@ +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest' + +import { depositAbi } from '../../test/constants.js' +import { sourcify } from './sourcify.js' + +const baseUrl = 'https://sourcify.dev/server/v2/contract' +const address = '0x00000000219ab540356cbb839cbe05303d7705fa' +const chainId = 1 +const multichainAddress = '0xC4c622862a8F548997699bE24EA4bc504e5cA865' +const multichainIdGnosis = 100 +const multichainIdPolygon = 137 +const successJson = { + abi: depositAbi, +} + +const handlers = [ + http.get(`${baseUrl}/${chainId}/${address}`, () => + HttpResponse.json(successJson), + ), + http.get(`${baseUrl}/${multichainIdGnosis}/${address}`, () => + HttpResponse.json({}, { status: 404 }), + ), + http.get(`${baseUrl}/${multichainIdGnosis}/${multichainAddress}`, () => + HttpResponse.json(successJson), + ), + http.get(`${baseUrl}/${multichainIdPolygon}/${multichainAddress}`, () => + HttpResponse.json(successJson), + ), +] + +const server = setupServer(...handlers) + +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +test('fetches ABI', () => { + expect( + sourcify({ + chainId: chainId, + contracts: [{ name: 'DepositContract', address }], + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fetches ABI with multichain deployment', () => { + expect( + sourcify({ + chainId: 100, + contracts: [ + { + name: 'Community', + address: { 100: multichainAddress, 137: multichainAddress }, + }, + ], + }).contracts?.(), + ).resolves.toMatchSnapshot() +}) + +test('fails to fetch for unverified contract', () => { + expect( + sourcify({ + chainId: 100, + contracts: [{ name: 'DepositContract', address }], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: Contract not found in Sourcify repository.]', + ) +}) + +test('missing address for chainId', () => { + expect( + sourcify({ + chainId: 1, + // @ts-expect-error `chainId` and `keyof typeof contracts[number].address` mismatch + contracts: [{ name: 'DepositContract', address: { 10: address } }], + }).contracts?.(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: No address found for chainId "1". Make sure chainId "1" is set as an address.]`, + ) +}) diff --git a/packages/cli/src/plugins/sourcify.ts b/packages/cli/src/plugins/sourcify.ts new file mode 100644 index 0000000000..0d452046ca --- /dev/null +++ b/packages/cli/src/plugins/sourcify.ts @@ -0,0 +1,312 @@ +import { Abi as AbiSchema } from 'abitype/zod' +import type { Address } from 'viem' +import { z } from 'zod' + +import type { ContractConfig } from '../config.js' +import { fromZodError } from '../errors.js' +import type { Compute } from '../types.js' +import { fetch } from './fetch.js' + +export type SourcifyConfig = { + /** + * Duration in milliseconds to cache ABIs. + * + * @default 1_800_000 // 30m in ms + */ + cacheDuration?: number | undefined + /** + * Chain id to use for fetching ABI. + * + * If `address` is an object, `chainId` is used to select the address. + * + * See https://docs.sourcify.dev/docs/chains for supported chains. + */ + chainId: (chainId extends ChainId ? chainId : never) | (ChainId & {}) + /** + * Contracts to fetch ABIs for. + */ + contracts: Compute, 'abi'>>[] +} + +const SourcifyResponse = z.object({ + abi: AbiSchema, +}) + +/** Fetches contract ABIs from Sourcify. */ +export function sourcify( + config: SourcifyConfig, +) { + const { cacheDuration, chainId, contracts: contracts_ } = config + + const contracts = contracts_.map((x) => ({ + ...x, + address: + typeof x.address === 'string' ? { [chainId]: x.address } : x.address, + })) as Omit[] + + return fetch({ + cacheDuration, + contracts, + async parse({ response }) { + if (response.status === 404) + throw new Error('Contract not found in Sourcify repository.') + + const json = await response.json() + const parsed = await SourcifyResponse.safeParseAsync(json) + if (!parsed.success) + throw fromZodError(parsed.error, { prefix: 'Invalid response' }) + + if (parsed.data.abi) return parsed.data.abi as ContractConfig['abi'] + throw new Error('contract not found') + }, + request({ address }) { + if (!address) throw new Error('address is required') + + let contractAddress: Address | undefined + if (typeof address === 'string') contractAddress = address + else if (typeof address === 'object') contractAddress = address[chainId] + + if (!contractAddress) + throw new Error( + `No address found for chainId "${chainId}". Make sure chainId "${chainId}" is set as an address.`, + ) + return { + url: `https://sourcify.dev/server/v2/contract/${chainId}/${contractAddress}?fields=abi`, + } + }, + }) +} + +// Supported chains +// https://docs.sourcify.dev/docs/chains +type ChainId = + | 1 // Ethereum Mainnet + | 17000 // Ethereum Testnet Holesky + | 5 // Ethereum Testnet Goerli + | 11155111 // Ethereum Testnet Sepolia + | 3 // Ethereum Testnet Ropsten + | 4 // Ethereum Testnet Rinkeby + | 10 // OP Mainnet + | 100 // Gnosis + | 100009 // VeChain + | 100010 // VeChain Testnet + | 1001 // Kaia Kairos Testnet + | 10200 // Gnosis Chiado Testnet + | 10242 // Arthera Mainnet + | 10243 // Arthera Testnet + | 1030 // Conflux eSpace + | 103090 // Crystaleum + | 105105 // Stratis Mainnet + | 106 // Velas EVM Mainnet + | 10849 // Lamina1 + | 10850 // Lamina1 Identity + | 1088 // Metis Andromeda Mainnet + | 1101 // Polygon zkEVM + | 111000 // Siberium Test Network + | 11111 // WAGMI + | 1114 // Core Blockchain Testnet2 + | 1115 // Core Blockchain Testnet + | 11155420 // OP Sepolia Testnet + | 1116 // Core Blockchain Mainnet + | 11235 // Haqq Network + | 1127469 // Tiltyard Subnet + | 11297108099 // Palm Testnet + | 11297108109 // Palm + | 1149 // Symplexia Smart Chain + | 122 // Fuse Mainnet + | 1284 // Moonbeam + | 1285 // Moonriver + | 1287 // Moonbase Alpha + | 12898 // PlayFair Testnet Subnet + | 1291 // Swisstronik Testnet + | 1313161554 // Aurora Mainnet + | 1313161555 // Aurora Testnet + | 13337 // Beam Testnet + | 13381 // Phoenix Mainnet + | 1339 // Elysium Mainnet + | 137 // Polygon Mainnet + | 14 // Flare Mainnet + | 1433 // Rikeza Network Mainnet + | 1516 // Story Odyssey Testnet + | 16180 // PLYR PHI + | 16350 // Incentiv Devnet + | 167005 // Taiko Grimsvotn L2 + | 167006 // Taiko Eldfell L3 + | 17069 // Garnet Holesky + | 180 // AME Chain Mainnet + | 1890 // Lightlink Phoenix Mainnet + | 1891 // Lightlink Pegasus Testnet + | 19 // Songbird Canary-Network + | 19011 // HOME Verse Mainnet + | 192837465 // Gather Mainnet Network + | 2000 // Dogechain Mainnet + | 200810 // Bitlayer Testnet + | 200901 // Bitlayer Mainnet + | 2017 // Adiri + | 2020 // Ronin Mainnet + | 2021 // Edgeware EdgeEVM Mainnet + | 202401 // YMTECH-BESU Testnet + | 2037 // Kiwi Subnet + | 2038 // Shrapnel Testnet + | 2044 // Shrapnel Subnet + | 2047 // Stratos Testnet + | 2048 // Stratos + | 205205 // Auroria Testnet + | 212 // MAPO Makalu + | 216 // Happychain Testnet + | 222000222 // Kanazawa + | 2221 // Kava Testnet + | 2222 // Kava + | 223 // B2 Mainnet + | 22776 // MAP Protocol + | 23294 // Oasis Sapphire + | 23295 // Oasis Sapphire Testnet + | 2358 // Kroma Sepolia + | 2442 // Polygon zkEVM Cardona Testnet + | 246 // Energy Web Chain + | 25 // Cronos Mainnet + | 250 // Fantom Opera + | 252 // Fraxtal + | 2522 // Fraxtal Testnet + | 255 // Kroma + | 25925 // KUB Testnet + | 26100 // Ferrum Quantum Portal Network + | 28 // Boba Network Rinkeby Testnet + | 28528 // Optimism Bedrock (Goerli Alpha Testnet) + | 288 // Boba Network + | 295 // Hedera Mainnet + | 30 // Rootstock Mainnet + | 300 // zkSync Sepolia Testnet + | 311752642 // OneLedger Mainnet + | 314 // Filecoin - Mainnet + | 314159 // Filecoin - Calibration testnet + | 32769 // Zilliqa EVM + | 32770 // Zilliqa 2 EVM proto-mainnet + | 33101 // Zilliqa EVM Testnet + | 33103 // Zilliqa 2 EVM proto-testnet + | 33111 // Curtis + | 333000333 // Meld + | 335 // DFK Chain Test + | 336 // Shiden + | 34443 // Mode + | 35441 // Q Mainnet + | 35443 // Q Testnet + | 356256156 // Gather Testnet Network + | 369 // PulseChain + | 3737 // Crossbell + | 37714555429 // Xai Testnet v2 + | 383414847825 // Zeniq + | 39797 // Energi Mainnet + | 40 // Telos EVM Mainnet + | 4000 // Ozone Chain Mainnet + | 41 // Telos EVM Testnet + | 4157 // CrossFi Testnet + | 420 // Optimism Goerli Testnet + | 4200 // Merlin Mainnet + | 420420 // Kekchain + | 420666 // Kekchain (kektest) + | 42161 // Arbitrum One + | 421611 // Arbitrum Rinkeby + | 421613 // Arbitrum Goerli + | 4216137055 // OneLedger Testnet Frankenstein + | 421614 // Arbitrum Sepolia + | 42170 // Arbitrum Nova + | 42220 // Celo Mainnet + | 42261 // Oasis Emerald Testnet + | 42262 // Oasis Emerald + | 42766 // ZKFair Mainnet + | 43 // Darwinia Pangolin Testnet + | 43113 // Avalanche Fuji Testnet + | 43114 // Avalanche C-Chain + | 432201 // Dexalot Subnet Testnet + | 432204 // Dexalot Subnet + | 4337 // Beam + | 44 // Crab Network + | 44787 // Celo Alfajores Testnet + | 46 // Darwinia Network + | 486217935 // Gather Devnet Network + | 48898 // Zircuit Garfield Testnet + | 48899 // Zircuit Testnet + | 48900 // Zircuit Mainnet + | 49797 // Energi Testnet + | 50 // XDC Network + | 5000 // Mantle + | 5003 // Mantle Sepolia Testnet + | 51 // XDC Apothem Network + | 5115 // Citrea Testnet + | 534 // Candle + | 534351 // Scroll Sepolia Testnet + | 534352 // Scroll + | 53935 // DFK Chain + | 54211 // Haqq Chain Testnet + | 56 // BNB Smart Chain Mainnet + | 560048 // Hoodi testnet + | 57 // Syscoin Mainnet + | 570 // Rollux Mainnet + | 5700 // Syscoin Tanenbaum Testnet + | 57000 // Rollux Testnet + | 5845 // Tangle + | 59141 // Linea Sepolia + | 59144 // Linea + | 592 // Astar + | 59902 // Metis Sepolia Testnet + | 61 // Ethereum Classic + | 6119 // UPTN + | 62320 // Celo Baklava Testnet + | 62621 // MultiVAC Mainnet + | 62831 // PLYR TAU Testnet + | 6321 // Aura Euphoria Testnet + | 6322 // Aura Mainnet + | 641230 // Bear Network Chain Mainnet + | 648 // Endurance Smart Chain Mainnet + | 660279 // Xai Mainnet + | 666666666 // Degen Chain + | 69 // Optimism Kovan + | 690 // Redstone + | 7000 // ZetaChain Mainnet + | 7001 // ZetaChain Testnet + | 7078815900 // Mekong + | 710420 // Tiltyard Mainnet Subnet + | 71401 // Godwoken Testnet v1 + | 71402 // Godwoken Mainnet + | 7171 // Bitrock Mainnet + | 7200 // exSat Mainnet + | 723107 // TixChain Testnet + | 73799 // Energy Web Volta Testnet + | 764984 // Lamina1 Testnet + | 7668 // The Root Network - Mainnet + | 7672 // The Root Network - Porcini Testnet + | 767368 // Lamina1 Identity Testnet + | 77 // POA Network Sokol + | 7700 // Canto + | 7701 // Canto Tesnet + | 7771 // Bitrock Testnet + | 7777777 // Zora + | 78430 // Amplify Subnet + | 78431 // Bulletin Subnet + | 78432 // Conduit Subnet + | 8 // Ubiq + | 80001 // Mumbai + | 80002 // Amoy + | 82 // Meter Mainnet + | 8217 // Kaia Mainnet + | 83 // Meter Testnet + | 839999 // exSat Testnet + | 841 // Taraxa Mainnet + | 842 // Taraxa Testnet + | 8453 // Base + | 84531 // Base Goerli Testnet + | 84532 // Base Sepolia Testnet + | 888 // Wanchain + | 9000 // Evmos Testnet + | 9001 // Evmos + | 919 // Mode Testnet + | 957 // Lyra Chain + | 96 // KUB Mainnet + | 97 // BNB Smart Chain Testnet + | 970 // Oort Mainnet + | 99 // POA Network Core + | 9977 // Mind Smart Chain Testnet + | 999 // Wanchain Testnet + | 9996 // Mind Smart Chain Mainnet + | 999999999 // Zora Sepolia Testnet diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts new file mode 100644 index 0000000000..adb2889348 --- /dev/null +++ b/packages/cli/src/types.ts @@ -0,0 +1,10 @@ +export type Compute = { [key in keyof type]: type[key] } & unknown + +export type MaybeArray = T | T[] + +export type MaybePromise = T | Promise + +export type RequiredBy = Required< + Pick +> & + Omit diff --git a/packages/cli/src/utils/findConfig.test.ts b/packages/cli/src/utils/findConfig.test.ts new file mode 100644 index 0000000000..52c2066773 --- /dev/null +++ b/packages/cli/src/utils/findConfig.test.ts @@ -0,0 +1,42 @@ +import { afterEach, expect, test, vi } from 'vitest' + +import { createFixture } from '../../test/utils.js' +import { findConfig } from './findConfig.js' + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('finds config file', async () => { + const { dir, paths } = await createFixture({ + files: { 'wagmi.config.ts': '' }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(findConfig()).resolves.toBe(paths['wagmi.config.ts']) +}) + +test('finds config file at location', async () => { + const { dir, paths } = await createFixture({ + files: { 'wagmi.config.ts': '' }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(findConfig({ config: paths['wagmi.config.ts'] })).resolves.toBe( + paths['wagmi.config.ts'], + ) +}) + +test('finds config file at root', async () => { + const { dir, paths } = await createFixture({ + files: { 'wagmi.config.ts': '' }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(findConfig({ root: dir })).resolves.toBe( + paths['wagmi.config.ts'], + ) +}) diff --git a/packages/cli/src/utils/findConfig.ts b/packages/cli/src/utils/findConfig.ts new file mode 100644 index 0000000000..2e94e2d5a4 --- /dev/null +++ b/packages/cli/src/utils/findConfig.ts @@ -0,0 +1,39 @@ +import { existsSync } from 'node:fs' +import escalade from 'escalade' +import { resolve } from 'pathe' + +// Do not reorder +// In order of preference files are checked +const configFiles = [ + 'wagmi.config.ts', + 'wagmi.config.js', + 'wagmi.config.mjs', + 'wagmi.config.mts', +] + +type FindConfigParameters = { + /** Config file name */ + config?: string | undefined + /** Config file directory */ + root?: string | undefined +} + +/** + * Resolves path to wagmi CLI config file. + */ +export async function findConfig(parameters: FindConfigParameters = {}) { + const { config, root } = parameters + const rootDir = resolve(root || process.cwd()) + if (config) { + const path = resolve(rootDir, config) + if (existsSync(path)) return path + return + } + const configPath = await escalade(rootDir, (_dir, names) => { + for (const name of names) { + if (configFiles.includes(name)) return name + } + return undefined + }) + return configPath +} diff --git a/packages/cli/src/utils/format.test.ts b/packages/cli/src/utils/format.test.ts new file mode 100644 index 0000000000..6d04eb5465 --- /dev/null +++ b/packages/cli/src/utils/format.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from 'vitest' + +import { format } from './format.js' + +test('formats code', async () => { + await expect( + format(`const foo = "bar"`), + ).resolves.toMatchInlineSnapshot(` + "const foo = 'bar' + " + `) +}) diff --git a/packages/cli/src/utils/format.ts b/packages/cli/src/utils/format.ts new file mode 100644 index 0000000000..e4fd289c69 --- /dev/null +++ b/packages/cli/src/utils/format.ts @@ -0,0 +1,16 @@ +import prettier from 'prettier' + +export async function format(content: string) { + const config = await prettier.resolveConfig(process.cwd()) + return prettier.format(content, { + arrowParens: 'always', + endOfLine: 'lf', + parser: 'typescript', + printWidth: 80, + semi: false, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', + ...config, + }) +} diff --git a/packages/cli/src/utils/getAddressDocString.test.ts b/packages/cli/src/utils/getAddressDocString.test.ts new file mode 100644 index 0000000000..798e6e14e5 --- /dev/null +++ b/packages/cli/src/utils/getAddressDocString.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'vitest' + +import { getAddressDocString } from './getAddressDocString.js' + +test('address', async () => { + expect( + getAddressDocString({ + address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }), + ).toMatchInlineSnapshot('""') +}) + +test('multichain address with known chain ids', async () => { + expect( + getAddressDocString({ + address: { + 1: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + 5: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + 10: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, + }), + ).toMatchInlineSnapshot(` + "* - [__View Contract on Ethereum Etherscan__](https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e) + * - [__View Contract on Goerli Etherscan__](https://goerli.etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e) + * - [__View Contract on Op Mainnet Optimism Explorer__](https://optimistic.etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e)" + `) +}) + +test('multichain address with unknown chain id', async () => { + expect( + getAddressDocString({ + address: { + 1: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + 2: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, + }), + ).toMatchInlineSnapshot( + '"* [__View Contract on Ethereum Etherscan__](https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e)"', + ) +}) diff --git a/packages/cli/src/utils/getAddressDocString.ts b/packages/cli/src/utils/getAddressDocString.ts new file mode 100644 index 0000000000..d0e137928c --- /dev/null +++ b/packages/cli/src/utils/getAddressDocString.ts @@ -0,0 +1,53 @@ +import { capitalCase } from 'change-case' +import dedent from 'dedent' +import * as allChains from 'viem/chains' + +import type { Contract } from '../config.js' + +const chainMap: Record = {} +for (const chain of Object.values(allChains)) { + if (typeof chain !== 'object') continue + if (!('id' in chain)) continue + chainMap[chain.id] = chain +} + +export function getAddressDocString(parameters: { + address: Contract['address'] +}) { + const { address } = parameters + if (!address || typeof address === 'string') return '' + + if (Object.keys(address).length === 1) + return `* ${getLink({ + address: address[Number.parseInt(Object.keys(address)[0]!)]!, + chainId: Number.parseInt(Object.keys(address)[0]!), + })}` + + const addresses = Object.entries(address).filter( + (x) => chainMap[Number.parseInt(x[0])], + ) + if (addresses.length === 0) return '' + if (addresses.length === 1 && addresses[0]) + return `* ${getLink({ + address: addresses[0][1], + chainId: Number.parseInt(addresses[0][0])!, + })}` + + return dedent` + ${addresses.reduce((prev, curr) => { + const chainId = Number.parseInt(curr[0]) + const address = curr[1] + return `${prev}\n* - ${getLink({ address, chainId })}` + }, '')} + ` +} + +function getLink({ address, chainId }: { address: string; chainId: number }) { + const chain = chainMap[chainId] + if (!chain) return '' + const blockExplorer = chain.blockExplorers?.default + if (!blockExplorer) return '' + return `[__View Contract on ${capitalCase(chain.name)} ${capitalCase( + blockExplorer.name, + )}__](${blockExplorer.url}/address/${address})` +} diff --git a/packages/cli/src/utils/getIsUsingTypeScript.test.ts b/packages/cli/src/utils/getIsUsingTypeScript.test.ts new file mode 100644 index 0000000000..3ba7c86a65 --- /dev/null +++ b/packages/cli/src/utils/getIsUsingTypeScript.test.ts @@ -0,0 +1,43 @@ +import { afterEach, expect, test, vi } from 'vitest' + +import { createFixture } from '../../test/utils.js' +import { getIsUsingTypeScript } from './getIsUsingTypeScript.js' + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('true if has tsconfig', async () => { + const { dir } = await createFixture({ + files: { + 'tsconfig.json': '', + }, + }) + + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(getIsUsingTypeScript()).resolves.toBe(true) +}) + +test('true if has wagmi.config', async () => { + const { dir } = await createFixture({ + files: { + 'wagmi.config.ts': '', + }, + }) + + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(getIsUsingTypeScript()).resolves.toBe(true) +}) + +test('false', async () => { + const { dir } = await createFixture() + + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + await expect(getIsUsingTypeScript()).resolves.toBe(false) +}) diff --git a/packages/cli/src/utils/getIsUsingTypeScript.ts b/packages/cli/src/utils/getIsUsingTypeScript.ts new file mode 100644 index 0000000000..bd2383b0eb --- /dev/null +++ b/packages/cli/src/utils/getIsUsingTypeScript.ts @@ -0,0 +1,33 @@ +import escalade from 'escalade' + +export async function getIsUsingTypeScript() { + try { + const cwd = process.cwd() + const tsconfig = await escalade(cwd, (_dir, names) => { + const files = [ + 'tsconfig.json', + 'tsconfig.base.json', + 'tsconfig.lib.json', + 'tsconfig.node.json', + ] + for (const name of names) { + if (files.includes(name)) return name + } + return undefined + }) + if (tsconfig) return true + + const wagmiConfig = await escalade(cwd, (_dir, names) => { + const files = ['wagmi.config.ts', 'wagmi.config.mts'] + for (const name of names) { + if (files.includes(name)) return name + } + return undefined + }) + if (wagmiConfig) return true + + return false + } catch { + return false + } +} diff --git a/packages/cli/src/utils/loadEnv.test.ts b/packages/cli/src/utils/loadEnv.test.ts new file mode 100644 index 0000000000..d577778eb0 --- /dev/null +++ b/packages/cli/src/utils/loadEnv.test.ts @@ -0,0 +1,77 @@ +import { afterEach, expect, test, vi } from 'vitest' + +import { createFixture } from '../../test/utils.js' +import { loadEnv } from './loadEnv.js' + +afterEach(() => { + vi.restoreAllMocks() +}) + +test('loads env', async () => { + const { dir } = await createFixture({ + files: { + '.env': ` + FOO=bar + SOME_ENV_VAR=1 + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + expect(loadEnv()).toMatchInlineSnapshot(` + { + "FOO": "bar", + "SOME_ENV_VAR": "1", + } + `) +}) + +test('loads env from envDir', async () => { + const { dir } = await createFixture({ + files: { + '.env': ` + FOO=bar + SOME_ENV_VAR=1 + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + expect(loadEnv({ envDir: dir })).toMatchInlineSnapshot(` + { + "FOO": "bar", + "SOME_ENV_VAR": "1", + } + `) +}) + +test('loads env with mode', async () => { + const mode = 'dev' + const { dir } = await createFixture({ + files: { + [`.env.${mode}`]: ` + FOO=bar + SOME_ENV_VAR=1 + `, + }, + }) + const spy = vi.spyOn(process, 'cwd') + spy.mockImplementation(() => dir) + + expect(loadEnv({ mode })).toMatchInlineSnapshot(` + { + "FOO": "bar", + "SOME_ENV_VAR": "1", + } + `) +}) + +test('throws error when mode is "local"', async () => { + expect(() => { + loadEnv({ mode: 'local' }) + }).toThrowErrorMatchingInlineSnapshot( + `[Error: "local" cannot be used as a mode name because it conflicts with the .local postfix for .env files.]`, + ) +}) diff --git a/packages/cli/src/utils/loadEnv.ts b/packages/cli/src/utils/loadEnv.ts new file mode 100644 index 0000000000..d7ffa99919 --- /dev/null +++ b/packages/cli/src/utils/loadEnv.ts @@ -0,0 +1,90 @@ +import { parse } from 'dotenv' +import { expand } from 'dotenv-expand' + +import { existsSync, readFileSync, statSync } from 'node:fs' +import { dirname, join } from 'node:path' + +// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/env.ts#L7 +export function loadEnv( + config: { + mode?: string + envDir?: string + } = {}, +): Record { + const mode = config.mode + if (mode === 'local') { + throw new Error( + `"local" cannot be used as a mode name because it conflicts with the .local postfix for .env files.`, + ) + } + + const envFiles = [ + /** default file */ '.env', + /** local file */ '.env.local', + ...(mode + ? [ + /** mode file */ `.env.${mode}`, + /** mode local file */ `.env.${mode}.local`, + ] + : []), + ] + + const envDir = config.envDir ?? process.cwd() + const parsed = Object.fromEntries( + envFiles.flatMap((file) => { + const path = lookupFile(envDir, [file], { + pathOnly: true, + rootDir: envDir, + }) + if (!path) return [] + return Object.entries(parse(readFileSync(path))) + }), + ) + + try { + // let environment variables use each other + expand({ parsed }) + } catch (error) { + // custom error handling until https://github.com/motdotla/dotenv-expand/issues/65 is fixed upstream + // check for message "TypeError: Cannot read properties of undefined (reading 'split')" + if ((error as Error).message.includes('split')) { + throw new Error( + 'dotenv-expand failed to expand env vars. Maybe you need to escape `$`?', + ) + } + throw error + } + + return parsed +} + +function lookupFile( + dir: string, + formats: string[], + options?: { + pathOnly?: boolean + rootDir?: string + predicate?: (file: string) => boolean + }, +): string | undefined { + for (const format of formats) { + const fullPath = join(dir, format) + if (existsSync(fullPath) && statSync(fullPath).isFile()) { + const result = options?.pathOnly + ? fullPath + : readFileSync(fullPath, 'utf-8') + if (!options?.predicate || options.predicate(result)) { + return result + } + } + } + + const parentDir = dirname(dir) + if ( + parentDir !== dir && + (!options?.rootDir || parentDir.startsWith(options?.rootDir)) + ) + return lookupFile(parentDir, formats, options) + + return undefined +} diff --git a/packages/cli/src/utils/packages.test.ts b/packages/cli/src/utils/packages.test.ts new file mode 100644 index 0000000000..96ea2e26ef --- /dev/null +++ b/packages/cli/src/utils/packages.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'vitest' + +import { getIsPackageInstalled, getPackageManager } from './packages.js' + +test('getIsPackageInstalled: true', async () => { + await expect(getIsPackageInstalled({ packageName: 'vitest' })).resolves.toBe( + true, + ) +}) + +test('getIsPackageInstalled: false', async () => { + await expect( + getIsPackageInstalled({ packageName: 'vitest-unknown' }), + ).resolves.toBe(false) +}) + +test('getPackageManager', async () => { + await expect(getPackageManager()).resolves.toMatchInlineSnapshot('"pnpm"') +}) diff --git a/packages/cli/src/utils/packages.ts b/packages/cli/src/utils/packages.ts new file mode 100644 index 0000000000..e55eeaf0ea --- /dev/null +++ b/packages/cli/src/utils/packages.ts @@ -0,0 +1,124 @@ +import { execSync } from 'node:child_process' +import { promises as fs } from 'node:fs' +import { resolve } from 'node:path' + +export async function getIsPackageInstalled(parameters: { + packageName: string + cwd?: string +}) { + const { packageName, cwd = process.cwd() } = parameters + try { + const packageManager = await getPackageManager() + const command = (() => { + switch (packageManager) { + case 'yarn': + return ['why', packageName] + case 'bun': + return ['pm', 'ls', '--all'] + default: + return ['ls', packageName] + } + })() + + const result = execSync(`${packageManager} ${command.join(' ')}`, { + cwd, + encoding: 'utf-8', + stdio: 'pipe', + }) + + // For Bun, we need to check if the package name is in the output + if (packageManager === 'bun') return result.includes(packageName) + + return result !== '' + } catch (_error) { + return false + } +} + +export async function getPackageManager(executable?: boolean | undefined) { + const userAgent = process.env.npm_config_user_agent + if (userAgent) { + if (userAgent.includes('pnpm')) return 'pnpm' + // The yarn@^3 user agent includes npm, so yarn must be checked first. + if (userAgent.includes('yarn')) return 'yarn' + if (userAgent.includes('npm')) return executable ? 'npx' : 'npm' + if (userAgent.includes('bun')) return executable ? 'bunx' : 'bun' + } + + const packageManager = await detect() + if (packageManager === 'npm' && executable) return 'npx' + return packageManager +} + +type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' + +async function detect( + parameters: { cwd?: string; includeGlobalBun?: boolean } = {}, +) { + const { cwd, includeGlobalBun } = parameters + const type = await getTypeofLockFile(cwd) + if (type) { + return type + } + const [hasYarn, hasPnpm, hasBun] = await Promise.all([ + hasGlobalInstallation('yarn'), + hasGlobalInstallation('pnpm'), + includeGlobalBun && hasGlobalInstallation('bun'), + ]) + if (hasYarn) return 'yarn' + if (hasPnpm) return 'pnpm' + if (hasBun) return 'bun' + return 'npm' +} + +const cache = new Map() + +function hasGlobalInstallation(pm: PackageManager): boolean { + const key = `has_global_${pm}` + if (cache.has(key)) return cache.get(key) + + try { + const result = execSync(`${pm} --version`, { + encoding: 'utf-8', + stdio: 'pipe', + }) + const isGlobal = /^\d+.\d+.\d+$/.test(result) + cache.set(key, isGlobal) + return isGlobal + } catch { + return false + } +} + +function getTypeofLockFile(cwd = '.'): Promise { + const key = `lockfile_${cwd}` + if (cache.has(key)) { + return Promise.resolve(cache.get(key)) + } + + return Promise.all([ + pathExists(resolve(cwd, 'yarn.lock')), + pathExists(resolve(cwd, 'package-lock.json')), + pathExists(resolve(cwd, 'pnpm-lock.yaml')), + pathExists(resolve(cwd, 'bun.lockb')), + ]).then(([isYarn, isNpm, isPnpm, isBun]) => { + let value: PackageManager | null = null + + if (isYarn) value = 'yarn' + else if (isPnpm) value = 'pnpm' + else if (isBun) value = 'bun' + else if (isNpm) value = 'npm' + + cache.set(key, value) + return value + }) +} + +async function pathExists(p: string) { + try { + await fs.access(p) + return true + } catch { + return false + } +} diff --git a/packages/cli/src/utils/resolveConfig.test.ts b/packages/cli/src/utils/resolveConfig.test.ts new file mode 100644 index 0000000000..6669edc9b6 --- /dev/null +++ b/packages/cli/src/utils/resolveConfig.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from 'vitest' + +import { createFixture } from '../../test/utils.js' +import { defaultConfig } from '../config.js' +import { findConfig } from './findConfig.js' +import { resolveConfig } from './resolveConfig.js' + +test.skip('resolves config', async () => { + const { paths } = await createFixture({ + files: { + 'wagmi.config.ts': ` + import { defineConfig } from '@wagmi/cli' + + export default defineConfig(${JSON.stringify(defaultConfig)}) + `, + }, + }) + + const configPath = await findConfig({ + config: paths['wagmi.config.ts'], + }) + await expect( + resolveConfig({ configPath: configPath! }), + ).resolves.toMatchInlineSnapshot(` + { + "contracts": [], + "out": "src/generated.ts", + "plugins": [], + } + `) +}) + +test.skip('resolves function config', async () => { + const { paths } = await createFixture({ + files: { + 'wagmi.config.ts': ` + import { defineConfig } from '@wagmi/cli' + + export default defineConfig(() => (${JSON.stringify(defaultConfig)})) + `, + }, + }) + + const configPath = await findConfig({ + config: paths['wagmi.config.ts'], + }) + await expect( + resolveConfig({ configPath: configPath! }), + ).resolves.toMatchInlineSnapshot(` + { + "contracts": [], + "out": "src/generated.ts", + "plugins": [], + } + `) +}) + +test.skip('resolves array config', async () => { + const { paths } = await createFixture({ + files: { + 'wagmi.config.ts': ` + import { defineConfig } from '@wagmi/cli' + + export default defineConfig([${JSON.stringify(defaultConfig)}]) + `, + }, + }) + + const configPath = await findConfig({ + config: paths['wagmi.config.ts'], + }) + await expect( + resolveConfig({ configPath: configPath! }), + ).resolves.toMatchInlineSnapshot(` + [ + { + "contracts": [], + "out": "src/generated.ts", + "plugins": [], + }, + ] + `) +}) diff --git a/packages/cli/src/utils/resolveConfig.ts b/packages/cli/src/utils/resolveConfig.ts new file mode 100644 index 0000000000..048b1c337f --- /dev/null +++ b/packages/cli/src/utils/resolveConfig.ts @@ -0,0 +1,21 @@ +import { bundleRequire } from 'bundle-require' + +import type { Config } from '../config.js' +import type { MaybeArray } from '../types.js' + +type ResolveConfigParameters = { + /** Path to config file */ + configPath: string +} + +/** Bundles and returns wagmi config object from path. */ +export async function resolveConfig( + parameters: ResolveConfigParameters, +): Promise> { + const { configPath } = parameters + const res = await bundleRequire({ filepath: configPath }) + let config = res.mod.default + if (config.default) config = config.default + if (typeof config !== 'function') return config + return await config() +} diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts new file mode 100644 index 0000000000..166b988ccb --- /dev/null +++ b/packages/cli/src/version.ts @@ -0,0 +1 @@ +export const version = '2.3.1' diff --git a/packages/cli/test/constants.ts b/packages/cli/test/constants.ts new file mode 100644 index 0000000000..9b84c5bb1b --- /dev/null +++ b/packages/cli/test/constants.ts @@ -0,0 +1,32 @@ +import { parseAbi } from 'viem' + +export const wagmiAbi = parseAbi([ + 'constructor()', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', + 'event ApprovalForAll(address indexed owner, address indexed operator, bool approved)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'function approve(address to, uint256 tokenId)', + 'function balanceOf(address owner) view returns (uint256)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function mint()', + 'function name() view returns (string)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)', + 'function setApprovalForAll(address operator, bool approved)', + 'function supportsInterface(bytes4 interfaceId) view returns (bool)', + 'function symbol() view returns (string)', + 'function tokenURI(uint256 tokenId) pure returns (string)', + 'function totalSupply() view returns (uint256)', + 'function transferFrom(address from, address to, uint256 tokenId)', +]) + +export const depositAbi = parseAbi([ + 'constructor()', + 'event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index)', + 'function deposit(bytes pubkey, bytes withdrawal_credentials, bytes signature, bytes32 deposit_data_root) payable', + 'function get_deposit_count() view returns (bytes)', + 'function get_deposit_root() view returns (bytes32)', + 'function supportsInterface(bytes4 interfaceId) pure returns (bool)', +]) diff --git a/packages/cli/test/setup.ts b/packages/cli/test/setup.ts new file mode 100644 index 0000000000..d2eed4a5e5 --- /dev/null +++ b/packages/cli/test/setup.ts @@ -0,0 +1,57 @@ +import { mkdir } from 'node:fs/promises' +import { homedir } from 'node:os' +import type { createSpinner as nanospinner_createSpinner } from 'nanospinner' +import { join } from 'pathe' +import { vi } from 'vitest' + +const cacheDir = join(homedir(), '.wagmi-cli/plugins/fetch/cache') +await mkdir(cacheDir, { recursive: true }) + +vi.mock('nanospinner', async (importOriginal) => { + const mod = await importOriginal<{ + createSpinner: typeof nanospinner_createSpinner + }>() + + function createSpinner( + initialText: string, + opts: Parameters[1], + ) { + let currentText = '' + const spinner = mod.createSpinner(initialText, opts) + return { + ...spinner, + start(text = initialText) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(`- ${text}`) + spinner.start(text) + currentText = text + }, + success(text = currentText) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(`√ ${text}`) + spinner.success(text) + }, + error(text = currentText) { + console.error(`× ${text}`) + spinner.error(text) + }, + } + } + return { createSpinner } +}) + +vi.mock('picocolors', async () => { + function pass(input: string | number | null | undefined) { + return input + } + return { + default: { + blue: pass, + gray: pass, + green: pass, + red: pass, + white: pass, + yellow: pass, + }, + } +}) diff --git a/packages/cli/test/utils.ts b/packages/cli/test/utils.ts new file mode 100644 index 0000000000..4ea6c6051e --- /dev/null +++ b/packages/cli/test/utils.ts @@ -0,0 +1,292 @@ +import { spawnSync } from 'node:child_process' +import { cp, mkdir, symlink, writeFile } from 'node:fs/promises' +import fixtures from 'fixturez' +import { http, HttpResponse } from 'msw' +import * as path from 'pathe' +import { vi } from 'vitest' + +const f = fixtures(__dirname) + +type Json = + | string + | number + | boolean + | null + | { [property: string]: Json } + | Json[] + +export async function createFixture< + TFiles extends { [filename: string]: string | Json } & { + tsconfig?: true + }, +>( + config: { + copyNodeModules?: boolean + dir?: string + files?: TFiles + } = {}, +) { + const dir = config.dir ?? f.temp() + await mkdir(dir, { recursive: true }) + + // Create test files + const paths: { [_ in keyof TFiles]: string } = {} as any + await Promise.all( + (Object.keys(config.files ?? {}) as (keyof TFiles)[]).map( + async (filename_) => { + let file: Json | true | undefined + let filename = filename_ + if (filename === 'tsconfig') { + filename = 'tsconfig.json' + file = getTsConfig(dir) + } else file = config.files![filename] + + const filePath = path.join(dir, filename.toString()) + await mkdir(path.dirname(filePath), { recursive: true }) + + await writeFile( + filePath, + typeof file === 'string' ? file : JSON.stringify(file, null, 2), + ) + paths[filename === 'tsconfig.json' ? 'tsconfig' : filename] = filePath + }, + ), + ) + + if (config.copyNodeModules) { + await symlink( + path.join(__dirname, '../node_modules'), + path.join(dir, 'node_modules'), + 'dir', + ) + await cp( + path.join(__dirname, '../package.json'), + path.join(dir, 'package.json'), + ) + } + + return { + dir, + paths: paths, + } +} + +type TsConfig = { + compilerOptions: { [property: string]: any } + exclude: string[] + include: string[] +} +function getTsConfig(baseUrl: string) { + return { + compilerOptions: { + allowJs: true, + baseUrl: '.', + esModuleInterop: true, + forceConsistentCasingInFileNames: true, + incremental: true, + isolatedModules: true, + jsx: 'preserve', + lib: ['dom', 'dom.iterable', 'esnext'], + module: 'esnext', + moduleResolution: 'node', + noEmit: true, + paths: { + '@wagmi/cli': [path.relative(baseUrl, 'packages/cli/src')], + '@wagmi/cli/*': [path.relative(baseUrl, 'packages/cli/src/*')], + '@wagmi/connectors': [ + path.relative(baseUrl, 'packages/connectors/src'), + ], + '@wagmi/connectors/*': [ + path.relative(baseUrl, 'packages/connectors/src/*'), + ], + '@wagmi/core': [path.relative(baseUrl, 'packages/core/src')], + '@wagmi/core/*': [path.relative(baseUrl, 'packages/core/src/*')], + wagmi: [path.relative(baseUrl, 'packages/react/src')], + 'wagmi/*': [path.relative(baseUrl, 'packages/react/src/*')], + }, + resolveJsonModule: true, + skipLibCheck: true, + strict: true, + target: 'es6', + }, + include: [`${baseUrl}/**/*.ts`, `${baseUrl}/**/*.tsx`], + exclude: ['node_modules'], + } as TsConfig +} + +export function watchConsole() { + type Console = 'info' | 'log' | 'warn' | 'error' + const output: { [_ in Console | 'all']: string[] } = { + info: [], + log: [], + warn: [], + error: [], + all: [], + } + function handleOutput(method: Console) { + return (message: string) => { + output[method].push(message) + output.all.push(message) + } + } + return { + debug: console.debug, + info: vi.spyOn(console, 'info').mockImplementation(handleOutput('info')), + log: vi.spyOn(console, 'log').mockImplementation(handleOutput('log')), + warn: vi.spyOn(console, 'warn').mockImplementation(handleOutput('warn')), + error: vi.spyOn(console, 'error').mockImplementation(handleOutput('error')), + output, + get formatted() { + return output.all.join('\n') + }, + } +} + +export async function typecheck(project: string) { + try { + const result = spawnSync( + 'tsc', + ['--noEmit', '--target', 'es2021', '--pretty', 'false', '-p', project], + { + encoding: 'utf-8', + stdio: 'pipe', + }, + ) + if (result.error) throw result.error + if (result.status !== 0) + throw new Error(`Failed with code ${result.status}`) + if (result.signal) throw new Error('Process terminated by signal') + return result.stdout + } catch (error) { + throw new Error( + (error as Error).message.replaceAll( + path.dirname(project), + '/path/to/project', + ), + ) + } +} + +export const baseUrl = 'https://api.etherscan.io/v2/api' +export const apiKey = 'abc' +export const invalidApiKey = 'xyz' +export const address = '0xaf0326d92b97df1221759476b072abfd8084f9be' +export const proxyAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' +export const implementationAddress = + '0x43506849d7c04f9138d1a2050bbf3a0c054402dd' +export const unverifiedContractAddress = + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e' +export const timeoutAddress = '0xecb504d39723b0be0e3a9aa33d646642d1051ee1' + +export const handlers = [ + http.get(baseUrl, async ({ request }) => { + const url = new URL(request.url) + const search = url.search.replace(/^\?chainId=\d&/, '?') + + if ( + search === + `?module=contract&action=getabi&address=${unverifiedContractAddress}&apikey=${apiKey}` + ) + return HttpResponse.json({ + status: '0', + message: 'NOTOK', + result: 'Contract source code not verified', + }) + + if ( + search === + `?module=contract&action=getabi&address=${timeoutAddress}&apikey=${invalidApiKey}` + ) + return HttpResponse.json({ + status: '0', + message: 'NOTOK', + result: 'Invalid API Key', + }) + + if ( + search === + `?module=contract&action=getabi&address=${address}&apikey=${apiKey}` + ) + return HttpResponse.json({ + status: '1', + message: 'OK', + result: + '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]', + }) + + if ( + search === + `?module=contract&action=getabi&address=${timeoutAddress}&apikey=${apiKey}` + ) { + await new Promise((resolve) => setTimeout(resolve, 10_000)) + return HttpResponse.json({}) + } + + if ( + search === + `?module=contract&action=getabi&address=${implementationAddress}&apikey=${apiKey}` + ) + return HttpResponse.json({ + status: '1', + message: 'OK', + result: + '[{"constant":false,"inputs":[{"name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newImplementation","type":"address"},{"name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newAdmin","type":"address"}],"name":"changeAdmin","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_implementation","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"previousAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"implementation","type":"address"}],"name":"Upgraded","type":"event"}]', + }) + + if ( + search === + `?module=contract&action=getsourcecode&address=${proxyAddress}&apikey=${apiKey}` + ) + return HttpResponse.json({ + status: '1', + message: 'OK', + result: [ + { + SourceCode: '...', + ABI: '[{"constant":false,"inputs":[{"name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newImplementation","type":"address"},{"name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newAdmin","type":"address"}],"name":"changeAdmin","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_implementation","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"previousAdmin","type":"address"},{"indexed":false,"name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"implementation","type":"address"}],"name":"Upgraded","type":"event"}]', + ContractName: 'FiatTokenProxy', + CompilerVersion: 'v0.4.24+commit.e67f0147', + OptimizationUsed: '0', + Runs: '200', + ConstructorArguments: + '0000000000000000000000000882477e7895bdc5cea7cb1552ed914ab157fe56', + EVMVersion: 'Default', + Library: '', + LicenseType: '', + Proxy: '1', + Implementation: '0x43506849d7c04f9138d1a2050bbf3a0c054402dd', + SwarmSource: + 'bzzr://a4a547cfc7202c5acaaae74d428e988bc62ad5024eb0165532d3a8f91db4ed24', + }, + ], + }) + + if ( + search === + `?module=contract&action=getsourcecode&address=${address}&apikey=${apiKey}` + ) + return HttpResponse.json({ + status: '1', + message: 'OK', + result: [ + { + SourceCode: '...', + ABI: '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]', + ContractName: 'WagmiMintExample', + CompilerVersion: 'v0.8.11+commit.d7f03943', + OptimizationUsed: '1', + Runs: '10000', + ConstructorArguments: '', + EVMVersion: 'Default', + Library: '', + LicenseType: '', + Proxy: '0', + Implementation: '', + SwarmSource: '', + }, + ], + }) + + throw new Error(`Unhandled request: ${search}`) + }), +] diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json new file mode 100644 index 0000000000..3a046d812b --- /dev/null +++ b/packages/cli/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"], + "compilerOptions": { + "sourceMap": true, + "types": ["@types/node"] + } +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000000..4054dee118 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts", "test/**/*.ts", "types/**/*.d.ts"], + "exclude": [] +} diff --git a/packages/cli/types/fixturez.d.ts b/packages/cli/types/fixturez.d.ts new file mode 100644 index 0000000000..f96282756f --- /dev/null +++ b/packages/cli/types/fixturez.d.ts @@ -0,0 +1,18 @@ +declare module 'fixturez' { + interface FixturezOpts { + glob?: string + cleanup?: boolean + root?: string + } + + interface Fixturez { + find(basename: string): string + copy(basename: string): string + temp(): string + cleanup(): void + } + + function fixturez(dirname: string, opts?: FixturezOpts): Fixturez + + export = fixturez +} diff --git a/packages/connectors/CHANGELOG.md b/packages/connectors/CHANGELOG.md new file mode 100644 index 0000000000..c67e25dcb0 --- /dev/null +++ b/packages/connectors/CHANGELOG.md @@ -0,0 +1,1640 @@ +# @wagmi/connectors + +## 5.8.3 + +### Patch Changes + +- [#4660](https://github.com/wevm/wagmi/pull/4660) [`42b1fed58e9ac09da0f8ebf3e9271f98a707aaac`](https://github.com/wevm/wagmi/commit/42b1fed58e9ac09da0f8ebf3e9271f98a707aaac) Thanks [@ganchoradkov](https://github.com/ganchoradkov)! - Updated `@walletconnect/ethereum-provider` version to `2.20.2` + +## 5.8.2 + +### Patch Changes + +- Updated dependencies [[`29297a48af72b537173d948ccd2fe37d39914c66`](https://github.com/wevm/wagmi/commit/29297a48af72b537173d948ccd2fe37d39914c66), [`07370106d5fb6b8fe300992d93abf25b3d0eaf57`](https://github.com/wevm/wagmi/commit/07370106d5fb6b8fe300992d93abf25b3d0eaf57)]: + - @wagmi/core@2.17.2 + +## 5.8.1 + +### Patch Changes + +- Updated dependencies [[`01f64e64fa4f85cdd30023903f972f4f9023681f`](https://github.com/wevm/wagmi/commit/01f64e64fa4f85cdd30023903f972f4f9023681f)]: + - @wagmi/core@2.17.1 + +## 5.8.0 + +### Minor Changes + +- [#4644](https://github.com/wevm/wagmi/pull/4644) [`cc5517ff6880bb630f1b201930acc20dd1a0b451`](https://github.com/wevm/wagmi/commit/cc5517ff6880bb630f1b201930acc20dd1a0b451) Thanks [@lukaisailovic](https://github.com/lukaisailovic)! - Updated `@walletconnect/etherereum-provider` to `2.20.0`. + +## 5.7.13 + +### Patch Changes + +- [#4622](https://github.com/wevm/wagmi/pull/4622) [`88427b2bcd13ec375ef519e9ad1ccffef9f02a7b`](https://github.com/wevm/wagmi/commit/88427b2bcd13ec375ef519e9ad1ccffef9f02a7b) Thanks [@dan1kov](https://github.com/dan1kov)! - Added `rdns` property to Coinbase Wallet v3 connector + +- [#4605](https://github.com/wevm/wagmi/pull/4605) [`3f8b2edc4f237cccff1009bcef03d51ca27a7324`](https://github.com/wevm/wagmi/commit/3f8b2edc4f237cccff1009bcef03d51ca27a7324) Thanks [@chybisov](https://github.com/chybisov)! - Bumped `@safe-global/safe-apps-provider` version to `0.18.6`. + +- Updated dependencies [[`799ee4d4b23c2ecd64e3f3668e67634e81939719`](https://github.com/wevm/wagmi/commit/799ee4d4b23c2ecd64e3f3668e67634e81939719)]: + - @wagmi/core@2.17.0 + +## 5.7.12 + +### Patch Changes + +- [#4608](https://github.com/wevm/wagmi/pull/4608) [`b59c024b23c69f5459b17390531207cfdf126ce4`](https://github.com/wevm/wagmi/commit/b59c024b23c69f5459b17390531207cfdf126ce4) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +## 5.7.11 + +### Patch Changes + +- Updated dependencies [[`a4bd0623eed28e3761a27295831a60ad835f0ee0`](https://github.com/wevm/wagmi/commit/a4bd0623eed28e3761a27295831a60ad835f0ee0)]: + - @wagmi/core@2.16.7 + +## 5.7.10 + +### Patch Changes + +- [#4573](https://github.com/wevm/wagmi/pull/4573) [`e944812ebc234a72c1417b77cff341166f5e0fef`](https://github.com/wevm/wagmi/commit/e944812ebc234a72c1417b77cff341166f5e0fef) Thanks [@ganchoradkov](https://github.com/ganchoradkov)! - updated `@walletconnect/ethereum-provider` to `2.19.1` + +- Updated dependencies [[`edf47477b2f6385a1c3ae01d36a8498c47f30a0b`](https://github.com/wevm/wagmi/commit/edf47477b2f6385a1c3ae01d36a8498c47f30a0b)]: + - @wagmi/core@2.16.6 + +## 5.7.9 + +### Patch Changes + +- [#4571](https://github.com/wevm/wagmi/pull/4571) [`5b7101fddb61df56e34b2e02b46bc409e496eaf9`](https://github.com/wevm/wagmi/commit/5b7101fddb61df56e34b2e02b46bc409e496eaf9) Thanks [@ganchoradkov](https://github.com/ganchoradkov)! - Updated `@walletconnect/ethereum-provider` to `2.19.0` + +## 5.7.8 + +### Patch Changes + +- Updated dependencies [[`d0c9a86921a4e939373cc6e763284e53f2a2e93c`](https://github.com/wevm/wagmi/commit/d0c9a86921a4e939373cc6e763284e53f2a2e93c)]: + - @wagmi/core@2.16.5 + +## 5.7.7 + +### Patch Changes + +- [`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec) Thanks [@jxom](https://github.com/jxom)! - Updated `@coinbase/wallet-sdk`. + +- Updated dependencies [[`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec)]: + - @wagmi/core@2.16.4 + +## 5.7.6 + +### Patch Changes + +- [#4524](https://github.com/wevm/wagmi/pull/4524) [`639952c97f0fe3927106f42d3c9f7f366cdf7f7a`](https://github.com/wevm/wagmi/commit/639952c97f0fe3927106f42d3c9f7f366cdf7f7a) Thanks [@chakra-guy](https://github.com/chakra-guy)! - Updated MetaMask SDK. + +- [#4525](https://github.com/wevm/wagmi/pull/4525) [`5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766`](https://github.com/wevm/wagmi/commit/5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Added Phantom flag to Injected Connector. + +## 5.7.5 + +### Patch Changes + +- [#4512](https://github.com/wevm/wagmi/pull/4512) [`a257e8d4f97431a4af872cda1817b4ae17c7bbed`](https://github.com/wevm/wagmi/commit/a257e8d4f97431a4af872cda1817b4ae17c7bbed) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Fixed MetaMask switchChain/addChain handling. + +## 5.7.4 + +### Patch Changes + +- [#4505](https://github.com/wevm/wagmi/pull/4505) [`c8a257e0f6d2ece013b873895c35769a8a804fdc`](https://github.com/wevm/wagmi/commit/c8a257e0f6d2ece013b873895c35769a8a804fdc) Thanks [@chakra-guy](https://github.com/chakra-guy)! - Bumped Metamask SDK Version (changes include [bug fixes and minor changes](https://github.com/MetaMask/metamask-sdk/pull/1194)). + +## 5.7.3 + +### Patch Changes + +- [#4480](https://github.com/wevm/wagmi/pull/4480) [`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8) Thanks [@RodeRickIsWatching](https://github.com/RodeRickIsWatching)! - Fixed invocation of default storage. + +- Updated dependencies [[`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8)]: + - @wagmi/core@2.16.3 + +## 5.7.2 + +### Patch Changes + +- [`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6) Thanks [@jxom](https://github.com/jxom)! - Fixed assignment in `getDefaultStorage`. + +- Updated dependencies [[`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6)]: + - @wagmi/core@2.16.2 + +## 5.7.1 + +### Patch Changes + +- [#4471](https://github.com/wevm/wagmi/pull/4471) [`9c8c35a3b829f2c58edcd3a29e2dcd99974d7470`](https://github.com/wevm/wagmi/commit/9c8c35a3b829f2c58edcd3a29e2dcd99974d7470) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Improved MetaMask chain switching behavior. + +- Updated dependencies [[`3892ebd21c06beef4b28ece4e70d2a38807bce6f`](https://github.com/wevm/wagmi/commit/3892ebd21c06beef4b28ece4e70d2a38807bce6f)]: + - @wagmi/core@2.16.1 + +## 5.7.0 + +### Minor Changes + +- [#4440](https://github.com/wevm/wagmi/pull/4440) [`e3f63a02c1f7d80481804584f262bc98dab0400d`](https://github.com/wevm/wagmi/commit/e3f63a02c1f7d80481804584f262bc98dab0400d) Thanks [@johanneskares](https://github.com/johanneskares)! - Added Coinbase Smart Wallet "Instant Onboarding" mode to `coinbaseWallet`. + +## 5.6.2 + +### Patch Changes + +- [#4437](https://github.com/wevm/wagmi/pull/4437) [`adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85`](https://github.com/wevm/wagmi/commit/adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85) Thanks [@chybisov](https://github.com/chybisov)! - Bumped `@safe-global/safe-apps-provider` version to `0.18.5`. + +## 5.6.1 + +### Patch Changes + +- [#4458](https://github.com/wevm/wagmi/pull/4458) [`987404f590c1d29ebb3cb68928f5e54aa032793d`](https://github.com/wevm/wagmi/commit/987404f590c1d29ebb3cb68928f5e54aa032793d) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Fixed MetaMask internal metadata handling. + +## 5.6.0 + +### Minor Changes + +- [#4453](https://github.com/wevm/wagmi/pull/4453) [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227) Thanks [@tmm](https://github.com/tmm)! - Added narrowing to `config.connectors`. + +### Patch Changes + +- [#4456](https://github.com/wevm/wagmi/pull/4456) [`8b0726c1106fce88b782e676498eabf0718b2619`](https://github.com/wevm/wagmi/commit/8b0726c1106fce88b782e676498eabf0718b2619) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Bumped MetaMask SDK and fixed internal metadata handling. +- Updated dependencies [[`afea6b67822a7a2b96901ec851441d27ee0f7a52`](https://github.com/wevm/wagmi/commit/afea6b67822a7a2b96901ec851441d27ee0f7a52), [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227)]: + - @wagmi/core@2.16.0 + +## 5.5.3 + +### Patch Changes + +- [#4433](https://github.com/wevm/wagmi/pull/4433) [`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc) Thanks [@Aerilym](https://github.com/Aerilym)! - Bumped Metamask SDK version to `0.31.1`. + +- Updated dependencies [[`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc)]: + - @wagmi/core@2.15.2 + +## 5.5.2 + +### Patch Changes + +- [#4422](https://github.com/wevm/wagmi/pull/4422) [`e563ef69130a511fd6f3f72ed4cd4fbe1390541f`](https://github.com/wevm/wagmi/commit/e563ef69130a511fd6f3f72ed4cd4fbe1390541f) Thanks [@abretonc7s](https://github.com/abretonc7s)! - Bumped MetaMask SDK. + +## 5.5.1 + +### Patch Changes + +- Updated dependencies [[`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693)]: + - @wagmi/core@2.15.1 + +## 5.5.0 + +### Minor Changes + +- [#4417](https://github.com/wevm/wagmi/pull/4417) [`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141) Thanks [@jxom](https://github.com/jxom)! - Removed simulation in `writeContract` & `sendTransaction`. + +### Patch Changes + +- Updated dependencies [[`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141)]: + - @wagmi/core@2.15.0 + +## 5.4.0 + +### Minor Changes + +- [#4409](https://github.com/wevm/wagmi/pull/4409) [`7ca62b44cd997d48f92c2b81343726a5908aa00b`](https://github.com/wevm/wagmi/commit/7ca62b44cd997d48f92c2b81343726a5908aa00b) Thanks [@fan-zhang-sv](https://github.com/fan-zhang-sv)! - Added `preference` object for Coinbase Wallet connector. + +## 5.3.10 + +### Patch Changes + +- [#4406](https://github.com/wevm/wagmi/pull/4406) [`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3) Thanks [@tmm](https://github.com/tmm)! - Added additional RDNS to MetaMask Connector. + +- Updated dependencies [[`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3)]: + - @wagmi/core@2.14.6 + +## 5.3.9 + +### Patch Changes + +- [`b12a04eeec985c48d2feac94b011d41fb29ca23e`](https://github.com/wevm/wagmi/commit/b12a04eeec985c48d2feac94b011d41fb29ca23e) Thanks [@tmm](https://github.com/tmm)! - Bumped Coinbase Wallet SDK version. + +## 5.3.8 + +### Patch Changes + +- [#4390](https://github.com/wevm/wagmi/pull/4390) [`dac62dc99a0679fa632a0fae49873d6053d06b35`](https://github.com/wevm/wagmi/commit/dac62dc99a0679fa632a0fae49873d6053d06b35) Thanks [@chybisov](https://github.com/chybisov)! - Bumped Safe Apps Provider version. + +- Updated dependencies [[`6b9bbacdc7bffd44fc2165362a5e65fd434e7646`](https://github.com/wevm/wagmi/commit/6b9bbacdc7bffd44fc2165362a5e65fd434e7646)]: + - @wagmi/core@2.14.5 + +## 5.3.7 + +### Patch Changes + +- Updated dependencies [[`e08681c81fbdf475213e2d0f4c5517d0abf4e743`](https://github.com/wevm/wagmi/commit/e08681c81fbdf475213e2d0f4c5517d0abf4e743)]: + - @wagmi/core@2.14.4 + +## 5.3.6 + +### Patch Changes + +- [#4385](https://github.com/wevm/wagmi/pull/4385) [`7558ff3133c11bc4c49473d08ee9a47eaa12df5b`](https://github.com/wevm/wagmi/commit/7558ff3133c11bc4c49473d08ee9a47eaa12df5b) Thanks [@cb-jake](https://github.com/cb-jake)! - Bumped Coinbase Wallet SDK version. + +## 5.3.5 + +### Patch Changes + +- [`7fe78f2d09778fc01fd0cffe85ba198e64999275`](https://github.com/wevm/wagmi/commit/7fe78f2d09778fc01fd0cffe85ba198e64999275) Thanks [@tmm](https://github.com/tmm)! - Fixed MetaMask connector not returning provider in some cases. + +- Updated dependencies [[`cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7`](https://github.com/wevm/wagmi/commit/cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7)]: + - @wagmi/core@2.14.3 + +## 5.3.4 + +### Patch Changes + +- [#4371](https://github.com/wevm/wagmi/pull/4371) [`b6861a4c378dab78d8751ae0ac2aa425f3c24b8f`](https://github.com/wevm/wagmi/commit/b6861a4c378dab78d8751ae0ac2aa425f3c24b8f) Thanks [@iceanddust](https://github.com/iceanddust)! - Fixed Safe connector not working in some Vite apps + +- Updated dependencies [[`d0d0963bb5904a15cf0355862d62dd141ce0c31c`](https://github.com/wevm/wagmi/commit/d0d0963bb5904a15cf0355862d62dd141ce0c31c), [`ecac0ba36243d94c9199d0bd21937104c835d9a0`](https://github.com/wevm/wagmi/commit/ecac0ba36243d94c9199d0bd21937104c835d9a0)]: + - @wagmi/core@2.14.2 + +## 5.3.3 + +### Patch Changes + +- [#4362](https://github.com/wevm/wagmi/pull/4362) [`83c6d16b7d6dddfa6bda036e04f00ec313c6248c`](https://github.com/wevm/wagmi/commit/83c6d16b7d6dddfa6bda036e04f00ec313c6248c) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Fixed MetaMask connector internal logic. + +## 5.3.2 + +### Patch Changes + +- [#4357](https://github.com/wevm/wagmi/pull/4357) [`8970cc51398e1ac713435533096215c6d31ffdf9`](https://github.com/wevm/wagmi/commit/8970cc51398e1ac713435533096215c6d31ffdf9) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +## 5.3.1 + +### Patch Changes + +- Updated dependencies [[`052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702`](https://github.com/wevm/wagmi/commit/052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702), [`b250fc21ee577b2a75c5a34ff684f62fb4ad771a`](https://github.com/wevm/wagmi/commit/b250fc21ee577b2a75c5a34ff684f62fb4ad771a)]: + - @wagmi/core@2.14.1 + +## 5.3.0 + +### Minor Changes + +- [#4343](https://github.com/wevm/wagmi/pull/4343) [`f43e074f473820b208a6295d7c97f847332f1a1d`](https://github.com/wevm/wagmi/commit/f43e074f473820b208a6295d7c97f847332f1a1d) Thanks [@tmm](https://github.com/tmm)! - Added `rdns` property to connector interface. This is used to filter out duplicate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) injected providers when [`createConfig#multiInjectedProviderDiscovery`](https://wagmi.sh/core/api/createConfig#multiinjectedproviderdiscovery) is enabled and `createConfig#connectors` already matches EIP-6963 providers' `rdns` property. + +### Patch Changes + +- Updated dependencies [[`f43e074f473820b208a6295d7c97f847332f1a1d`](https://github.com/wevm/wagmi/commit/f43e074f473820b208a6295d7c97f847332f1a1d)]: + - @wagmi/core@2.14.0 + +## 5.2.2 + +### Patch Changes + +- [#4347](https://github.com/wevm/wagmi/pull/4347) [`5ae49af590ff168426c9c283d54c34ae5148fcd9`](https://github.com/wevm/wagmi/commit/5ae49af590ff168426c9c283d54c34ae5148fcd9) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Added workaround for MetaMask mobile sometimes disconnecting. + +- [#4350](https://github.com/wevm/wagmi/pull/4350) [`f3182b22e6e454d9bd74f1b940ef34431fd9555d`](https://github.com/wevm/wagmi/commit/f3182b22e6e454d9bd74f1b940ef34431fd9555d) Thanks [@abretonc7s](https://github.com/abretonc7s)! - Updated MetaMask SDK. + +- Updated dependencies [[`c05caabc20c3ced9682cfc7ba1f3f7dcfece0703`](https://github.com/wevm/wagmi/commit/c05caabc20c3ced9682cfc7ba1f3f7dcfece0703)]: + - @wagmi/core@2.13.9 + +## 5.2.1 + +### Patch Changes + +- [#4345](https://github.com/wevm/wagmi/pull/4345) [`91a40f2db08e3a91db421b8732a5511a1e6c88fd`](https://github.com/wevm/wagmi/commit/91a40f2db08e3a91db421b8732a5511a1e6c88fd) Thanks [@tmm](https://github.com/tmm)! - Bumped MetaMask SDK. + +## 5.2.0 + +### Minor Changes + +- [#4337](https://github.com/wevm/wagmi/pull/4337) [`34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4`](https://github.com/wevm/wagmi/commit/34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4) Thanks [@tmm](https://github.com/tmm)! - Added "Connect and Sign" behavior to MetaMask Connector. + +## 5.1.15 + +### Patch Changes + +- [`3b2123664b7ac66848390739e855c3b9702ab60c`](https://github.com/wevm/wagmi/commit/3b2123664b7ac66848390739e855c3b9702ab60c) Thanks [@tmm](https://github.com/tmm)! - Bumped WalletConnect Provider. + +## 5.1.14 + +### Patch Changes + +- [#4207](https://github.com/wevm/wagmi/pull/4207) [`56f2482508f2ba71bd6b0295c70c6abca7101e57`](https://github.com/wevm/wagmi/commit/56f2482508f2ba71bd6b0295c70c6abca7101e57) Thanks [@Smert](https://github.com/Smert)! - Updated chain switch listener for `injected` and `metaMask` to be more robust. + +- Updated dependencies [[`56f2482508f2ba71bd6b0295c70c6abca7101e57`](https://github.com/wevm/wagmi/commit/56f2482508f2ba71bd6b0295c70c6abca7101e57)]: + - @wagmi/core@2.13.8 + +## 5.1.13 + +### Patch Changes + +- Updated dependencies [[`be75c2d4ef636d7362420ab0a106bfdf63f5d1e6`](https://github.com/wevm/wagmi/commit/be75c2d4ef636d7362420ab0a106bfdf63f5d1e6)]: + - @wagmi/core@2.13.7 + +## 5.1.12 + +### Patch Changes + +- Updated dependencies [[`edcbf5d6fbe92f639bead800502edda9e0aa39f1`](https://github.com/wevm/wagmi/commit/edcbf5d6fbe92f639bead800502edda9e0aa39f1)]: + - @wagmi/core@2.13.6 + +## 5.1.11 + +### Patch Changes + +- [#4271](https://github.com/wevm/wagmi/pull/4271) [`82404c960e04c83e0bae6e1e12459ef9debf9554`](https://github.com/wevm/wagmi/commit/82404c960e04c83e0bae6e1e12459ef9debf9554) Thanks [@omridan159](https://github.com/omridan159)! - Bumped MetaMask SDK. + +- [#4227](https://github.com/wevm/wagmi/pull/4227) [`d07ad7f63a018256908a673d078aaf79e47ac703`](https://github.com/wevm/wagmi/commit/d07ad7f63a018256908a673d078aaf79e47ac703) Thanks [@xianchenxc](https://github.com/xianchenxc)! - Fixed MetaMask Connector throwing error after switching to a chain that was just added via `'wallet_addEthereumChain'`. + +## 5.1.10 + +### Patch Changes + +- [#4255](https://github.com/wevm/wagmi/pull/4255) [`81de006e66121a18c61945c1f9b8426c83a5713c`](https://github.com/wevm/wagmi/commit/81de006e66121a18c61945c1f9b8426c83a5713c) Thanks [@tomiir](https://github.com/tomiir)! - Bumped `@walletconnect/ethereum-provider` from version `2.15.3` to version `2.16.1`. + +- Updated dependencies [[`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb)]: + - @wagmi/core@2.13.5 + +## 5.1.9 + +### Patch Changes + +- [#4243](https://github.com/wevm/wagmi/pull/4243) [`21bd0e473d374cbbd7a01bececa6022d529026ba`](https://github.com/wevm/wagmi/commit/21bd0e473d374cbbd7a01bececa6022d529026ba) Thanks [@tomiir](https://github.com/tomiir)! - Bumped `@walletconnect/ethereum-provider` from version `2.15.2` to version `2.15.3` + +- [#4251](https://github.com/wevm/wagmi/pull/4251) [`5c89c6853e616437a3be2b019db895451fecfb3c`](https://github.com/wevm/wagmi/commit/5c89c6853e616437a3be2b019db895451fecfb3c) Thanks [@tmm](https://github.com/tmm)! - Bumped MM SDK. + +## 5.1.8 + +### Patch Changes + +- [`b580ad4edff1721e0b9d138cf5ae2ec74d2374c7`](https://github.com/wevm/wagmi/commit/b580ad4edff1721e0b9d138cf5ae2ec74d2374c7) Thanks [@tmm](https://github.com/tmm)! - Bumped WalletConnect Provider. + +## 5.1.7 + +### Patch Changes + +- [#4213](https://github.com/wevm/wagmi/pull/4213) [`91fd81a068789c5020e891f539bcad8f54a7a52f`](https://github.com/wevm/wagmi/commit/91fd81a068789c5020e891f539bcad8f54a7a52f) Thanks [@tomiir](https://github.com/tomiir)! - Updated `@walletconnect/ethereum-provider` from version `2.15.0` to version `2.15.1`. + +## 5.1.6 + +### Patch Changes + +- [#4208](https://github.com/wevm/wagmi/pull/4208) [`3168616298cbb6135d0ffda771cba4126e83eba8`](https://github.com/wevm/wagmi/commit/3168616298cbb6135d0ffda771cba4126e83eba8) Thanks [@tomiir](https://github.com/tomiir)! - Updated WalletConnect Ethereum Provider version from `2.14.0` to `2.15.0`. + +- [#4211](https://github.com/wevm/wagmi/pull/4211) [`d7608ef9a79459465dc8c06a2ab740465c881907`](https://github.com/wevm/wagmi/commit/d7608ef9a79459465dc8c06a2ab740465c881907) Thanks [@tmm](https://github.com/tmm)! - Added default name for MetaMask Connector. + +## 5.1.5 + +### Patch Changes + +- Updated dependencies [[`b4c8971788c70b09479946ecfa998cff2f1b3953`](https://github.com/wevm/wagmi/commit/b4c8971788c70b09479946ecfa998cff2f1b3953)]: + - @wagmi/core@2.13.4 + +## 5.1.4 + +### Patch Changes + +- Updated dependencies [[`871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4`](https://github.com/wevm/wagmi/commit/871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4)]: + - @wagmi/core@2.13.3 + +## 5.1.3 + +### Patch Changes + +- Updated dependencies [[`1b9b523fa9b9dfe839aecdf4b40caa9547d7e594`](https://github.com/wevm/wagmi/commit/1b9b523fa9b9dfe839aecdf4b40caa9547d7e594)]: + - @wagmi/core@2.13.2 + +## 5.1.2 + +### Patch Changes + +- [`abb490dac4f0f02f46cb0878e7ca9a0db6aada56`](https://github.com/wevm/wagmi/commit/abb490dac4f0f02f46cb0878e7ca9a0db6aada56) Thanks [@tmm](https://github.com/tmm)! - Bumped MetaMask SDK version. + +- [`28e0e5c9a4f856583f9d36a807502bd51a0c6ec2`](https://github.com/wevm/wagmi/commit/28e0e5c9a4f856583f9d36a807502bd51a0c6ec2) Thanks [@tmm](https://github.com/tmm)! - Bumped WalletConnect Ethereum Provider version. + +## 5.1.1 + +### Patch Changes + +- Updated dependencies [[`07c1227f306d0efb9421d4bb77a774f92f5fcf45`](https://github.com/wevm/wagmi/commit/07c1227f306d0efb9421d4bb77a774f92f5fcf45)]: + - @wagmi/core@2.13.1 + +## 5.1.0 + +### Minor Changes + +- [#4162](https://github.com/wevm/wagmi/pull/4162) [`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0) Thanks [@jxom](https://github.com/jxom)! - Added functionality for consumer-defined RPC URLs (`config.transports`) to be propagated to the WalletConnect & MetaMask Connectors. + +### Patch Changes + +- Updated dependencies [[`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0)]: + - @wagmi/core@2.13.0 + +## 5.0.26 + +### Patch Changes + +- [`8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5`](https://github.com/wevm/wagmi/commit/8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5) Thanks [@tmm](https://github.com/tmm)! - Fixed `metaMask` connector switch chain issue. + +- Updated dependencies [[`5bc8c8877810b2eec24a829df87dce40a51e6f20`](https://github.com/wevm/wagmi/commit/5bc8c8877810b2eec24a829df87dce40a51e6f20)]: + - @wagmi/core@2.12.2 + +## 5.0.25 + +### Patch Changes + +- [#4146](https://github.com/wevm/wagmi/pull/4146) [`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b) Thanks [@jxom](https://github.com/jxom)! - Updated `@safe-global/safe-apps-sdk` + `@safe-global/safe-apps-provider` dependencies. + +- Updated dependencies [[`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b)]: + - @wagmi/core@2.12.1 + +## 5.0.24 + +### Patch Changes + +- Updated dependencies [[`5581a810ef70308e99c6f8b630cd4bca59f64afc`](https://github.com/wevm/wagmi/commit/5581a810ef70308e99c6f8b630cd4bca59f64afc)]: + - @wagmi/core@2.12.0 + +## 5.0.23 + +### Patch Changes + +- [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d) Thanks [@jxom](https://github.com/jxom)! - Updated `mipd` dependency. + +- Updated dependencies [[`b08013eaa9ce97c02f8a7128ea400e3da7ef74bb`](https://github.com/wevm/wagmi/commit/b08013eaa9ce97c02f8a7128ea400e3da7ef74bb), [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d)]: + - @wagmi/core@2.11.8 + +## 5.0.22 + +### Patch Changes + +- [`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e) Thanks [@tmm](https://github.com/tmm)! - Improved TypeScript `'exactOptionalPropertyTypes'` support. + +- Updated dependencies [[`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e)]: + - @wagmi/core@2.11.7 + +## 5.0.21 + +### Patch Changes + +- [#4094](https://github.com/wevm/wagmi/pull/4094) [`ff0760b5900114bcfdf420a9fba3cc278ac95afe`](https://github.com/wevm/wagmi/commit/ff0760b5900114bcfdf420a9fba3cc278ac95afe) Thanks [@omridan159](https://github.com/omridan159)! - Bumped MetaMask SDK to fix `metaMask` connector error bubbling. + +- Updated dependencies [[`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad)]: + - @wagmi/core@2.11.6 + +## 5.0.20 + +### Patch Changes + +- [`43fa971d34cac57fa5a2898ad4d839b95d7af37c`](https://github.com/wevm/wagmi/commit/43fa971d34cac57fa5a2898ad4d839b95d7af37c) Thanks [@tmm](https://github.com/tmm)! - Bumped Coinbase Wallet SDK and fixed `metaMask` connector hang on mobile. + +## 5.0.19 + +### Patch Changes + +- [#4083](https://github.com/wevm/wagmi/pull/4083) [`b7ad208030d9f2e3f89912ff76b16cdbd848feda`](https://github.com/wevm/wagmi/commit/b7ad208030d9f2e3f89912ff76b16cdbd848feda) Thanks [@omridan159](https://github.com/omridan159)! - Bumped MetaMask SDK + +## 5.0.18 + +### Patch Changes + +- [#4081](https://github.com/wevm/wagmi/pull/4081) [`44d24620c9e3957f3245d14d6a042736371df70b`](https://github.com/wevm/wagmi/commit/44d24620c9e3957f3245d14d6a042736371df70b) Thanks [@tmm](https://github.com/tmm)! - Bumped MetaMask SDK + +## 5.0.17 + +### Patch Changes + +- Updated dependencies [[`04f2b846b113f3d300d82c9fa75212f1805817c5`](https://github.com/wevm/wagmi/commit/04f2b846b113f3d300d82c9fa75212f1805817c5)]: + - @wagmi/core@2.11.5 + +## 5.0.16 + +### Patch Changes + +- [#4071](https://github.com/wevm/wagmi/pull/4071) [`02c38c28d1aa0ad7a61c33775de603ed974c5c1b`](https://github.com/wevm/wagmi/commit/02c38c28d1aa0ad7a61c33775de603ed974c5c1b) Thanks [@omridan159](https://github.com/omridan159)! - Bumped MetaMask SDK + +- Updated dependencies [[`9e8345cd56186b997b5e56deaa2cfc69b30d15f6`](https://github.com/wevm/wagmi/commit/9e8345cd56186b997b5e56deaa2cfc69b30d15f6)]: + - @wagmi/core@2.11.4 + +## 5.0.15 + +### Patch Changes + +- Updated dependencies [[`8974e6269bb5d7bfaa90db0246bc7d13e8bff798`](https://github.com/wevm/wagmi/commit/8974e6269bb5d7bfaa90db0246bc7d13e8bff798)]: + - @wagmi/core@2.11.3 + +## 5.0.14 + +### Patch Changes + +- Updated dependencies [[`b4d9ef79deb554ee20fed6666a474be5e7cdd522`](https://github.com/wevm/wagmi/commit/b4d9ef79deb554ee20fed6666a474be5e7cdd522)]: + - @wagmi/core@2.11.2 + +## 5.0.13 + +### Patch Changes + +- [`9c862d8d63e3d692a22cef2a90782b74a9103f17`](https://github.com/wevm/wagmi/commit/9c862d8d63e3d692a22cef2a90782b74a9103f17) Thanks [@tmm](https://github.com/tmm)! - Reverted internal module loading utility. + +- Updated dependencies [[`9c862d8d63e3d692a22cef2a90782b74a9103f17`](https://github.com/wevm/wagmi/commit/9c862d8d63e3d692a22cef2a90782b74a9103f17)]: + - @wagmi/core@2.11.1 + +## 5.0.12 + +### Patch Changes + +- Updated dependencies [[`06bb598a7f04c7b167f5b7ff6d46bd15886a6a14`](https://github.com/wevm/wagmi/commit/06bb598a7f04c7b167f5b7ff6d46bd15886a6a14), [`24a45b269bd0214a29d6f82a84ac66ef8c3f3822`](https://github.com/wevm/wagmi/commit/24a45b269bd0214a29d6f82a84ac66ef8c3f3822)]: + - @wagmi/core@2.11.0 + +## 5.0.11 + +### Patch Changes + +- [#4020](https://github.com/wevm/wagmi/pull/4020) [`e3b124ce414b8fd1b2214e2c5a28dc72158a13d1`](https://github.com/wevm/wagmi/commit/e3b124ce414b8fd1b2214e2c5a28dc72158a13d1) Thanks [@tmm](https://github.com/tmm)! - Added reconnection support to `metaMask` on mobile and use deeplinks by default. + +- Updated dependencies [[`f2a7cefab96691ebed8b8e45ffde071c47b58dbe`](https://github.com/wevm/wagmi/commit/f2a7cefab96691ebed8b8e45ffde071c47b58dbe), [`f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5`](https://github.com/wevm/wagmi/commit/f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5)]: + - @wagmi/core@2.10.6 + +## 5.0.10 + +### Patch Changes + +- [`560952acd4bfe33db6c7c07b35c613cef278677c`](https://github.com/wevm/wagmi/commit/560952acd4bfe33db6c7c07b35c613cef278677c) Thanks [@tmm](https://github.com/tmm)! - Captured Coinbase Smart Wallet error when closing window as EIP-1193 `4001` error. + +## 5.0.9 + +### Patch Changes + +- [`32cdd7b7dc5aff916c040628519562c3a99d418d`](https://github.com/wevm/wagmi/commit/32cdd7b7dc5aff916c040628519562c3a99d418d) Thanks [@tmm](https://github.com/tmm)! - Bumped `@metamask/sdk` to remove peer dependency install warning. + +## 5.0.8 + +### Patch Changes + +- [#3997](https://github.com/wevm/wagmi/pull/3997) [`c1952d1ff7f0a491dc88595a49159451b07b5621`](https://github.com/wevm/wagmi/commit/c1952d1ff7f0a491dc88595a49159451b07b5621) Thanks [@nateReiners](https://github.com/nateReiners)! - Bumped Coinbase Wallet SDK. + +## 5.0.7 + +### Patch Changes + +- Updated dependencies [[`030c7c2cb380dfd67a2182f62e2aa7a6e1601898`](https://github.com/wevm/wagmi/commit/030c7c2cb380dfd67a2182f62e2aa7a6e1601898)]: + - @wagmi/core@2.10.5 + +## 5.0.6 + +### Patch Changes + +- Updated dependencies [[`51fde8a0433b4fff357c1a8d7e08b41b4c86c968`](https://github.com/wevm/wagmi/commit/51fde8a0433b4fff357c1a8d7e08b41b4c86c968)]: + - @wagmi/core@2.10.4 + +## 5.0.5 + +### Patch Changes + +- [#3979](https://github.com/wevm/wagmi/pull/3979) [`70dd28669dd8d2ce08217cd02e29a8fbba7a08d4`](https://github.com/wevm/wagmi/commit/70dd28669dd8d2ce08217cd02e29a8fbba7a08d4) Thanks [@tmm](https://github.com/tmm)! - Fixed `walletConnect` connector. + +## 5.0.4 + +### Patch Changes + +- [#3972](https://github.com/wevm/wagmi/pull/3972) [`be9e1b8a9818b92eb0654a20d9471e9e39329e7e`](https://github.com/wevm/wagmi/commit/be9e1b8a9818b92eb0654a20d9471e9e39329e7e) Thanks [@nateReiners](https://github.com/nateReiners)! - Bumped Coinbase Wallet SDK. + +## 5.0.3 + +### Patch Changes + +- [#3962](https://github.com/wevm/wagmi/pull/3962) [`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c) Thanks [@tmm](https://github.com/tmm)! - Added timeout to `getInfo` called in `safe` connector since [non-Safe App iFrames cause it to not resolve](https://github.com/safe-global/safe-apps-sdk/issues/263#issuecomment-1029835840). + +- Updated dependencies [[`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c)]: + - @wagmi/core@2.10.3 + +## 5.0.2 + +### Patch Changes + +- [#3940](https://github.com/wevm/wagmi/pull/3940) [`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a) Thanks [@jxom](https://github.com/jxom)! - Fixed usage of `metaMask` connector in Vite environments. + +- Updated dependencies [[`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a)]: + - @wagmi/core@2.10.2 + +## 5.0.1 + +### Patch Changes + +- Bumped versions. + +- Updated dependencies []: + - @wagmi/core@2.10.1 + +## 5.0.0 + +### Major Changes + +- [#3928](https://github.com/wevm/wagmi/pull/3928) [`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1) Thanks [@tmm](https://github.com/tmm)! - **Breaking:** Updated default Coinbase SDK in `coinbaseWallet` Connector to v4.x. + + Added a `version` property (defaults to `'4'`) to the `coinbaseWallet` Connector to target a version of the Coinbase SDK: + + ```diff + coinbaseWallet({ + + version: '3' | '4', + }) + ``` + + If `headlessMode` property is set to `true`, then the Connector will target v3 of the Coinbase SDK. + + The following properties are removed in v4 of the `coinbaseWallet` Connector: + + - `chainId` + - `darkMode` + - `diagnosticLogger` + - `enableMobileDeepLink` + - `jsonRpcUrl` + - `linkApiUrl` + - `overrideIsCoinbaseBrowser` + - `overrideIsCoinbaseWallet` + - `overrideIsMetaMask` + - `reloadOnDisconnect` + - `uiConstructor` + + Consumers can still use the above properties in v3 by passing `version: '3'` to the Connector. However, please note that v3 of the Coinbase SDK is deprecated and will be removed in a future release. + +### Patch Changes + +- Updated dependencies [[`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1)]: + - @wagmi/core@2.10.0 + +## 4.3.10 + +### Patch Changes + +- [#3906](https://github.com/wevm/wagmi/pull/3906) [`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67) Thanks [@tmm](https://github.com/tmm)! - Added support for Vue. + +- Updated dependencies [[`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67)]: + - @wagmi/core@2.9.8 + +## 4.3.9 + +### Patch Changes + +- [#3924](https://github.com/wevm/wagmi/pull/3924) [`1f58734f88458e0f6adb05c99f0c90f36ab286b8`](https://github.com/wevm/wagmi/commit/1f58734f88458e0f6adb05c99f0c90f36ab286b8) Thanks [@jxom](https://github.com/jxom)! - Refactored `isChainsStale` logic in `walletConnect` connector. + +- Updated dependencies [[`1f58734f88458e0f6adb05c99f0c90f36ab286b8`](https://github.com/wevm/wagmi/commit/1f58734f88458e0f6adb05c99f0c90f36ab286b8)]: + - @wagmi/core@2.9.7 + +## 4.3.8 + +### Patch Changes + +- [#3917](https://github.com/wevm/wagmi/pull/3917) [`05948fdad5bb4a56b08916d45b3dec2cb1e5f55b`](https://github.com/wevm/wagmi/commit/05948fdad5bb4a56b08916d45b3dec2cb1e5f55b) Thanks [@jxom](https://github.com/jxom)! - Updated `@metamask/sdk`. + +- Updated dependencies [[`05948fdad5bb4a56b08916d45b3dec2cb1e5f55b`](https://github.com/wevm/wagmi/commit/05948fdad5bb4a56b08916d45b3dec2cb1e5f55b)]: + - @wagmi/core@2.9.6 + +## 4.3.7 + +### Patch Changes + +- Updated dependencies [[`4fecbbb66d0aacd03b8c62a6455d11a33cde8f85`](https://github.com/wevm/wagmi/commit/4fecbbb66d0aacd03b8c62a6455d11a33cde8f85)]: + - @wagmi/core@2.9.5 + +## 4.3.6 + +### Patch Changes + +- Updated dependencies [[`e6139a97c4b8804d734b1547b5e3921ce01fbe24`](https://github.com/wevm/wagmi/commit/e6139a97c4b8804d734b1547b5e3921ce01fbe24)]: + - @wagmi/core@2.9.4 + +## 4.3.5 + +### Patch Changes + +- [#3904](https://github.com/wevm/wagmi/pull/3904) [`addca28ebc20f1a4367c35fe9ef786decff9c87e`](https://github.com/wevm/wagmi/commit/addca28ebc20f1a4367c35fe9ef786decff9c87e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +- Updated dependencies [[`addca28ebc20f1a4367c35fe9ef786decff9c87e`](https://github.com/wevm/wagmi/commit/addca28ebc20f1a4367c35fe9ef786decff9c87e)]: + - @wagmi/core@2.9.3 + +## 4.3.4 + +### Patch Changes + +- [#3902](https://github.com/wevm/wagmi/pull/3902) [`204b7b624612405500ec098fb9e35facd3f74ca4`](https://github.com/wevm/wagmi/commit/204b7b624612405500ec098fb9e35facd3f74ca4) Thanks [@jxom](https://github.com/jxom)! - Made third-party SDK imports type-only. + +- Updated dependencies [[`204b7b624612405500ec098fb9e35facd3f74ca4`](https://github.com/wevm/wagmi/commit/204b7b624612405500ec098fb9e35facd3f74ca4)]: + - @wagmi/core@2.9.2 + +## 4.3.3 + +### Patch Changes + +- Updated dependencies [[`cda6a5d5`](https://github.com/wevm/wagmi/commit/cda6a5d56328330fbde050b4ef40b01c58d2519a)]: + - @wagmi/core@2.9.1 + +## 4.3.2 + +### Patch Changes + +- Updated dependencies [[`017828fc`](https://github.com/wevm/wagmi/commit/017828fc027c7a84b54ea9d627e9389f4d60d6c2)]: + - @wagmi/core@2.9.0 + +## 4.3.1 + +### Patch Changes + +- Updated dependencies [[`d4a78eb0`](https://github.com/wevm/wagmi/commit/d4a78eb07119d2e5617e52481ac7d6c6d1583ddc)]: + - @wagmi/core@2.8.1 + +## 4.3.0 + +### Minor Changes + +- [#3868](https://github.com/wevm/wagmi/pull/3868) [`c2af20b8`](https://github.com/wevm/wagmi/commit/c2af20b88cf16970d087faaec10b463357a5836e) Thanks [@jxom](https://github.com/jxom)! - Added `supportsSimulation` property to connectors that indicates if the connector's wallet supports contract simulation. + +### Patch Changes + +- Updated dependencies [[`0d141f17`](https://github.com/wevm/wagmi/commit/0d141f171d6ec44bcbfc9c876565b5e2fb8af6de), [`c2af20b8`](https://github.com/wevm/wagmi/commit/c2af20b88cf16970d087faaec10b463357a5836e)]: + - @wagmi/core@2.8.0 + +## 4.2.0 + +### Minor Changes + +- [#3857](https://github.com/wevm/wagmi/pull/3857) [`d4274c03`](https://github.com/wevm/wagmi/commit/d4274c03a6af5f2d26d31432016ebc14950a330e) Thanks [@tmm](https://github.com/tmm)! - Added `addEthereumChainParameter` to `switchChain`-related methods. + +### Patch Changes + +- Updated dependencies [[`d4274c03`](https://github.com/wevm/wagmi/commit/d4274c03a6af5f2d26d31432016ebc14950a330e), [`4781a405`](https://github.com/wevm/wagmi/commit/4781a4056d4ffc2c74f96a75429e9b2cd2417ad8), [`400c960b`](https://github.com/wevm/wagmi/commit/400c960b30d701c134850c695ae903a382c29b5b)]: + - @wagmi/core@2.7.0 + +## 4.1.28 + +### Patch Changes + +- [`e3c832a1`](https://github.com/wevm/wagmi/commit/e3c832a12c301f9b0ee129d877b3101d220ba8b2) Thanks [@jxom](https://github.com/jxom)! - Fixed undefined `navigator` issue in MetaMask connector. + +- Updated dependencies [[`e3c832a1`](https://github.com/wevm/wagmi/commit/e3c832a12c301f9b0ee129d877b3101d220ba8b2)]: + - @wagmi/core@2.6.19 + +## 4.1.27 + +### Patch Changes + +- [#3848](https://github.com/wevm/wagmi/pull/3848) [`dd40a41c`](https://github.com/wevm/wagmi/commit/dd40a41c526ab60a288aff2250ed8dba92a27b16) Thanks [@jxom](https://github.com/jxom)! - Updated MetaMask SDK. + +- Updated dependencies [[`dd40a41c`](https://github.com/wevm/wagmi/commit/dd40a41c526ab60a288aff2250ed8dba92a27b16)]: + - @wagmi/core@2.6.18 + +## 4.1.26 + +### Patch Changes + +- Updated dependencies [[`a97bfbae`](https://github.com/wevm/wagmi/commit/a97bfbaeb615cfef04665e5e7348d85d17f960f0)]: + - @wagmi/core@2.6.17 + +## 4.1.25 + +### Patch Changes + +- [#3788](https://github.com/wevm/wagmi/pull/3788) [`42ad380d`](https://github.com/wevm/wagmi/commit/42ad380d9a5d8bc0f61d73612142dea9d098de5e) Thanks [@tmm](https://github.com/tmm)! - Refactored connectors to remove unnecessarily event listeners. + +- Updated dependencies [[`42ad380d`](https://github.com/wevm/wagmi/commit/42ad380d9a5d8bc0f61d73612142dea9d098de5e)]: + - @wagmi/core@2.6.16 + +## 4.1.24 + +### Patch Changes + +- Updated dependencies [[`b907d5ac`](https://github.com/wevm/wagmi/commit/b907d5ac3a746bcbccc06d1fe78c5bd8f9a7d685)]: + - @wagmi/core@2.6.15 + +## 4.1.23 + +### Patch Changes + +- Updated dependencies [[`b3b54ef1`](https://github.com/wevm/wagmi/commit/b3b54ef179c5fa0d1694d38d4b808549a0550409), [`3da20bb8`](https://github.com/wevm/wagmi/commit/3da20bb80e7c3efeef8227ced66ad615370fc242), [`a3d1858f`](https://github.com/wevm/wagmi/commit/a3d1858fce448d2b70e36ee692ef1589b74e9d3f)]: + - @wagmi/core@2.6.14 + +## 4.1.22 + +### Patch Changes + +- Updated dependencies [[`b80236dc`](https://github.com/wevm/wagmi/commit/b80236dc623095fe8f1e1d10957d7776fb6ab48b)]: + - @wagmi/core@2.6.13 + +## 4.1.21 + +### Patch Changes + +- Updated dependencies [[`a59069e9`](https://github.com/wevm/wagmi/commit/a59069e9fab45dd606bb89a7f829fe94c51a5494), [`0acd3132`](https://github.com/wevm/wagmi/commit/0acd31320f534993af566be5490c2978b6184f66)]: + - @wagmi/core@2.6.12 + +## 4.1.20 + +### Patch Changes + +- [`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d) Thanks [@tmm](https://github.com/tmm)! - Deprecated `normalizeChainId`. Use `Number` instead. + +- Updated dependencies [[`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d)]: + - @wagmi/core@2.6.11 + +## 4.1.19 + +### Patch Changes + +- Updated dependencies [[`dbdca8fd`](https://github.com/wevm/wagmi/commit/dbdca8fd14b90c166222a66a373c1b33c06ce019)]: + - @wagmi/core@2.6.10 + +## 4.1.18 + +### Patch Changes + +- Updated dependencies [[`d56edf4f`](https://github.com/wevm/wagmi/commit/d56edf4f27c52acc7a0f57114454b0d3e22cacd6)]: + - @wagmi/core@2.6.9 + +## 4.1.17 + +### Patch Changes + +- Updated dependencies [[`e46bcd47`](https://github.com/wevm/wagmi/commit/e46bcd4738a18da15b53f6612b614379c1985374)]: + - @wagmi/core@2.6.8 + +## 4.1.16 + +### Patch Changes + +- [`1c1fee6a`](https://github.com/wevm/wagmi/commit/1c1fee6ab8f01f7734ac6ce05093fa8e388beb3e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +- [#3653](https://github.com/wevm/wagmi/pull/3653) [`88a2d744`](https://github.com/wevm/wagmi/commit/88a2d744a1315908c9e54156026df3ad2435ad44) Thanks [@tash-2s](https://github.com/tash-2s)! - Fixed error occurring when adding chains without explorers to MetaMask. + +- Updated dependencies [[`b479b5e8`](https://github.com/wevm/wagmi/commit/b479b5e8a5866cba792862f22e6352c4fb566137), [`f5648dd2`](https://github.com/wevm/wagmi/commit/f5648dd28b3576b628f57732b89287f55acbb1c1), [`1c1fee6a`](https://github.com/wevm/wagmi/commit/1c1fee6ab8f01f7734ac6ce05093fa8e388beb3e), [`88a2d744`](https://github.com/wevm/wagmi/commit/88a2d744a1315908c9e54156026df3ad2435ad44)]: + - @wagmi/core@2.6.7 + +## 4.1.15 + +### Patch Changes + +- Updated dependencies [[`a91c0b64`](https://github.com/wevm/wagmi/commit/a91c0b64ba8b3e6537a560e69724eb601f26af27)]: + - @wagmi/core@2.6.6 + +## 4.1.14 + +### Patch Changes + +- [#3591](https://github.com/wevm/wagmi/pull/3591) [`ca5decdb`](https://github.com/wevm/wagmi/commit/ca5decdb712f81e3f5dab933a94b967bca5b6af4) Thanks [@tmm](https://github.com/tmm)! - Fixed Coinbase Wallet import. + +- Updated dependencies [[`c677dcd2`](https://github.com/wevm/wagmi/commit/c677dcd245dccdf69289a3d66dded237b09570a2)]: + - @wagmi/core@2.6.5 + +## 4.1.13 + +### Patch Changes + +- [#3569](https://github.com/wevm/wagmi/pull/3569) [`fa25b448`](https://github.com/wevm/wagmi/commit/fa25b4482504b4d9729a5687ea6d6dc959265bc0) Thanks [@svenvoskamp](https://github.com/svenvoskamp)! - Updated dependencies. + +- [#3558](https://github.com/wevm/wagmi/pull/3558) [`895f28e8`](https://github.com/wevm/wagmi/commit/895f28e873af7c8eda5ca85734ff67c8979fd950) Thanks [@tmm](https://github.com/tmm)! - Fixed connector warnings. + +- Updated dependencies [[`7c6618e6`](https://github.com/wevm/wagmi/commit/7c6618e6a0eb1ff39cf8f66b34d3ddc14be538fe), [`895f28e8`](https://github.com/wevm/wagmi/commit/895f28e873af7c8eda5ca85734ff67c8979fd950)]: + - @wagmi/core@2.6.4 + +## 4.1.12 + +### Patch Changes + +- Updated dependencies [[`9c3b85dd`](https://github.com/wevm/wagmi/commit/9c3b85dd0a9a4a593e1d7e029345275735330e32), [`2a72214a`](https://github.com/wevm/wagmi/commit/2a72214a2901d6b6ddd39f80238aa0bd4db670a7)]: + - @wagmi/core@2.6.3 + +## 4.1.11 + +### Patch Changes + +- [#3518](https://github.com/wevm/wagmi/pull/3518) [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +- Updated dependencies [[`414eb048`](https://github.com/wevm/wagmi/commit/414eb048af492caac70c0e874dfc87c30702804a), [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d)]: + - @wagmi/core@2.6.2 + +## 4.1.10 + +### Patch Changes + +- [#3510](https://github.com/wevm/wagmi/pull/3510) [`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where connectors returning multiple addresses didn't checksum correctly. + +- Updated dependencies [[`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d), [`101a7dd1`](https://github.com/wevm/wagmi/commit/101a7dd131b0cae2dc25579ecab9044290efd37b)]: + - @wagmi/core@2.6.1 + +## 4.1.9 + +### Patch Changes + +- [#3496](https://github.com/wevm/wagmi/pull/3496) [`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +- Updated dependencies [[`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b)]: + - @wagmi/core@2.6.0 + +## 4.1.8 + +### Patch Changes + +- Updated dependencies [[`ca98041d`](https://github.com/wevm/wagmi/commit/ca98041d1b39893d90246929485f4db0d1c6f9f7)]: + - @wagmi/core@2.5.0 + +## 4.1.7 + +### Patch Changes + +- [#3427](https://github.com/wevm/wagmi/pull/3427) [`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Bumped dependencies. + +- Updated dependencies [[`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4)]: + - @wagmi/core@2.4.0 + +## 4.1.6 + +### Patch Changes + +- Updated dependencies [[`3be5bb7b`](https://github.com/wevm/wagmi/commit/3be5bb7b0b38646e12e6da5c762ef74dff66bcc2)]: + - @wagmi/core@2.3.1 + +## 4.1.5 + +### Patch Changes + +- [#3459](https://github.com/wevm/wagmi/pull/3459) [`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Bumped dependencies + +- [`1cfb6e5a`](https://github.com/wevm/wagmi/commit/1cfb6e5a875e707abcee00dd5739e87da05e8c90) Thanks [@jxom](https://github.com/jxom)! - Bumped listener limit on WalletConnect connector. + +- Updated dependencies [[`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5), [`90ef39bb`](https://github.com/wevm/wagmi/commit/90ef39bb0f4ecb3c914d317875348e35ba0f4524), [`1cfb6e5a`](https://github.com/wevm/wagmi/commit/1cfb6e5a875e707abcee00dd5739e87da05e8c90)]: + - @wagmi/core@2.3.0 + +## 4.1.4 + +### Patch Changes + +- [#3443](https://github.com/wevm/wagmi/pull/3443) [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb) Thanks [@jmrossy](https://github.com/jmrossy)! - Bumped dependencies. + +- [#3447](https://github.com/wevm/wagmi/pull/3447) [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +- Updated dependencies [[`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a), [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb)]: + - @wagmi/core@2.2.1 + +## 4.1.3 + +### Patch Changes + +- Updated dependencies [[`00bf10a4`](https://github.com/wevm/wagmi/commit/00bf10a428b0d1c5dac35ebf25b19571e033ac26), [`64c073f6`](https://github.com/wevm/wagmi/commit/64c073f6c2720961e2d6aff986670b73dbfab9c3), [`fb6c4148`](https://github.com/wevm/wagmi/commit/fb6c4148d9e9e2fccfbe74c8f343b444dc68dec5)]: + - @wagmi/core@2.2.0 + +## 4.1.2 + +### Patch Changes + +- Updated dependencies [[`e00b8205`](https://github.com/wevm/wagmi/commit/e00b82058685751637edfa9a6b2d196a12549fe7)]: + - @wagmi/core@2.1.2 + +## 4.1.1 + +### Patch Changes + +- [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37) Thanks [@tmm](https://github.com/tmm)! - Added note to `metaMask` connector. + +- Updated dependencies [[`64b82282`](https://github.com/wevm/wagmi/commit/64b82282c1e57e77c25aa0814673780e4d11edd4), [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37)]: + - @wagmi/core@2.1.1 + +## 4.1.0 + +### Minor Changes + +- Updated dependencies [[`c9cd302e`](https://github.com/wevm/wagmi/commit/c9cd302e1c65c980deaee2e12567c2a8ec08b399)]: + - @wagmi/core@2.1.0 + +## 4.0.2 + +### Patch Changes + +- [#3384](https://github.com/wevm/wagmi/pull/3384) [`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844) Thanks [@tmm](https://github.com/tmm)! - Fixed connectors not bubbling error when connecting with `chainId` and subsequent user rejection. + +- Updated dependencies [[`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844)]: + - @wagmi/core@2.0.2 + +## 4.0.1 + +### Major Changes + +- [#3333](https://github.com/wevm/wagmi/pull/3333) [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a) Thanks [@tmm](https://github.com/tmm)! - Added support for Wagmi 2.0. + +### Patch Changes + +- Updated dependencies [[`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a)]: + - @wagmi/core@2.0.0 + +## 3.1.11 + +### Patch Changes + +- [#3361](https://github.com/wevm/wagmi/pull/3361) [`bbbbf587`](https://github.com/wevm/wagmi/commit/bbbbf587e41bae12b072b7a7c897d580fc07cd2b) Thanks [@0xAsimetriq](https://github.com/0xAsimetriq)! - Updated WalletConnect connector dependencies + +## 3.1.10 + +### Patch Changes + +- [`53ca1f7e`](https://github.com/wevm/wagmi/commit/53ca1f7eb411d912e11fcce7e03bd61ed067959c) Thanks [@tmm](https://github.com/tmm)! - Removed LedgerConnector due to security vulnerability + +## 3.1.9 + +### Patch Changes + +- [#3114](https://github.com/wevm/wagmi/pull/3114) [`51eca0fb`](https://github.com/wevm/wagmi/commit/51eca0fbaea6932f31a5b8b4213f0252280053e2) Thanks [@akathecoder](https://github.com/akathecoder)! - Added Okto Wallet to Injected Wallets Connector + +- [#3299](https://github.com/wevm/wagmi/pull/3299) [`b02020b3`](https://github.com/wevm/wagmi/commit/b02020b3724e0228198f35817611bb063295906e) Thanks [@dasanra](https://github.com/dasanra)! - Fixed issue with [Safe SDK](https://github.com/wevm/viem/issues/579) by bumping `@safe-global/safe-apps-provider@0.18.1` + +## 3.1.8 + +### Patch Changes + +- [#3197](https://github.com/wevm/wagmi/pull/3197) [`e8f7bcbc`](https://github.com/wevm/wagmi/commit/e8f7bcbcd9c038a901c29e71769682c088efe2ac) Thanks [@ByteZhang1024](https://github.com/ByteZhang1024)! - Added OneKey Wallet to injected connector flags. + +## 3.1.7 + +### Patch Changes + +- [#3276](https://github.com/wevm/wagmi/pull/3276) [`83223a06`](https://github.com/wevm/wagmi/commit/83223a0659e2f675d897a1d3374c7af752c16abf) Thanks [@glitch-txs](https://github.com/glitch-txs)! - Removed required namespaces from WalletConnect connector + +## 3.1.6 + +### Patch Changes + +- [#3236](https://github.com/wevm/wagmi/pull/3236) [`cc7e18f2`](https://github.com/wevm/wagmi/commit/cc7e18f2e7f6b8b989f60f0b05aee70e996a9975) Thanks [@0xAsimetriq](https://github.com/0xAsimetriq)! - Updated @walletconnect/ethereum-provider + +- [#3236](https://github.com/wevm/wagmi/pull/3236) [`cc7e18f2`](https://github.com/wevm/wagmi/commit/cc7e18f2e7f6b8b989f60f0b05aee70e996a9975) Thanks [@0xAsimetriq](https://github.com/0xAsimetriq)! - Updated @walletconnect/ethereum-provider + +## 3.1.5 + +### Patch Changes + +- [#3220](https://github.com/wagmi-dev/wagmi/pull/3220) [`a1950449`](https://github.com/wagmi-dev/wagmi/commit/a1950449127ddf72fff8ecd1fc34c3690befbb05) Thanks [@rkalis](https://github.com/rkalis)! - Fixed a bug where injected walets with an empty providers array could not connect + +## 3.1.4 + +### Patch Changes + +- [#3115](https://github.com/wagmi-dev/wagmi/pull/3115) [`4e6ec415`](https://github.com/wagmi-dev/wagmi/commit/4e6ec4151baece94e940e227e0e3711c7f8534d9) Thanks [@bifot](https://github.com/bifot)! - Added SafePal injected name mapping. + +## 3.1.3 + +### Patch Changes + +- [#3141](https://github.com/wagmi-dev/wagmi/pull/3141) [`e78aa337`](https://github.com/wagmi-dev/wagmi/commit/e78aa337c454f04b41a3cbd381d25270dd4a0afd) Thanks [@einaralex](https://github.com/einaralex)! - Updated WalletConnect libraries. + +## 3.1.2 + +### Patch Changes + +- [#3009](https://github.com/wagmi-dev/wagmi/pull/3009) [`3aaba328`](https://github.com/wagmi-dev/wagmi/commit/3aaba32808ddb4035ec885f96992c91078056715) Thanks [@0xAsimetriq](https://github.com/0xAsimetriq)! - Update WalletConnect dependencies + +## 3.1.1 + +### Patch Changes + +- [#2973](https://github.com/wevm/wagmi/pull/2973) [`bf831bb3`](https://github.com/wevm/wagmi/commit/bf831bb30df8037cc4312342d0fe3c045408c2fe) Thanks [@masm](https://github.com/masm)! - Added Zeal wallet + +## 3.1.0 + +### Minor Changes + +- [#2956](https://github.com/wevm/wagmi/pull/2956) [`2abeb285`](https://github.com/wevm/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895) Thanks [@tmm](https://github.com/tmm)! - Replaced `@wagmi/chains` with `viem/chains`. + +## 3.0.0 + +### Patch Changes + +- 0306383: Updated WalletConnect dependencies +- Updated dependencies [d1ef9b4] +- Updated dependencies [484c846] + - @wagmi/chains@1.8.0 + +## 2.7.0 + +### Minor Changes + +- a270cb9: Updated WalletConnect dependencies. + +### Patch Changes + +- 06cc1b4: Add SubWallet injected flags +- 131a337: Added Desig Wallet name mapping. +- e089d7d: Added Fordefi Wallet name mapping. +- ce84d0a: Added Coin98 Wallet injected flags. +- Updated dependencies [8fdacd8] +- Updated dependencies [2e9283a] +- Updated dependencies [a432a2b] +- Updated dependencies [408740a] +- Updated dependencies [6794a61] +- Updated dependencies [0c5a32b] +- Updated dependencies [ebc85ec] +- Updated dependencies [5683df2] +- Updated dependencies [414ff36] +- Updated dependencies [4f514c6] +- Updated dependencies [1cf72bc] +- Updated dependencies [cd68471] +- Updated dependencies [baf3143] +- Updated dependencies [9737f24] +- Updated dependencies [7797238] +- Updated dependencies [3846811] +- Updated dependencies [0ea344c] + - @wagmi/chains@1.7.0 + +## 2.6.6 + +### Patch Changes + +- 56c127d: Updated WalletConnect dependencies. +- Updated dependencies [4b411d2] +- Updated dependencies [df697ac] +- Updated dependencies [186f5a7] +- Updated dependencies [a96b514] +- Updated dependencies [0a6e6da] + - @wagmi/chains@1.5.0 + +## 2.6.5 + +### Patch Changes + +- 51e346e: Updated WalletConnectConnector logic to handle individual namespaces like eip155:\* + +## 2.6.4 + +### Patch Changes + +- 0a57de2: Added conditional for WalletConnectConnector optionalChains + +## 2.6.3 + +### Patch Changes + +- f2d532d: Updated WalletConnect dependencies, exposed `relayUrl` option for `WalletConnectConnector` +- ff53857: Fixed issue importing `EthereumProvider` in Vite environments. +- Updated dependencies [d642e1d] +- Updated dependencies [3027d7b] +- Updated dependencies [97dbd44] + - @wagmi/chains@1.4.0 + +## 2.6.2 + +### Patch Changes + +- 27bb1b3: Added explicit type annotations for the `getWalletClient()` method. + +## 2.6.1 + +### Patch Changes + +- a3507a9: Updated @walletconnect/ethereum-provider dependency + +## 2.6.0 + +### Minor Changes + +- 32dc317: Updated @walletconnect/ethereum-provider and @walletconnect/modal dependencies + +## 2.5.0 + +### Minor Changes + +- 57e674e: Updated `@safe-global/safe-apps-sdk` & `@safe-global/safe-apps-provider` + +## 2.4.0 + +### Patch Changes + +- f21c8e0: Added WalletConnect v2 support to Ledger connector. +- 27482bb: Add HAQQ Wallet detection +- 7d6aa43: Exported `normalizeChainId`. +- Updated dependencies [62b8209] +- Updated dependencies [106ac13] +- Updated dependencies [8b3f5e5] + - @wagmi/chains@1.3.0 + +## 2.3.0 + +### Minor Changes + +- 28219ae: Added metadata property to WalletConnect init function +- 6fef949: Updated @walletconnect/modal and @walletconnect/ethereum-provider deps + +### Patch Changes + +- 72f6465: Added `TTWallet` to `getInjectedName` list +- Updated dependencies [a7cbd04] +- Updated dependencies [f6ee133] + - @wagmi/chains@1.2.0 + +## 2.2.0 + +### Minor Changes + +- 6c841d4: Changed `Address` type import from ABIType to viem. + +### Patch Changes + +- 09c83f8: Update @walletconnect/ethereum-provider, Replace @web3modal/standalone with @walletconnect/modal, Fix issue with wallet_addEthereumChain method in WalletConnectConnector + +## 2.1.1 + +### Patch Changes + +- c24de75: Updated `@walletconnect/ethereum-provider` and `@web3modal/standalone` dependencies. +- 605c422: Bumped `viem` peer dependency. +- dc1c546: Throw ResourceUnavailableError on -30002 errors. + +## 2.1.0 + +### Minor Changes + +- b001569: Bumped minimum TypeScript version to v5.0.4. + +### Patch Changes + +- 0f05b2b: Updated `abitype` to `0.8.7`. +- 6aea7ee: Fixed internal types. +- b187cb0: Added `isNovaWallet` injected flag. +- 5e44429: Added Edgeware mainnet and testnet +- b18b314: Updated @walletconnect/ethereum-provider and @web3modal/standalone dependencies +- Updated dependencies [b62a199] +- Updated dependencies [b001569] +- Updated dependencies [260ab59] +- Updated dependencies [6aea7ee] +- Updated dependencies [5e44429] + - @wagmi/chains@1.0.0 + +## 2.0.0 + +### Patch Changes + +- Updated dependencies [36c14b2] + - @wagmi/chains@0.3.0 + +## 1.0.5 + +### Patch Changes + +- fa61dfe: Updated viem. +- Updated dependencies [577d2a0] + - @wagmi/chains@0.2.25 + +## 1.0.4 + +### Patch Changes + +- bbbd11b: Corrected Rabby Wallet name +- Updated dependencies [0639a1f] + - @wagmi/chains@0.2.24 + +## 1.0.3 + +### Patch Changes + +- 64dfe61: Update @web3modal/standalone to v2.4.1, Update @walletconnect/ethereum-provider to 2.7.4 +- bab7ad8: Added Defiant to injected connector flags +- 44cde07: Added Talisman wallet flag + +## 1.0.2 + +### Patch Changes + +- bce5a0c: Removed chain fallback when instantiating a Wallet Client. + +## 1.0.1 + +### Patch Changes + +- [`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af) Thanks [@jxom](https://github.com/jxom)! - Downgraded abitype. + +## 1.0.0 + +### Major Changes + +- 7e274f5: Released v1. + +### Patch Changes + +- 0966bf7: Changed Kucoin Wallet name mapping to Halo Wallet + +## 1.0.0-next.5 + +### Major Changes + +- Updated references. + +## 1.0.0-next.4 + +### Major Changes + +- Updated references. + +## 1.0.0-next.3 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/chains@1.0.0-next.0 + +## 1.0.0-next.2 + +### Major Changes + +- updated viem + +## 1.0.0-next.1 + +### Major Changes + +- [`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb) Thanks [@jxom](https://github.com/jxom)! - Released v1. + +## 1.0.0-next.0 + +### Major Changes + +- 33488cf: Released v1. + +## 0.3.19 + +### Patch Changes + +- 274eef3: - Updated @web3modal/standalone to 2.3.7 + - Updated @walletconnect/ethereum-provider to 2.7.1 +- 41697df: Updated @walletconnect/ethereum-provider version to 2.7.2 +- 82dcb72: Added Enkrypt extension detection + +## 0.3.18 + +### Patch Changes + +- f66e065: Added BlockWallet to injected connector flags. + +## 0.3.17 + +### Patch Changes + +- 12ab5d1: Updated @coinbase/wallet-sdk to 3.6.6 + +## 0.3.16 + +### Patch Changes + +- c1e3ddf: Reverted ABIType version change. + +## 0.3.15 + +### Patch Changes + +- d4825e6: Fixed ABIType version to match downstream packages. + +## 0.3.14 + +### Patch Changes + +- c25ac82: Added more flags to `MetaMaskConnector` `getProvider` check. +- b19a932: Updated @web3modal/standalone to 2.3.0, @walletconnect/ethereum-provider to 2.7.0 +- cdc387e: Added `ImToken` to `getInjectedName` list + +## 0.3.13 + +### Patch Changes + +- 2a21d27: Updated `@coinbase/wallet-sdk` to `3.6.4` + +## 0.3.12 + +### Patch Changes + +- 9bb22b6: Updated `@walletconnect/ethereum-provider` to `2.6.2`, relaxed `@web3modal/standalone` version requirement +- 0d7625b: Added Rabby to injected connector flags +- f63d7fd: Added correct error to switch network cause. + +## 0.3.11 + +### Patch Changes + +- 0778abc: Renamed `isTally` injected provider to `Taho` + +## 0.3.10 + +### Patch Changes + +- 4267020: Added `qrModalOptions` option to `WalletConnectConnector` +- e78fb0a: Pinned WalletConnect dependencies + +## 0.3.9 + +### Patch Changes + +- 5cd0afc: Added `isZerion` to `InjectedProviderFlags` and `getInjectedName` +- be4825e: Added GameStop Wallet to injected connector flags + +## 0.3.8 + +### Patch Changes + +- 11f3fe2: Fixed issue where `UNSTABLE_shimOnConnectSelectAccount` would not bubble up error for MetaMask if request to connect was already active. + +## 0.3.7 + +### Patch Changes + +- 04c0e47: Fixed issue switching chain after adding to MetaMask. + +## 0.3.6 + +### Patch Changes + +- 85330c1: Removed `InjectedConnector` `shimChainChangedDisconnect` shim (no longer necessary). + +## 0.3.5 + +### Patch Changes + +- 8b1a526: Added Dawn wallet flag + +## 0.3.4 + +### Patch Changes + +- 6b15d6f: Updated `@walletconnect/ethereum-provider` to `2.5.1`. +- 1f452e7: Added OKX Wallet to injected connector flags. +- a4d9083: Added Backpack wallet to injected connector flags. +- 6a4af48: Enabled support for programmatic chain switching on `LedgerConnector` & added `"ledger"` to the switch chain regex on `WalletConnectLegacyConnector`. + +## 0.3.3 + +### Patch Changes + +- f24ce0c: Updated @walletconnect/ethereum-provider to 2.4.8 +- e3a3fee: Added "uniswap wallet" to the regex that determines wallets allowed to switch chains in the WalletConnect legacy connector +- 641af48: Added name mapping for Bifrost Wallet +- 4d2c90a: Added name mapping for Phantom +- 3d276d0: Added Status as the name of the injected connector for the Status App + +## 0.3.2 + +### Patch Changes + +- 13a6a07: Updated `@walletconnect/ethereum-provider` to `2.4.7`. + +## 0.3.1 + +### Patch Changes + +- a23c40f: Added name mapping for [Frontier](https://frontier.xyz) Wallet +- d779fb3: Added name mapping for HyperPay. + +## 0.3.0 + +### Minor Changes + +- c4d5bb5: **Breaking:** Removed the `version` config option for `WalletConnectConnector`. + + `WalletConnectConnector` now uses WalletConnect v2 by default. WalletConnect v1 is now `WalletConnectLegacyConnector`. + + ### WalletConnect v2 + + ```diff + import { WalletConnectConnector } from '@wagmi/connectors/walletConnect' + + const connector = new WalletConnectConnector({ + options: { + - version: '2', + projectId: 'abc', + }, + }) + ``` + + ### WalletConnect v1 + + ```diff + -import { WalletConnectConnector } from '@wagmi/connectors/walletConnect' + +import { WalletConnectLegacyConnector } from '@wagmi/connectors/walletConnectLegacy' + + -const connector = new WalletConnectConnector({ + +const connector = new WalletConnectLegacyConnector({ + options: { + qrcode: true, + }, + }) + ``` + +## 0.2.7 + +### Patch Changes + +- 57f1226: Added name mapping for XDEFI + +## 0.2.6 + +### Patch Changes + +- bb1b88c: Added name mapping for Bitski injected wallet +- fcb5595: Fixed shim disconnect key to read from defined Connector ID. +- 49f8853: Fixed `SafeConnector` import type error that existed for specific build environments. + +## 0.2.5 + +### Patch Changes + +- 5d121f2: Added `isApexWallet` to injected `window.ethereum` flags. +- e3566eb: Updated `@web3modal/standalone` to `2.1.1` for WalletConnectConnector. + +## 0.2.4 + +### Patch Changes + +- a4f31bc: Added Connector for [Safe](https://safe.global) wallet +- d5e25d9: Locked ethers peer dependency version to >=5.5.1 <6 + +## 0.2.3 + +### Patch Changes + +- 6fa74dd: Updated `@walletconnect/universal-provider` + Added more signable methods to WC v2. + +## 0.2.2 + +### Patch Changes + +- 6b0725b: Fixed race condition between `switchNetwork` and mutation Hooks that use `chainId` (e.g. `sendTransaction`). + +## 0.2.1 + +### Patch Changes + +- 942fcde: Updated `@walletconnect/universal-provider` and `@web3modal/standalone` packages for WalletConnectConnector (v2). + + Improved initialization flow for `@walletconnect/universal-provider` for WalletConnectConnector (v2). + +## 0.2.0 + +### Minor Changes + +- be33c7d: Chains are now narrowed to their most specific type using the TypeScript [`satisfies`](https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/#the-satisfies-operator) operator. + +## 0.1.10 + +### Patch Changes + +- d75e8d2: Fixed ABIType version mismatch between packages. + +## 0.1.9 + +### Patch Changes + +- 8c3fc00: Added public RPC URL to Connector fallback chains + +## 0.1.8 + +### Patch Changes + +- 5e6dc30: Replaced legacy qrcodemodal with web3modal for WalletConnect v2. + +## 0.1.7 + +### Patch Changes + +- be4add2: Added `isRainbow` flag to `InjectedConnector`. + +## 0.1.6 + +### Patch Changes + +- 3dfc558: Add `switchSigner` method to `MockProvider`. + +## 0.1.5 + +### Patch Changes + +- 7dce4b5: Bumped WalletConnect Universal Provider version. + +## 0.1.4 + +### Patch Changes + +- 4cec598: Added CJS escape hatch bundle under the "cjs" tag. + +## 0.1.3 + +### Patch Changes + +- 822bc88: The `WalletConnectConnector` now supports WalletConnect v2. + + It can be enabled by setting `version` to `'2'` and supplying a [WalletConnect Cloud `projectId`](https://cloud.walletconnect.com/sign-in). + +## 0.1.2 + +### Patch Changes + +- 5e5f37f: Fixed issue where connecting to MetaMask may return with a stale address + +## 0.1.1 + +### Patch Changes + +- 919790c: Updated `@ledgerhq/connect-kit-loader` to `1.0.1` + +## 0.1.0 + +### Minor Changes + +- 5db7cba: Added `LedgerConnector` +- 55a0ca2: Initial release of the `@wagmi/connectors` package – a collection of Connectors for wagmi. diff --git a/packages/connectors/README.md b/packages/connectors/README.md new file mode 100644 index 0000000000..05418ec15c --- /dev/null +++ b/packages/connectors/README.md @@ -0,0 +1,13 @@ +# @wagmi/connectors + +Collection of connectors for Wagmi + +## Installation + +```bash +pnpm add @wagmi/connectors @wagmi/core viem +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). diff --git a/packages/connectors/package.json b/packages/connectors/package.json new file mode 100644 index 0000000000..5ddcfaa107 --- /dev/null +++ b/packages/connectors/package.json @@ -0,0 +1,71 @@ +{ + "name": "@wagmi/connectors", + "description": "Collection of connectors for Wagmi", + "version": "5.8.3", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/connectors" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "test:build": "publint --strict && attw --pack --ignore-rules cjs-resolves-to-esm" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts" + ], + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./package.json": "./package.json" + }, + "peerDependencies": { + "@wagmi/core": "workspace:*", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "dependencies": { + "@coinbase/wallet-sdk": "4.3.0", + "@metamask/sdk": "0.33.1", + "@safe-global/safe-apps-provider": "0.18.6", + "@safe-global/safe-apps-sdk": "9.1.0", + "@walletconnect/ethereum-provider": "2.20.2", + "cbw-sdk": "npm:@coinbase/wallet-sdk@3.9.3" + }, + "devDependencies": { + "@wagmi/core": "workspace:*", + "msw": "^2.4.9" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": [ + "react", + "hooks", + "eth", + "ethereum", + "dapps", + "wallet", + "web3", + "abi" + ] +} diff --git a/packages/connectors/src/coinbaseWallet.test.ts b/packages/connectors/src/coinbaseWallet.test.ts new file mode 100644 index 0000000000..99b141e49b --- /dev/null +++ b/packages/connectors/src/coinbaseWallet.test.ts @@ -0,0 +1,17 @@ +import { config } from '@wagmi/test' +import { expect, expectTypeOf, test } from 'vitest' + +import { coinbaseWallet } from './coinbaseWallet.js' + +test('setup', () => { + const connectorFn = coinbaseWallet({ appName: 'wagmi', version: '4' }) + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('Coinbase Wallet') + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf< + boolean | undefined + >() +}) diff --git a/packages/connectors/src/coinbaseWallet.ts b/packages/connectors/src/coinbaseWallet.ts new file mode 100644 index 0000000000..630c91261e --- /dev/null +++ b/packages/connectors/src/coinbaseWallet.ts @@ -0,0 +1,546 @@ +import type { + Preference, + ProviderInterface, + createCoinbaseWalletSDK, +} from '@coinbase/wallet-sdk' +import { + ChainNotConfiguredError, + type Connector, + createConnector, +} from '@wagmi/core' +import type { Compute, Mutable, Omit } from '@wagmi/core/internal' +import type { + CoinbaseWalletProvider as CBW_Provider, + CoinbaseWalletSDK as CBW_SDK, +} from 'cbw-sdk' +import { + type AddEthereumChainParameter, + type Address, + type Hex, + type ProviderRpcError, + SwitchChainError, + UserRejectedRequestError, + getAddress, + numberToHex, +} from 'viem' + +type Version = '3' | '4' + +export type CoinbaseWalletParameters = + version extends '4' + ? Compute< + { + headlessMode?: false | undefined + /** Coinbase Wallet SDK version */ + version?: version | '3' | undefined + } & Version4Parameters + > + : Compute< + { + /** + * @deprecated `headlessMode` will be removed in the next major version. Upgrade to `version: '4'`. + */ + headlessMode?: true | undefined + /** + * Coinbase Wallet SDK version + * @deprecated Version 3 will be removed in the next major version. Upgrade to `version: '4'`. + * @default '4' + */ + version?: version | '4' | undefined + } & Version3Parameters + > + +coinbaseWallet.type = 'coinbaseWallet' as const +export function coinbaseWallet( + parameters: CoinbaseWalletParameters = {} as any, +): version extends '4' + ? ReturnType + : ReturnType { + if (parameters.version === '3' || parameters.headlessMode) + return version3(parameters as Version3Parameters) as any + return version4(parameters as Version4Parameters) as any +} + +type Version4Parameters = Mutable< + Omit< + Parameters[0], + | 'appChainIds' // set via wagmi config + | 'preference' + > & { + // TODO(v3): Remove `Preference['options']` + /** + * Preference for the type of wallet to display. + * @default 'all' + */ + preference?: Preference['options'] | Compute | undefined + } +> + +function version4(parameters: Version4Parameters) { + type Provider = ProviderInterface & { + // for backwards compatibility + close?(): void + } + type Properties = { + connect(parameters?: { + chainId?: number | undefined + instantOnboarding?: boolean | undefined + isReconnecting?: boolean | undefined + }): Promise<{ + accounts: readonly Address[] + chainId: number + }> + } + + let walletProvider: Provider | undefined + + let accountsChanged: Connector['onAccountsChanged'] | undefined + let chainChanged: Connector['onChainChanged'] | undefined + let disconnect: Connector['onDisconnect'] | undefined + + return createConnector((config) => ({ + id: 'coinbaseWalletSDK', + name: 'Coinbase Wallet', + rdns: 'com.coinbase.wallet', + type: coinbaseWallet.type, + async connect({ chainId, ...rest } = {}) { + try { + const provider = await this.getProvider() + const accounts = ( + (await provider.request({ + method: 'eth_requestAccounts', + params: + 'instantOnboarding' in rest && rest.instantOnboarding + ? [{ onboarding: 'instant' }] + : [], + })) as string[] + ).map((x) => getAddress(x)) + + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + + // Switch to chain if provided + let currentChainId = await this.getChainId() + if (chainId && currentChainId !== chainId) { + const chain = await this.switchChain!({ chainId }).catch((error) => { + if (error.code === UserRejectedRequestError.code) throw error + return { id: currentChainId } + }) + currentChainId = chain?.id ?? currentChainId + } + + return { accounts, chainId: currentChainId } + } catch (error) { + if ( + /(user closed modal|accounts received is empty|user denied account|request rejected)/i.test( + (error as Error).message, + ) + ) + throw new UserRejectedRequestError(error as Error) + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + + if (accountsChanged) { + provider.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + + provider.disconnect() + provider.close?.() + }, + async getAccounts() { + const provider = await this.getProvider() + return ( + (await provider.request({ + method: 'eth_accounts', + })) as string[] + ).map((x) => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const chainId = (await provider.request({ + method: 'eth_chainId', + })) as Hex + return Number(chainId) + }, + async getProvider() { + if (!walletProvider) { + const preference = (() => { + if (typeof parameters.preference === 'string') + return { options: parameters.preference } + return { + ...parameters.preference, + options: parameters.preference?.options ?? 'all', + } + })() + + const { createCoinbaseWalletSDK } = await import('@coinbase/wallet-sdk') + const sdk = createCoinbaseWalletSDK({ + ...parameters, + appChainIds: config.chains.map((x) => x.id), + preference, + }) + + walletProvider = sdk.getProvider() + } + + return walletProvider + }, + async isAuthorized() { + try { + const accounts = await this.getAccounts() + return !!accounts.length + } catch { + return false + } + }, + async switchChain({ addEthereumChainParameter, chainId }) { + const chain = config.chains.find((chain) => chain.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + const provider = await this.getProvider() + + try { + await provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chain.id) }], + }) + return chain + } catch (error) { + // Indicates chain is not added to provider + if ((error as ProviderRpcError).code === 4902) { + try { + let blockExplorerUrls: string[] | undefined + if (addEthereumChainParameter?.blockExplorerUrls) + blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls + else + blockExplorerUrls = chain.blockExplorers?.default.url + ? [chain.blockExplorers?.default.url] + : [] + + let rpcUrls: readonly string[] + if (addEthereumChainParameter?.rpcUrls?.length) + rpcUrls = addEthereumChainParameter.rpcUrls + else rpcUrls = [chain.rpcUrls.default?.http[0] ?? ''] + + const addEthereumChain = { + blockExplorerUrls, + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? + chain.nativeCurrency, + rpcUrls, + } satisfies AddEthereumChainParameter + + await provider.request({ + method: 'wallet_addEthereumChain', + params: [addEthereumChain], + }) + + return chain + } catch (error) { + throw new UserRejectedRequestError(error as Error) + } + } + + throw new SwitchChainError(error as Error) + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onDisconnect(_error) { + config.emitter.emit('disconnect') + + const provider = await this.getProvider() + if (accountsChanged) { + provider.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + }, + })) +} + +type Version3Parameters = Mutable< + Omit< + ConstructorParameters[0], + 'reloadOnDisconnect' // remove property since TSDoc says default is `true` + > +> & { + /** + * Fallback Ethereum JSON RPC URL + * @default "" + */ + jsonRpcUrl?: string | undefined + /** + * Fallback Ethereum Chain ID + * @default 1 + */ + chainId?: number | undefined + /** + * Whether or not to reload dapp automatically after disconnect. + * @default false + */ + reloadOnDisconnect?: boolean | undefined +} + +function version3(parameters: Version3Parameters) { + const reloadOnDisconnect = false + + type Provider = CBW_Provider + + let sdk: CBW_SDK | undefined + let walletProvider: Provider | undefined + + let accountsChanged: Connector['onAccountsChanged'] | undefined + let chainChanged: Connector['onChainChanged'] | undefined + let disconnect: Connector['onDisconnect'] | undefined + + return createConnector((config) => ({ + id: 'coinbaseWalletSDK', + name: 'Coinbase Wallet', + rdns: 'com.coinbase.wallet', + type: coinbaseWallet.type, + async connect({ chainId } = {}) { + try { + const provider = await this.getProvider() + const accounts = ( + (await provider.request({ + method: 'eth_requestAccounts', + })) as string[] + ).map((x) => getAddress(x)) + + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + + // Switch to chain if provided + let currentChainId = await this.getChainId() + if (chainId && currentChainId !== chainId) { + const chain = await this.switchChain!({ chainId }).catch((error) => { + if (error.code === UserRejectedRequestError.code) throw error + return { id: currentChainId } + }) + currentChainId = chain?.id ?? currentChainId + } + + return { accounts, chainId: currentChainId } + } catch (error) { + if ( + /(user closed modal|accounts received is empty|user denied account)/i.test( + (error as Error).message, + ) + ) + throw new UserRejectedRequestError(error as Error) + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + + if (accountsChanged) { + provider.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + + provider.disconnect() + provider.close() + }, + async getAccounts() { + const provider = await this.getProvider() + return ( + await provider.request({ + method: 'eth_accounts', + }) + ).map((x) => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const chainId = await provider.request({ + method: 'eth_chainId', + }) + return Number(chainId) + }, + async getProvider() { + if (!walletProvider) { + // Unwrapping import for Vite compatibility. + // See: https://github.com/vitejs/vite/issues/9703 + const CoinbaseWalletSDK = await (async () => { + const { default: SDK } = await import('cbw-sdk') + if (typeof SDK !== 'function' && typeof SDK.default === 'function') + return SDK.default + return SDK as unknown as typeof SDK.default + })() + + sdk = new CoinbaseWalletSDK({ ...parameters, reloadOnDisconnect }) + + // Force types to retrieve private `walletExtension` method from the Coinbase Wallet SDK. + const walletExtensionChainId = ( + sdk as unknown as { + get walletExtension(): { getChainId(): number } | undefined + } + ).walletExtension?.getChainId() + + const chain = + config.chains.find((chain) => + parameters.chainId + ? chain.id === parameters.chainId + : chain.id === walletExtensionChainId, + ) || config.chains[0] + const chainId = parameters.chainId || chain?.id + const jsonRpcUrl = + parameters.jsonRpcUrl || chain?.rpcUrls.default.http[0] + + walletProvider = sdk.makeWeb3Provider(jsonRpcUrl, chainId) + } + + return walletProvider + }, + async isAuthorized() { + try { + const accounts = await this.getAccounts() + return !!accounts.length + } catch { + return false + } + }, + async switchChain({ addEthereumChainParameter, chainId }) { + const chain = config.chains.find((chain) => chain.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + const provider = await this.getProvider() + + try { + await provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chain.id) }], + }) + return chain + } catch (error) { + // Indicates chain is not added to provider + if ((error as ProviderRpcError).code === 4902) { + try { + let blockExplorerUrls: string[] | undefined + if (addEthereumChainParameter?.blockExplorerUrls) + blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls + else + blockExplorerUrls = chain.blockExplorers?.default.url + ? [chain.blockExplorers?.default.url] + : [] + + let rpcUrls: readonly string[] + if (addEthereumChainParameter?.rpcUrls?.length) + rpcUrls = addEthereumChainParameter.rpcUrls + else rpcUrls = [chain.rpcUrls.default?.http[0] ?? ''] + + const addEthereumChain = { + blockExplorerUrls, + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? + chain.nativeCurrency, + rpcUrls, + } satisfies AddEthereumChainParameter + + await provider.request({ + method: 'wallet_addEthereumChain', + params: [addEthereumChain], + }) + + return chain + } catch (error) { + throw new UserRejectedRequestError(error as Error) + } + } + + throw new SwitchChainError(error as Error) + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onDisconnect(_error) { + config.emitter.emit('disconnect') + + const provider = await this.getProvider() + if (accountsChanged) { + provider.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + }, + })) +} diff --git a/packages/connectors/src/exports/index.test.ts b/packages/connectors/src/exports/index.test.ts new file mode 100644 index 0000000000..bcf100cb61 --- /dev/null +++ b/packages/connectors/src/exports/index.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' + +import * as connectors from './index.js' + +test('exports', () => { + expect(Object.keys(connectors)).toMatchInlineSnapshot(` + [ + "injected", + "mock", + "coinbaseWallet", + "metaMask", + "safe", + "walletConnect", + "version", + ] + `) +}) diff --git a/packages/connectors/src/exports/index.ts b/packages/connectors/src/exports/index.ts new file mode 100644 index 0000000000..bac0975956 --- /dev/null +++ b/packages/connectors/src/exports/index.ts @@ -0,0 +1,23 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type InjectedParameters, + injected, + type MockParameters, + mock, +} from '@wagmi/core' + +export { + type CoinbaseWalletParameters, + coinbaseWallet, +} from '../coinbaseWallet.js' + +export { type MetaMaskParameters, metaMask } from '../metaMask.js' + +export { type SafeParameters, safe } from '../safe.js' + +export { + type WalletConnectParameters, + walletConnect, +} from '../walletConnect.js' + +export { version } from '../version.js' diff --git a/packages/connectors/src/metaMask.test.ts b/packages/connectors/src/metaMask.test.ts new file mode 100644 index 0000000000..40c3f0f7f9 --- /dev/null +++ b/packages/connectors/src/metaMask.test.ts @@ -0,0 +1,10 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { metaMask } from './metaMask.js' + +test('setup', () => { + const connectorFn = metaMask() + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('MetaMask') +}) diff --git a/packages/connectors/src/metaMask.ts b/packages/connectors/src/metaMask.ts new file mode 100644 index 0000000000..02ab4c3fb3 --- /dev/null +++ b/packages/connectors/src/metaMask.ts @@ -0,0 +1,505 @@ +import type { + MetaMaskSDK, + MetaMaskSDKOptions, + RPC_URLS_MAP, + SDKProvider, +} from '@metamask/sdk' +import { + ChainNotConfiguredError, + type Connector, + ProviderNotFoundError, + createConnector, + extractRpcUrls, +} from '@wagmi/core' +import type { + Compute, + ExactPartial, + OneOf, + RemoveUndefined, + UnionCompute, +} from '@wagmi/core/internal' +import { + type AddEthereumChainParameter, + type Address, + type Hex, + type ProviderConnectInfo, + type ProviderRpcError, + ResourceUnavailableRpcError, + type RpcError, + SwitchChainError, + UserRejectedRequestError, + getAddress, + hexToNumber, + numberToHex, + withRetry, + withTimeout, +} from 'viem' + +export type MetaMaskParameters = UnionCompute< + WagmiMetaMaskSDKOptions & + OneOf< + | { + /* Shortcut to connect and sign a message */ + connectAndSign?: string | undefined + } + | { + // TODO: Strongly type `method` and `params` + /* Allow `connectWith` any rpc method */ + connectWith?: { method: string; params: unknown[] } | undefined + } + > +> + +type WagmiMetaMaskSDKOptions = Compute< + ExactPartial< + Omit< + MetaMaskSDKOptions, + | '_source' + | 'forceDeleteProvider' + | 'forceInjectProvider' + | 'injectProvider' + | 'useDeeplink' + | 'readonlyRPCMap' + > + > & { + /** @deprecated */ + forceDeleteProvider?: MetaMaskSDKOptions['forceDeleteProvider'] + /** @deprecated */ + forceInjectProvider?: MetaMaskSDKOptions['forceInjectProvider'] + /** @deprecated */ + injectProvider?: MetaMaskSDKOptions['injectProvider'] + /** @deprecated */ + useDeeplink?: MetaMaskSDKOptions['useDeeplink'] + } +> + +metaMask.type = 'metaMask' as const +export function metaMask(parameters: MetaMaskParameters = {}) { + type Provider = SDKProvider + type Properties = { + onConnect(connectInfo: ProviderConnectInfo): void + onDisplayUri(uri: string): void + } + type Listener = Parameters[1] + + let sdk: MetaMaskSDK + let provider: Provider | undefined + let providerPromise: Promise + + let accountsChanged: Connector['onAccountsChanged'] | undefined + let chainChanged: Connector['onChainChanged'] | undefined + let connect: Connector['onConnect'] | undefined + let displayUri: ((uri: string) => void) | undefined + let disconnect: Connector['onDisconnect'] | undefined + + return createConnector((config) => ({ + id: 'metaMaskSDK', + name: 'MetaMask', + rdns: ['io.metamask', 'io.metamask.mobile'], + type: metaMask.type, + async setup() { + const provider = await this.getProvider() + if (provider?.on) { + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect as Listener) + } + + // We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet). + // Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead. + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged as Listener) + } + } + }, + async connect({ chainId, isReconnecting } = {}) { + const provider = await this.getProvider() + if (!displayUri) { + displayUri = this.onDisplayUri + provider.on('display_uri', displayUri as Listener) + } + + let accounts: readonly Address[] = [] + if (isReconnecting) accounts = await this.getAccounts().catch(() => []) + + try { + let signResponse: string | undefined + let connectWithResponse: unknown | undefined + if (!accounts?.length) { + if (parameters.connectAndSign || parameters.connectWith) { + if (parameters.connectAndSign) + signResponse = await sdk.connectAndSign({ + msg: parameters.connectAndSign, + }) + else if (parameters.connectWith) + connectWithResponse = await sdk.connectWith({ + method: parameters.connectWith.method, + params: parameters.connectWith.params, + }) + + accounts = await this.getAccounts() + } else { + const requestedAccounts = (await sdk.connect()) as string[] + accounts = requestedAccounts.map((x) => getAddress(x)) + } + } + // Switch to chain if provided + let currentChainId = (await this.getChainId()) as number + if (chainId && currentChainId !== chainId) { + const chain = await this.switchChain!({ chainId }).catch((error) => { + if (error.code === UserRejectedRequestError.code) throw error + return { id: currentChainId } + }) + currentChainId = chain?.id ?? currentChainId + } + + if (displayUri) { + provider.removeListener('display_uri', displayUri) + displayUri = undefined + } + + if (signResponse) + provider.emit('connectAndSign', { + accounts, + chainId: currentChainId, + signResponse, + }) + else if (connectWithResponse) + provider.emit('connectWith', { + accounts, + chainId: currentChainId, + connectWithResponse, + }) + + // Manage EIP-1193 event listeners + // https://eips.ethereum.org/EIPS/eip-1193#events + if (connect) { + provider.removeListener('connect', connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged as Listener) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged as Listener) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect as Listener) + } + + return { accounts, chainId: currentChainId } + } catch (err) { + const error = err as RpcError + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + if (error.code === ResourceUnavailableRpcError.code) + throw new ResourceUnavailableRpcError(error) + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + + // Manage EIP-1193 event listeners + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect as Listener) + } + + await sdk.terminate() + }, + async getAccounts() { + const provider = await this.getProvider() + const accounts = (await provider.request({ + method: 'eth_accounts', + })) as string[] + return accounts.map((x) => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const chainId = + provider.getChainId() || + (await provider?.request({ method: 'eth_chainId' })) + return Number(chainId) + }, + async getProvider() { + async function initProvider() { + // Unwrapping import for Vite compatibility. + // See: https://github.com/vitejs/vite/issues/9703 + const MetaMaskSDK = await (async () => { + const { default: SDK } = await import('@metamask/sdk') + if (typeof SDK !== 'function' && typeof SDK.default === 'function') + return SDK.default + return SDK as unknown as typeof SDK.default + })() + + const readonlyRPCMap: RPC_URLS_MAP = {} + for (const chain of config.chains) + readonlyRPCMap[numberToHex(chain.id)] = extractRpcUrls({ + chain, + transports: config.transports, + })?.[0] + + sdk = new MetaMaskSDK({ + _source: 'wagmi', + forceDeleteProvider: false, + forceInjectProvider: false, + injectProvider: false, + // Workaround cast since MetaMask SDK does not support `'exactOptionalPropertyTypes'` + ...(parameters as RemoveUndefined), + readonlyRPCMap, + dappMetadata: { + ...parameters.dappMetadata, + // Test if name and url are set AND not empty + name: parameters.dappMetadata?.name + ? parameters.dappMetadata?.name + : 'wagmi', + url: parameters.dappMetadata?.url + ? parameters.dappMetadata?.url + : typeof window !== 'undefined' + ? window.location.origin + : 'https://wagmi.sh', + }, + useDeeplink: parameters.useDeeplink ?? true, + }) + const result = await sdk.init() + // On initial load, sometimes `sdk.getProvider` does not return provider. + // https://github.com/wevm/wagmi/issues/4367 + // Use result of `init` call if available. + const provider = (() => { + if (result?.activeProvider) return result.activeProvider + return sdk.getProvider() + })() + if (!provider) throw new ProviderNotFoundError() + return provider + } + + if (!provider) { + if (!providerPromise) providerPromise = initProvider() + provider = await providerPromise + } + return provider! + }, + async isAuthorized() { + try { + // MetaMask mobile provider sometimes fails to immediately resolve + // JSON-RPC requests on page load + const timeout = 200 + const accounts = await withRetry( + () => withTimeout(() => this.getAccounts(), { timeout }), + { + delay: timeout + 1, + retryCount: 3, + }, + ) + return !!accounts.length + } catch { + return false + } + }, + async switchChain({ addEthereumChainParameter, chainId }) { + const provider = await this.getProvider() + + const chain = config.chains.find((x) => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + try { + await provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }], + }) + + // During `'wallet_switchEthereumChain'`, MetaMask makes a `'net_version'` RPC call to the target chain. + // If this request fails, MetaMask does not emit the `'chainChanged'` event, but will still switch the chain. + // To counter this behavior, we request and emit the current chain ID to confirm the chain switch either via + // this callback or an externally emitted `'chainChanged'` event. + // https://github.com/MetaMask/metamask-extension/issues/24247 + await waitForChainIdToSync() + await sendAndWaitForChangeEvent(chainId) + + return chain + } catch (err) { + const error = err as RpcError + + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + + // Indicates chain is not added to provider + if ( + error.code === 4902 || + // Unwrapping for MetaMask Mobile + // https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719 + (error as ProviderRpcError<{ originalError?: { code: number } }>) + ?.data?.originalError?.code === 4902 + ) { + try { + await provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + blockExplorerUrls: (() => { + const { default: blockExplorer, ...blockExplorers } = + chain.blockExplorers ?? {} + if (addEthereumChainParameter?.blockExplorerUrls) + return addEthereumChainParameter.blockExplorerUrls + if (blockExplorer) + return [ + blockExplorer.url, + ...Object.values(blockExplorers).map((x) => x.url), + ] + return + })(), + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? + chain.nativeCurrency, + rpcUrls: (() => { + if (addEthereumChainParameter?.rpcUrls?.length) + return addEthereumChainParameter.rpcUrls + return [chain.rpcUrls.default?.http[0] ?? ''] + })(), + } satisfies AddEthereumChainParameter, + ], + }) + + await waitForChainIdToSync() + await sendAndWaitForChangeEvent(chainId) + + return chain + } catch (err) { + const error = err as RpcError + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + throw new SwitchChainError(error) + } + } + + throw new SwitchChainError(error) + } + + async function waitForChainIdToSync() { + // On mobile, there is a race condition between the result of `'wallet_addEthereumChain'` and `'eth_chainId'`. + // To avoid this, we wait for `'eth_chainId'` to return the expected chain ID with a retry loop. + await withRetry( + async () => { + const value = hexToNumber( + // `'eth_chainId'` is cached by the MetaMask SDK side to avoid unnecessary deeplinks + (await provider.request({ method: 'eth_chainId' })) as Hex, + ) + // `value` doesn't match expected `chainId`, throw to trigger retry + if (value !== chainId) + throw new Error('User rejected switch after adding network.') + return value + }, + { + delay: 50, + retryCount: 20, // android device encryption is slower + }, + ) + } + + async function sendAndWaitForChangeEvent(chainId: number) { + await new Promise((resolve) => { + const listener = ((data) => { + if ('chainId' in data && data.chainId === chainId) { + config.emitter.off('change', listener) + resolve() + } + }) satisfies Parameters[1] + config.emitter.on('change', listener) + config.emitter.emit('change', { chainId }) + }) + } + }, + async onAccountsChanged(accounts) { + // Disconnect if there are no accounts + if (accounts.length === 0) { + // ... and using browser extension + if (sdk.isExtensionActive()) this.onDisconnect() + // FIXME(upstream): Mobile app sometimes emits invalid `accountsChanged` event with empty accounts array + else return + } + // Connect if emitter is listening for connect event (e.g. is disconnected and connects through wallet interface) + else if (config.emitter.listenerCount('connect')) { + const chainId = (await this.getChainId()).toString() + this.onConnect({ chainId }) + } + // Regular change event + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onConnect(connectInfo) { + const accounts = await this.getAccounts() + if (accounts.length === 0) return + + const chainId = Number(connectInfo.chainId) + config.emitter.emit('connect', { accounts, chainId }) + + const provider = await this.getProvider() + if (connect) { + provider.removeListener('connect', connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged as Listener) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged as Listener) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect as Listener) + } + }, + async onDisconnect(error) { + const provider = await this.getProvider() + + // If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting + // https://github.com/MetaMask/providers/pull/120 + if (error && (error as RpcError<1013>).code === 1013) { + if (provider && !!(await this.getAccounts()).length) return + } + + config.emitter.emit('disconnect') + + // Manage EIP-1193 event listeners + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect as Listener) + } + }, + onDisplayUri(uri) { + config.emitter.emit('message', { type: 'display_uri', data: uri }) + }, + })) +} diff --git a/packages/connectors/src/safe.test.ts b/packages/connectors/src/safe.test.ts new file mode 100644 index 0000000000..0571115f36 --- /dev/null +++ b/packages/connectors/src/safe.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { safe } from './safe.js' + +/* + * To manually test the Safe connector: + * + * 1. Run the wagmi playground app (`pnpm dev`) + * 2. Add a custom Safe App with App URL set to `http://localhost:5173` (make sure there is a `manifest.json` file served by the playground) + * 3. Open the playground app at `https://app.safe.global/eth:0x4557B18E779944BFE9d78A672452331C186a9f48/apps?appUrl=http%3A%2F%2Flocalhost%3A5173` + * + * See https://docs.gnosis-safe.io/learn/safe-tools/sdks/safe-apps/releasing-your-safe-app for more info. + */ + +test('setup', () => { + const connectorFn = safe({ + allowedDomains: [/gnosis-safe.io$/, /app.safe.global$/], + debug: false, + }) + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('Safe') +}) diff --git a/packages/connectors/src/safe.ts b/packages/connectors/src/safe.ts new file mode 100644 index 0000000000..13153e106f --- /dev/null +++ b/packages/connectors/src/safe.ts @@ -0,0 +1,145 @@ +import type { SafeAppProvider } from '@safe-global/safe-apps-provider' +import type { Opts } from '@safe-global/safe-apps-sdk' +import { + type Connector, + ProviderNotFoundError, + createConnector, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { getAddress, withTimeout } from 'viem' + +export type SafeParameters = Compute< + Opts & { + /** + * Connector automatically connects when used as Safe App. + * + * This flag simulates the disconnect behavior by keeping track of connection status in storage + * and only autoconnecting when previously connected by user action (e.g. explicitly choosing to connect). + * + * @default false + */ + shimDisconnect?: boolean | undefined + /** + * Timeout in milliseconds for `getInfo` (from the Safe SDK) to resolve. + * + * `getInfo` does not resolve when not used in Safe App iFrame. This allows the connector to force a timeout. + * @default 10 + */ + unstable_getInfoTimeout?: number | undefined + } +> + +safe.type = 'safe' as const +export function safe(parameters: SafeParameters = {}) { + const { shimDisconnect = false } = parameters + + type Provider = SafeAppProvider | undefined + type Properties = Record + type StorageItem = { 'safe.disconnected': true } + + let provider_: Provider | undefined + + let disconnect: Connector['onDisconnect'] | undefined + + return createConnector((config) => ({ + id: 'safe', + name: 'Safe', + type: safe.type, + async connect() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + const accounts = await this.getAccounts() + const chainId = await this.getChainId() + + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + + // Remove disconnected shim if it exists + if (shimDisconnect) await config.storage?.removeItem('safe.disconnected') + + return { accounts, chainId } + }, + async disconnect() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + + // Add shim signalling connector is disconnected + if (shimDisconnect) + await config.storage?.setItem('safe.disconnected', true) + }, + async getAccounts() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + return (await provider.request({ method: 'eth_accounts' })).map( + getAddress, + ) + }, + async getProvider() { + // Only allowed in iframe context + const isIframe = + typeof window !== 'undefined' && window?.parent !== window + if (!isIframe) return + + if (!provider_) { + const { default: SDK } = await import('@safe-global/safe-apps-sdk') + const sdk = new SDK(parameters) + + // `getInfo` hangs when not used in Safe App iFrame + // https://github.com/safe-global/safe-apps-sdk/issues/263#issuecomment-1029835840 + const safe = await withTimeout(() => sdk.safe.getInfo(), { + timeout: parameters.unstable_getInfoTimeout ?? 10, + }) + if (!safe) throw new Error('Could not load Safe information') + // Unwrapping import for Vite compatibility. + // See: https://github.com/vitejs/vite/issues/9703 + const SafeAppProvider = await (async () => { + const Provider = await import('@safe-global/safe-apps-provider') + if ( + typeof Provider.SafeAppProvider !== 'function' && + typeof Provider.default.SafeAppProvider === 'function' + ) + return Provider.default.SafeAppProvider + return Provider.SafeAppProvider + })() + provider_ = new SafeAppProvider(safe, sdk) + } + return provider_ + }, + async getChainId() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + return Number(provider.chainId) + }, + async isAuthorized() { + try { + const isDisconnected = + shimDisconnect && + // If shim exists in storage, connector is disconnected + (await config.storage?.getItem('safe.disconnected')) + if (isDisconnected) return false + + const accounts = await this.getAccounts() + return !!accounts.length + } catch { + return false + } + }, + onAccountsChanged() { + // Not relevant for Safe because changing account requires app reload. + }, + onChainChanged() { + // Not relevant for Safe because Safe smart contract wallets only exist on single chain. + }, + onDisconnect() { + config.emitter.emit('disconnect') + }, + })) +} diff --git a/packages/connectors/src/version.ts b/packages/connectors/src/version.ts new file mode 100644 index 0000000000..11f81d1c34 --- /dev/null +++ b/packages/connectors/src/version.ts @@ -0,0 +1 @@ +export const version = '5.8.3' diff --git a/packages/connectors/src/walletConnect.test.ts b/packages/connectors/src/walletConnect.test.ts new file mode 100644 index 0000000000..4e8a74ebfe --- /dev/null +++ b/packages/connectors/src/walletConnect.test.ts @@ -0,0 +1,67 @@ +import { config, walletConnectProjectId } from '@wagmi/test' +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' +import { + afterAll, + afterEach, + beforeAll, + expect, + expectTypeOf, + test, + vi, +} from 'vitest' + +import { walletConnect } from './walletConnect.js' + +const handlers = [ + http.get('https://relay.walletconnect.com', async () => + HttpResponse.json( + { + topic: '222781e3-3fad-4184-acde-077796bf0d3d', + type: 'sub', + payload: '', + silent: true, + }, + { status: 200 }, + ), + ), +] + +const server = setupServer(...handlers) + +beforeAll(() => { + server.listen({ + onUnhandledRequest: 'warn', + }) + + const matchMedia = vi.fn().mockImplementation((query) => { + return { + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + } + }) + vi.stubGlobal('matchMedia', matchMedia) +}) + +afterEach(() => server.resetHandlers()) + +afterAll(() => server.close()) + +test('setup', () => { + const connectorFn = walletConnect({ projectId: walletConnectProjectId }) + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('WalletConnect') + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf< + string | undefined + >() +}) diff --git a/packages/connectors/src/walletConnect.ts b/packages/connectors/src/walletConnect.ts new file mode 100644 index 0000000000..fc4f794c1f --- /dev/null +++ b/packages/connectors/src/walletConnect.ts @@ -0,0 +1,468 @@ +import { + ChainNotConfiguredError, + type Connector, + ProviderNotFoundError, + createConnector, + extractRpcUrls, +} from '@wagmi/core' +import type { Compute, ExactPartial, Omit } from '@wagmi/core/internal' +import type { EthereumProvider } from '@walletconnect/ethereum-provider' +import { + type AddEthereumChainParameter, + type Address, + type ProviderConnectInfo, + type ProviderRpcError, + type RpcError, + SwitchChainError, + UserRejectedRequestError, + getAddress, + numberToHex, +} from 'viem' + +type WalletConnectConnector = Connector & { + onDisplayUri(uri: string): void + onSessionDelete(data: { topic: string }): void +} + +type EthereumProviderOptions = Parameters<(typeof EthereumProvider)['init']>[0] + +export type WalletConnectParameters = Compute< + { + /** + * If a new chain is added to a previously existing configured connector `chains`, this flag + * will determine if that chain should be considered as stale. A stale chain is a chain that + * WalletConnect has yet to establish a relationship with (e.g. the user has not approved or + * rejected the chain). + * + * This flag mainly affects the behavior when a wallet does not support dynamic chain authorization + * with WalletConnect v2. + * + * If `true` (default), the new chain will be treated as a stale chain. If the user + * has yet to establish a relationship (approved/rejected) with this chain in their WalletConnect + * session, the connector will disconnect upon the dapp auto-connecting, and the user will have to + * reconnect to the dapp (revalidate the chain) in order to approve the newly added chain. + * This is the default behavior to avoid an unexpected error upon switching chains which may + * be a confusing user experience (e.g. the user will not know they have to reconnect + * unless the dapp handles these types of errors). + * + * If `false`, the new chain will be treated as a potentially valid chain. This means that if the user + * has yet to establish a relationship with the chain in their WalletConnect session, wagmi will successfully + * auto-connect the user. This comes with the trade-off that the connector will throw an error + * when attempting to switch to the unapproved chain if the wallet does not support dynamic session updates. + * This may be useful in cases where a dapp constantly + * modifies their configured chains, and they do not want to disconnect the user upon + * auto-connecting. If the user decides to switch to the unapproved chain, it is important that the + * dapp handles this error and prompts the user to reconnect to the dapp in order to approve + * the newly added chain. + * + * @default true + */ + isNewChainsStale?: boolean + } & Omit< + EthereumProviderOptions, + | 'chains' + | 'events' + | 'optionalChains' + | 'optionalEvents' + | 'optionalMethods' + | 'methods' + | 'rpcMap' + | 'showQrModal' + > & + ExactPartial> +> + +walletConnect.type = 'walletConnect' as const +export function walletConnect(parameters: WalletConnectParameters) { + const isNewChainsStale = parameters.isNewChainsStale ?? true + + type Provider = Awaited> + type Properties = { + connect(parameters?: { + chainId?: number | undefined + isReconnecting?: boolean | undefined + pairingTopic?: string | undefined + }): Promise<{ + accounts: readonly Address[] + chainId: number + }> + getNamespaceChainsIds(): number[] + getRequestedChainsIds(): Promise + isChainsStale(): Promise + onConnect(connectInfo: ProviderConnectInfo): void + onDisplayUri(uri: string): void + onSessionDelete(data: { topic: string }): void + setRequestedChainsIds(chains: number[]): void + requestedChainsStorageKey: `${string}.requestedChains` + } + type StorageItem = { + [_ in Properties['requestedChainsStorageKey']]: number[] + } + + let provider_: Provider | undefined + let providerPromise: Promise + const NAMESPACE = 'eip155' + + let accountsChanged: WalletConnectConnector['onAccountsChanged'] | undefined + let chainChanged: WalletConnectConnector['onChainChanged'] | undefined + let connect: WalletConnectConnector['onConnect'] | undefined + let displayUri: WalletConnectConnector['onDisplayUri'] | undefined + let sessionDelete: WalletConnectConnector['onSessionDelete'] | undefined + let disconnect: WalletConnectConnector['onDisconnect'] | undefined + + return createConnector((config) => ({ + id: 'walletConnect', + name: 'WalletConnect', + type: walletConnect.type, + async setup() { + const provider = await this.getProvider().catch(() => null) + if (!provider) return + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect) + } + if (!sessionDelete) { + sessionDelete = this.onSessionDelete.bind(this) + provider.on('session_delete', sessionDelete) + } + }, + async connect({ chainId, ...rest } = {}) { + try { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + if (!displayUri) { + displayUri = this.onDisplayUri + provider.on('display_uri', displayUri) + } + + let targetChainId = chainId + if (!targetChainId) { + const state = (await config.storage?.getItem('state')) ?? {} + const isChainSupported = config.chains.some( + (x) => x.id === state.chainId, + ) + if (isChainSupported) targetChainId = state.chainId + else targetChainId = config.chains[0]?.id + } + if (!targetChainId) throw new Error('No chains found on connector.') + + const isChainsStale = await this.isChainsStale() + // If there is an active session with stale chains, disconnect current session. + if (provider.session && isChainsStale) await provider.disconnect() + + // If there isn't an active session or chains are stale, connect. + if (!provider.session || isChainsStale) { + const optionalChains = config.chains + .filter((chain) => chain.id !== targetChainId) + .map((optionalChain) => optionalChain.id) + await provider.connect({ + optionalChains: [targetChainId, ...optionalChains], + ...('pairingTopic' in rest + ? { pairingTopic: rest.pairingTopic } + : {}), + }) + + this.setRequestedChainsIds(config.chains.map((x) => x.id)) + } + + // If session exists and chains are authorized, enable provider for required chain + const accounts = (await provider.enable()).map((x) => getAddress(x)) + const currentChainId = await this.getChainId() + + if (displayUri) { + provider.removeListener('display_uri', displayUri) + displayUri = undefined + } + if (connect) { + provider.removeListener('connect', connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + if (!sessionDelete) { + sessionDelete = this.onSessionDelete.bind(this) + provider.on('session_delete', sessionDelete) + } + + return { accounts, chainId: currentChainId } + } catch (error) { + if ( + /(user rejected|connection request reset)/i.test( + (error as ProviderRpcError)?.message, + ) + ) { + throw new UserRejectedRequestError(error as Error) + } + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + try { + await provider?.disconnect() + } catch (error) { + if (!/No matching key/i.test((error as Error).message)) throw error + } finally { + if (chainChanged) { + provider?.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider?.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider?.on('connect', connect) + } + if (accountsChanged) { + provider?.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (sessionDelete) { + provider?.removeListener('session_delete', sessionDelete) + sessionDelete = undefined + } + + this.setRequestedChainsIds([]) + } + }, + async getAccounts() { + const provider = await this.getProvider() + return provider.accounts.map((x) => getAddress(x)) + }, + async getProvider({ chainId } = {}) { + async function initProvider() { + const optionalChains = config.chains.map((x) => x.id) as [number] + if (!optionalChains.length) return + const { EthereumProvider } = await import( + '@walletconnect/ethereum-provider' + ) + return await EthereumProvider.init({ + ...parameters, + disableProviderPing: true, + optionalChains, + projectId: parameters.projectId, + rpcMap: Object.fromEntries( + config.chains.map((chain) => { + const [url] = extractRpcUrls({ + chain, + transports: config.transports, + }) + return [chain.id, url] + }), + ), + showQrModal: parameters.showQrModal ?? true, + }) + } + + if (!provider_) { + if (!providerPromise) providerPromise = initProvider() + provider_ = await providerPromise + provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY) + } + if (chainId) await this.switchChain?.({ chainId }) + return provider_! + }, + async getChainId() { + const provider = await this.getProvider() + return provider.chainId + }, + async isAuthorized() { + try { + const [accounts, provider] = await Promise.all([ + this.getAccounts(), + this.getProvider(), + ]) + + // If an account does not exist on the session, then the connector is unauthorized. + if (!accounts.length) return false + + // If the chains are stale on the session, then the connector is unauthorized. + const isChainsStale = await this.isChainsStale() + if (isChainsStale && provider.session) { + await provider.disconnect().catch(() => {}) + return false + } + return true + } catch { + return false + } + }, + async switchChain({ addEthereumChainParameter, chainId }) { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + const chain = config.chains.find((x) => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + try { + await Promise.all([ + new Promise((resolve) => { + const listener = ({ + chainId: currentChainId, + }: { chainId?: number | undefined }) => { + if (currentChainId === chainId) { + config.emitter.off('change', listener) + resolve() + } + } + config.emitter.on('change', listener) + }), + provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }], + }), + ]) + + const requestedChains = await this.getRequestedChainsIds() + this.setRequestedChainsIds([...requestedChains, chainId]) + + return chain + } catch (err) { + const error = err as RpcError + + if (/(user rejected)/i.test(error.message)) + throw new UserRejectedRequestError(error) + + // Indicates chain is not added to provider + try { + let blockExplorerUrls: string[] | undefined + if (addEthereumChainParameter?.blockExplorerUrls) + blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls + else + blockExplorerUrls = chain.blockExplorers?.default.url + ? [chain.blockExplorers?.default.url] + : [] + + let rpcUrls: readonly string[] + if (addEthereumChainParameter?.rpcUrls?.length) + rpcUrls = addEthereumChainParameter.rpcUrls + else rpcUrls = [...chain.rpcUrls.default.http] + + const addEthereumChain = { + blockExplorerUrls, + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency, + rpcUrls, + } satisfies AddEthereumChainParameter + + await provider.request({ + method: 'wallet_addEthereumChain', + params: [addEthereumChain], + }) + + const requestedChains = await this.getRequestedChainsIds() + this.setRequestedChainsIds([...requestedChains, chainId]) + return chain + } catch (error) { + throw new UserRejectedRequestError(error as Error) + } + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onConnect(connectInfo) { + const chainId = Number(connectInfo.chainId) + const accounts = await this.getAccounts() + config.emitter.emit('connect', { accounts, chainId }) + }, + async onDisconnect(_error) { + this.setRequestedChainsIds([]) + config.emitter.emit('disconnect') + + const provider = await this.getProvider() + if (accountsChanged) { + provider.removeListener('accountsChanged', accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (sessionDelete) { + provider.removeListener('session_delete', sessionDelete) + sessionDelete = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect) + } + }, + onDisplayUri(uri) { + config.emitter.emit('message', { type: 'display_uri', data: uri }) + }, + onSessionDelete() { + this.onDisconnect() + }, + getNamespaceChainsIds() { + if (!provider_) return [] + const chainIds = provider_.session?.namespaces[NAMESPACE]?.accounts?.map( + (account) => Number.parseInt(account.split(':')[1] || ''), + ) + return chainIds ?? [] + }, + async getRequestedChainsIds() { + return ( + (await config.storage?.getItem(this.requestedChainsStorageKey)) ?? [] + ) + }, + /** + * Checks if the target chains match the chains that were + * initially requested by the connector for the WalletConnect session. + * If there is a mismatch, this means that the chains on the connector + * are considered stale, and need to be revalidated at a later point (via + * connection). + * + * There may be a scenario where a dapp adds a chain to the + * connector later on, however, this chain will not have been approved or rejected + * by the wallet. In this case, the chain is considered stale. + */ + async isChainsStale() { + if (!isNewChainsStale) return false + + const connectorChains = config.chains.map((x) => x.id) + const namespaceChains = this.getNamespaceChainsIds() + if ( + namespaceChains.length && + !namespaceChains.some((id) => connectorChains.includes(id)) + ) + return false + + const requestedChains = await this.getRequestedChainsIds() + return !connectorChains.every((id) => requestedChains.includes(id)) + }, + async setRequestedChainsIds(chains) { + await config.storage?.setItem(this.requestedChainsStorageKey, chains) + }, + get requestedChainsStorageKey() { + return `${this.id}.requestedChains` as Properties['requestedChainsStorageKey'] + }, + })) +} diff --git a/packages/connectors/tsconfig.build.json b/packages/connectors/tsconfig.build.json new file mode 100644 index 0000000000..fbed2b1036 --- /dev/null +++ b/packages/connectors/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/connectors/tsconfig.json b/packages/connectors/tsconfig.json new file mode 100644 index 0000000000..bd33919ac3 --- /dev/null +++ b/packages/connectors/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md new file mode 100644 index 0000000000..88b541e93e --- /dev/null +++ b/packages/core/CHANGELOG.md @@ -0,0 +1,3365 @@ +# @wagmi/core + +## 2.17.2 + +### Patch Changes + +- [`29297a48af72b537173d948ccd2fe37d39914c66`](https://github.com/wevm/wagmi/commit/29297a48af72b537173d948ccd2fe37d39914c66) Thanks [@jxom](https://github.com/jxom)! - Fixed `sendCalls` generics. + +- [`07370106d5fb6b8fe300992d93abf25b3d0eaf57`](https://github.com/wevm/wagmi/commit/07370106d5fb6b8fe300992d93abf25b3d0eaf57) Thanks [@jxom](https://github.com/jxom)! - Fixed propagation of `waitForCallsStatus` parameters. + +## 2.17.1 + +### Patch Changes + +- [#4649](https://github.com/wevm/wagmi/pull/4649) [`01f64e64fa4f85cdd30023903f972f4f9023681f`](https://github.com/wevm/wagmi/commit/01f64e64fa4f85cdd30023903f972f4f9023681f) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` parameter to `getCapabilities`/`useCapabilities`. + +## 2.17.0 + +### Minor Changes + +- [#4638](https://github.com/wevm/wagmi/pull/4638) [`799ee4d4b23c2ecd64e3f3668e67634e81939719`](https://github.com/wevm/wagmi/commit/799ee4d4b23c2ecd64e3f3668e67634e81939719) Thanks [@jxom](https://github.com/jxom)! - Stabilized EIP-5792 Actions & Hooks. + +## 2.16.7 + +### Patch Changes + +- [`a4bd0623eed28e3761a27295831a60ad835f0ee0`](https://github.com/wevm/wagmi/commit/a4bd0623eed28e3761a27295831a60ad835f0ee0) Thanks [@jxom](https://github.com/jxom)! - **Experimental (EIP-5792):** Updated `id` parameter to be optional on `useWaitForCallsStatus`. + +## 2.16.6 + +### Patch Changes + +- [#4586](https://github.com/wevm/wagmi/pull/4586) [`edf47477b2f6385a1c3ae01d36a8498c47f30a0b`](https://github.com/wevm/wagmi/commit/edf47477b2f6385a1c3ae01d36a8498c47f30a0b) Thanks [@jxom](https://github.com/jxom)! - **Experimental (EIP-5792):** Added `waitForCallsStatus` + `useWaitForCallsStatus`. + +## 2.16.5 + +### Patch Changes + +- [`d0c9a86921a4e939373cc6e763284e53f2a2e93c`](https://github.com/wevm/wagmi/commit/d0c9a86921a4e939373cc6e763284e53f2a2e93c) Thanks [@jxom](https://github.com/jxom)! - **Experimental (ERC-5792)**: Added support for `account: null` in `sendCalls` to cater for sending calls without a connected account (account will be filled by the wallet). + +## 2.16.4 + +### Patch Changes + +- [`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec) Thanks [@jxom](https://github.com/jxom)! - Updated `@coinbase/wallet-sdk`. + +## 2.16.3 + +### Patch Changes + +- [#4480](https://github.com/wevm/wagmi/pull/4480) [`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8) Thanks [@RodeRickIsWatching](https://github.com/RodeRickIsWatching)! - Fixed invocation of default storage. + +## 2.16.2 + +### Patch Changes + +- [`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6) Thanks [@jxom](https://github.com/jxom)! - Fixed assignment in `getDefaultStorage`. + +## 2.16.1 + +### Patch Changes + +- [#4472](https://github.com/wevm/wagmi/pull/4472) [`3892ebd21c06beef4b28ece4e70d2a38807bce6f`](https://github.com/wevm/wagmi/commit/3892ebd21c06beef4b28ece4e70d2a38807bce6f) Thanks [@tmm](https://github.com/tmm)! - Added handling to default storage for `setItem` errors, like `QuotaExceededError`, `SecurityError`, etc. + +## 2.16.0 + +### Minor Changes + +- [#4453](https://github.com/wevm/wagmi/pull/4453) [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227) Thanks [@tmm](https://github.com/tmm)! - Added narrowing to `config.connectors`. + +### Patch Changes + +- [`afea6b67822a7a2b96901ec851441d27ee0f7a52`](https://github.com/wevm/wagmi/commit/afea6b67822a7a2b96901ec851441d27ee0f7a52) Thanks [@tmm](https://github.com/tmm)! - Passed through parameters to `connector.connect` in `connect` action. + +## 2.15.2 + +### Patch Changes + +- [#4433](https://github.com/wevm/wagmi/pull/4433) [`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc) Thanks [@Aerilym](https://github.com/Aerilym)! - Bumped Metamask SDK version to `0.31.1`. + +## 2.15.1 + +### Patch Changes + +- [`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `null` gas would accidentally pass through. + +## 2.15.0 + +### Minor Changes + +- [#4417](https://github.com/wevm/wagmi/pull/4417) [`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141) Thanks [@jxom](https://github.com/jxom)! - Removed simulation in `writeContract` & `sendTransaction`. + +## 2.14.6 + +### Patch Changes + +- [#4406](https://github.com/wevm/wagmi/pull/4406) [`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3) Thanks [@tmm](https://github.com/tmm)! - Added support for multiple connector `rdns` entries. + +## 2.14.5 + +### Patch Changes + +- [#4400](https://github.com/wevm/wagmi/pull/4400) [`6b9bbacdc7bffd44fc2165362a5e65fd434e7646`](https://github.com/wevm/wagmi/commit/6b9bbacdc7bffd44fc2165362a5e65fd434e7646) Thanks [@AzzouQ](https://github.com/AzzouQ)! - Fixed `createWatchContractEvent` internal wiring, where `eventName` was incorrectly `functionName`. + +## 2.14.4 + +### Patch Changes + +- [#4311](https://github.com/wevm/wagmi/pull/4311) [`e08681c81fbdf475213e2d0f4c5517d0abf4e743`](https://github.com/wevm/wagmi/commit/e08681c81fbdf475213e2d0f4c5517d0abf4e743) Thanks [@chybisov](https://github.com/chybisov)! - Fixed `injected` connector race condition after calling `'wallet_addEthereumChain'` in `switchChain`. + +## 2.14.3 + +### Patch Changes + +- [`cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7`](https://github.com/wevm/wagmi/commit/cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7) Thanks [@tmm](https://github.com/tmm)! - Removed unnecessary internal deep equal check in `structuralSharing`. + +## 2.14.2 + +### Patch Changes + +- [#4339](https://github.com/wevm/wagmi/pull/4339) [`d0d0963bb5904a15cf0355862d62dd141ce0c31c`](https://github.com/wevm/wagmi/commit/d0d0963bb5904a15cf0355862d62dd141ce0c31c) Thanks [@AndriyAntonenko](https://github.com/AndriyAntonenko)! - Fixed bug in `waitForTransactionReceipt`, where transaction data wasn't passed to `'eth_call'` method as part of getting the revert reason. + +- [`ecac0ba36243d94c9199d0bd21937104c835d9a0`](https://github.com/wevm/wagmi/commit/ecac0ba36243d94c9199d0bd21937104c835d9a0) Thanks [@tmm](https://github.com/tmm)! - Fixed `getBalance` symbol error handling. + +## 2.14.1 + +### Patch Changes + +- [`052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702`](https://github.com/wevm/wagmi/commit/052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702) Thanks [@tmm](https://github.com/tmm)! - Added `defaultConnected` feature to `mock` connector. + +- [#4349](https://github.com/wevm/wagmi/pull/4349) [`b250fc21ee577b2a75c5a34ff684f62fb4ad771a`](https://github.com/wevm/wagmi/commit/b250fc21ee577b2a75c5a34ff684f62fb4ad771a) Thanks [@tmm](https://github.com/tmm)! - Bumped internal deps. + +## 2.14.0 + +### Minor Changes + +- [#4343](https://github.com/wevm/wagmi/pull/4343) [`f43e074f473820b208a6295d7c97f847332f1a1d`](https://github.com/wevm/wagmi/commit/f43e074f473820b208a6295d7c97f847332f1a1d) Thanks [@tmm](https://github.com/tmm)! - Added `rdns` property to connector interface. This is used to filter out duplicate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) injected providers when [`createConfig#multiInjectedProviderDiscovery`](https://wagmi.sh/core/api/createConfig#multiinjectedproviderdiscovery) is enabled and `createConfig#connectors` already matches EIP-6963 providers' `rdns` property. + +## 2.13.9 + +### Patch Changes + +- [#4336](https://github.com/wevm/wagmi/pull/4336) [`c05caabc20c3ced9682cfc7ba1f3f7dcfece0703`](https://github.com/wevm/wagmi/commit/c05caabc20c3ced9682cfc7ba1f3f7dcfece0703) Thanks [@EdouardBougon](https://github.com/EdouardBougon)! - Added deprecation notice to `injected` target flags. + +## 2.13.8 + +### Patch Changes + +- [#4207](https://github.com/wevm/wagmi/pull/4207) [`56f2482508f2ba71bd6b0295c70c6abca7101e57`](https://github.com/wevm/wagmi/commit/56f2482508f2ba71bd6b0295c70c6abca7101e57) Thanks [@Smert](https://github.com/Smert)! - Updated chain switch listener for `injected` and `metaMask` to be more robust. + +## 2.13.7 + +### Patch Changes + +- [`be75c2d4ef636d7362420ab0a106bfdf63f5d1e6`](https://github.com/wevm/wagmi/commit/be75c2d4ef636d7362420ab0a106bfdf63f5d1e6) Thanks [@tmm](https://github.com/tmm)! - Added guard for missing `provider.on` for `injected` connector. + +## 2.13.6 + +### Patch Changes + +- [#4286](https://github.com/wevm/wagmi/pull/4286) [`edcbf5d6fbe92f639bead800502edda9e0aa39f1`](https://github.com/wevm/wagmi/commit/edcbf5d6fbe92f639bead800502edda9e0aa39f1) Thanks [@holic](https://github.com/holic)! - Removed duplicate code. + +## 2.13.5 + +### Patch Changes + +- [#4259](https://github.com/wevm/wagmi/pull/4259) [`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb) Thanks [@tmm](https://github.com/tmm)! - Added guard to `getConnectorClient` when reconnecting to check if connector is fully restored. + +## 2.13.4 + +### Patch Changes + +- [`b4c8971788c70b09479946ecfa998cff2f1b3953`](https://github.com/wevm/wagmi/commit/b4c8971788c70b09479946ecfa998cff2f1b3953) Thanks [@tmm](https://github.com/tmm)! - Made `serialize` and `deserialize` types more permissive. + +## 2.13.3 + +### Patch Changes + +- [`871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4`](https://github.com/wevm/wagmi/commit/871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4) Thanks [@tmm](https://github.com/tmm)! - Added validation to internal state for persisted `chainId`. + +## 2.13.2 + +### Patch Changes + +- [`1b9b523fa9b9dfe839aecdf4b40caa9547d7e594`](https://github.com/wevm/wagmi/commit/1b9b523fa9b9dfe839aecdf4b40caa9547d7e594) Thanks [@tmm](https://github.com/tmm)! - Fixed built-in cookie storage `removeItem` working for all paths. + +## 2.13.1 + +### Patch Changes + +- [`07c1227f306d0efb9421d4bb77a774f92f5fcf45`](https://github.com/wevm/wagmi/commit/07c1227f306d0efb9421d4bb77a774f92f5fcf45) Thanks [@tmm](https://github.com/tmm)! - Fixed internal `extractRpcUrls` usage with `unstable_connector`. + +## 2.13.0 + +### Minor Changes + +- [#4162](https://github.com/wevm/wagmi/pull/4162) [`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0) Thanks [@jxom](https://github.com/jxom)! - Added functionality for consumer-defined RPC URLs (`config.transports`) to be propagated to the WalletConnect & MetaMask Connectors. + +## 2.12.2 + +### Patch Changes + +- [`5bc8c8877810b2eec24a829df87dce40a51e6f20`](https://github.com/wevm/wagmi/commit/5bc8c8877810b2eec24a829df87dce40a51e6f20) Thanks [@tmm](https://github.com/tmm)! - Fixed reconnection when `status` is defined. + +## 2.12.1 + +### Patch Changes + +- [#4146](https://github.com/wevm/wagmi/pull/4146) [`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b) Thanks [@jxom](https://github.com/jxom)! - Updated `@safe-global/safe-apps-sdk` + `@safe-global/safe-apps-provider` dependencies. + +## 2.12.0 + +### Minor Changes + +- [#4128](https://github.com/wevm/wagmi/pull/4128) [`5581a810ef70308e99c6f8b630cd4bca59f64afc`](https://github.com/wevm/wagmi/commit/5581a810ef70308e99c6f8b630cd4bca59f64afc) Thanks [@dalechyn](https://github.com/dalechyn)! - Added `watchAsset` action. + +## 2.11.8 + +### Patch Changes + +- [`b08013eaa9ce97c02f8a7128ea400e3da7ef74bb`](https://github.com/wevm/wagmi/commit/b08013eaa9ce97c02f8a7128ea400e3da7ef74bb) Thanks [@tmm](https://github.com/tmm)! - Fixed injected accounts ordering for `'wallet_requestPermissions'`. + +- [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d) Thanks [@jxom](https://github.com/jxom)! - Updated `mipd` dependency. + +## 2.11.7 + +### Patch Changes + +- [`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e) Thanks [@tmm](https://github.com/tmm)! - Improved TypeScript `'exactOptionalPropertyTypes'` support. + +## 2.11.6 + +### Patch Changes + +- [#4060](https://github.com/wevm/wagmi/pull/4060) [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad) Thanks [@dalechyn](https://github.com/dalechyn)! - Bumped Tanstack Query dependencies to fix typing issues between exported Wagmi query options and TanStack Query suspense query methods (due to [`direction` property in `QueryFunctionContext` being deprecated](https://github.com/TanStack/query/pull/7410)). + +## 2.11.5 + +### Patch Changes + +- [#4079](https://github.com/wevm/wagmi/pull/4079) [`04f2b846b113f3d300d82c9fa75212f1805817c5`](https://github.com/wevm/wagmi/commit/04f2b846b113f3d300d82c9fa75212f1805817c5) Thanks [@tmm](https://github.com/tmm)! - Added revalidation for config chain ID in SSR and migration. + +## 2.11.4 + +### Patch Changes + +- [`9e8345cd56186b997b5e56deaa2cfc69b30d15f6`](https://github.com/wevm/wagmi/commit/9e8345cd56186b997b5e56deaa2cfc69b30d15f6) Thanks [@tmm](https://github.com/tmm)! - Switched `Register` to `interface` to fix declaration merging. + +## 2.11.3 + +### Patch Changes + +- [#4065](https://github.com/wevm/wagmi/pull/4065) [`8974e6269bb5d7bfaa90db0246bc7d13e8bff798`](https://github.com/wevm/wagmi/commit/8974e6269bb5d7bfaa90db0246bc7d13e8bff798) Thanks [@alx-khramov](https://github.com/alx-khramov)! - Added timeout to internal call of `'wallet_revokePermissions'` request during `injected#disconnect` as some wallets that do not support this method hang. + +## 2.11.2 + +### Patch Changes + +- [#4042](https://github.com/wevm/wagmi/pull/4042) [`b4d9ef79deb554ee20fed6666a474be5e7cdd522`](https://github.com/wevm/wagmi/commit/b4d9ef79deb554ee20fed6666a474be5e7cdd522) Thanks [@tmm](https://github.com/tmm)! - Removed `injected` connector `isAuthorized` timeout. + +## 2.11.1 + +### Patch Changes + +- [`9c862d8d63e3d692a22cef2a90782b74a9103f17`](https://github.com/wevm/wagmi/commit/9c862d8d63e3d692a22cef2a90782b74a9103f17) Thanks [@tmm](https://github.com/tmm)! - Reverted internal module loading utility. + +## 2.11.0 + +### Minor Changes + +- [#3816](https://github.com/wevm/wagmi/pull/3816) [`06bb598a7f04c7b167f5b7ff6d46bd15886a6a14`](https://github.com/wevm/wagmi/commit/06bb598a7f04c7b167f5b7ff6d46bd15886a6a14) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `deployContract` action. + +### Patch Changes + +- [`24a45b269bd0214a29d6f82a84ac66ef8c3f3822`](https://github.com/wevm/wagmi/commit/24a45b269bd0214a29d6f82a84ac66ef8c3f3822) Thanks [@tmm](https://github.com/tmm)! - Added `SameSite` default to `cookieStorage` + +## 2.10.6 + +### Patch Changes + +- [#4009](https://github.com/wevm/wagmi/pull/4009) [`f2a7cefab96691ebed8b8e45ffde071c47b58dbe`](https://github.com/wevm/wagmi/commit/f2a7cefab96691ebed8b8e45ffde071c47b58dbe) Thanks [@roninjin10](https://github.com/roninjin10)! - Marked `to` as optional for `sendTransaction`. + +- [#4023](https://github.com/wevm/wagmi/pull/4023) [`f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5`](https://github.com/wevm/wagmi/commit/f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5) Thanks [@jxom](https://github.com/jxom)! - Added chain check to `getConnectorClient` since it's possible for connection state chain ID to get out of sync with the connector (e.g. [wallet bug](https://github.com/MetaMask/metamask-extension/issues/25097)). + +## 2.10.5 + +### Patch Changes + +- [#3970](https://github.com/wevm/wagmi/pull/3970) [`030c7c2cb380dfd67a2182f62e2aa7a6e1601898`](https://github.com/wevm/wagmi/commit/030c7c2cb380dfd67a2182f62e2aa7a6e1601898) Thanks [@nanxiaobei](https://github.com/nanxiaobei)! - Fixed `cookieStorage` not working across paths. + +## 2.10.4 + +### Patch Changes + +- [#3984](https://github.com/wevm/wagmi/pull/3984) [`51fde8a0433b4fff357c1a8d7e08b41b4c86c968`](https://github.com/wevm/wagmi/commit/51fde8a0433b4fff357c1a8d7e08b41b4c86c968) Thanks [@tmm](https://github.com/tmm)! - Fixed `writeContract` query types for `value` property. + +## 2.10.3 + +### Patch Changes + +- [#3962](https://github.com/wevm/wagmi/pull/3962) [`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c) Thanks [@tmm](https://github.com/tmm)! - Added catch to `reconnect`. + +## 2.10.2 + +### Patch Changes + +- [#3940](https://github.com/wevm/wagmi/pull/3940) [`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a) Thanks [@jxom](https://github.com/jxom)! - Fixed usage of `metaMask` connector in Vite environments. + +## 2.10.1 + +### Patch Changes + +- Bumped versions. + +## 2.10.0 + +### Minor Changes + +- [#3928](https://github.com/wevm/wagmi/pull/3928) [`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1) Thanks [@tmm](https://github.com/tmm)! - Updated the default Coinbase SDK in `coinbaseWallet` Connector to v4.x. + +## 2.9.8 + +### Patch Changes + +- [#3906](https://github.com/wevm/wagmi/pull/3906) [`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67) Thanks [@tmm](https://github.com/tmm)! - Added support for Vue. + +## 2.9.7 + +### Patch Changes + +- [#3924](https://github.com/wevm/wagmi/pull/3924) [`1f58734f88458e0f6adb05c99f0c90f36ab286b8`](https://github.com/wevm/wagmi/commit/1f58734f88458e0f6adb05c99f0c90f36ab286b8) Thanks [@jxom](https://github.com/jxom)! - Refactored `isChainsStale` logic in `walletConnect` connector. + +## 2.9.6 + +### Patch Changes + +- [#3917](https://github.com/wevm/wagmi/pull/3917) [`05948fdad5bb4a56b08916d45b3dec2cb1e5f55b`](https://github.com/wevm/wagmi/commit/05948fdad5bb4a56b08916d45b3dec2cb1e5f55b) Thanks [@jxom](https://github.com/jxom)! - Updated `@metamask/sdk`. + +## 2.9.5 + +### Patch Changes + +- [`4fecbbb66d0aacd03b8c62a6455d11a33cde8f85`](https://github.com/wevm/wagmi/commit/4fecbbb66d0aacd03b8c62a6455d11a33cde8f85) Thanks [@jxom](https://github.com/jxom)! - Fixed address comparison in `getConnectorClient`. + +## 2.9.4 + +### Patch Changes + +- [#3910](https://github.com/wevm/wagmi/pull/3910) [`e6139a97c4b8804d734b1547b5e3921ce01fbe24`](https://github.com/wevm/wagmi/commit/e6139a97c4b8804d734b1547b5e3921ce01fbe24) Thanks [@tmm](https://github.com/tmm)! - Added experimental `wallet_revokePermissions` support to `injected`. + +## 2.9.3 + +### Patch Changes + +- [#3904](https://github.com/wevm/wagmi/pull/3904) [`addca28ebc20f1a4367c35fe9ef786decff9c87e`](https://github.com/wevm/wagmi/commit/addca28ebc20f1a4367c35fe9ef786decff9c87e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +## 2.9.2 + +### Patch Changes + +- [#3902](https://github.com/wevm/wagmi/pull/3902) [`204b7b624612405500ec098fb9e35facd3f74ca4`](https://github.com/wevm/wagmi/commit/204b7b624612405500ec098fb9e35facd3f74ca4) Thanks [@jxom](https://github.com/jxom)! - Made third-party SDK imports type-only. + +## 2.9.1 + +### Patch Changes + +- [`cda6a5d5`](https://github.com/wevm/wagmi/commit/cda6a5d56328330fbde050b4ef40b01c58d2519a) Thanks [@jxom](https://github.com/jxom)! - Updated packages. + +## 2.9.0 + +### Minor Changes + +- [#3878](https://github.com/wevm/wagmi/pull/3878) [`017828fc`](https://github.com/wevm/wagmi/commit/017828fc027c7a84b54ea9d627e9389f4d60d6c2) Thanks [@jxom](https://github.com/jxom)! - Added experimental EIP-5792 Actions & Hooks. + +## 2.8.1 + +### Patch Changes + +- [#3869](https://github.com/wevm/wagmi/pull/3869) [`d4a78eb0`](https://github.com/wevm/wagmi/commit/d4a78eb07119d2e5617e52481ac7d6c6d1583ddc) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `prepareTransactionRequest` would internally call unsupported wallet RPC methods. + +## 2.8.0 + +### Minor Changes + +- [#3868](https://github.com/wevm/wagmi/pull/3868) [`c2af20b8`](https://github.com/wevm/wagmi/commit/c2af20b88cf16970d087faaec10b463357a5836e) Thanks [@jxom](https://github.com/jxom)! - Added `supportsSimulation` property to connectors that indicates if the connector's wallet supports contract simulation. + +### Patch Changes + +- [#3858](https://github.com/wevm/wagmi/pull/3858) [`0d141f17`](https://github.com/wevm/wagmi/commit/0d141f171d6ec44bcbfc9c876565b5e2fb8af6de) Thanks [@yulafezmesi](https://github.com/yulafezmesi)! - Fixed accessing reverted reason property inside `waitForTransactionReceipt`. + +## 2.7.0 + +### Minor Changes + +- [#3857](https://github.com/wevm/wagmi/pull/3857) [`d4274c03`](https://github.com/wevm/wagmi/commit/d4274c03a6af5f2d26d31432016ebc14950a330e) Thanks [@tmm](https://github.com/tmm)! - Added `addEthereumChainParameter` to `switchChain`-related methods. + +### Patch Changes + +- [#3849](https://github.com/wevm/wagmi/pull/3849) [`4781a405`](https://github.com/wevm/wagmi/commit/4781a4056d4ffc2c74f96a75429e9b2cd2417ad8) Thanks [@tmm](https://github.com/tmm)! - Fixed `shimDisconnect: false` behavior. + +- [#3859](https://github.com/wevm/wagmi/pull/3859) [`400c960b`](https://github.com/wevm/wagmi/commit/400c960b30d701c134850c695ae903a382c29b5b) Thanks [@holic](https://github.com/holic)! - Added workaround to injected connector for MetaMask bug, where chain switching does not work if target chain RPC `'net_version'` request fails. + +## 2.6.19 + +### Patch Changes + +- [`e3c832a1`](https://github.com/wevm/wagmi/commit/e3c832a12c301f9b0ee129d877b3101d220ba8b2) Thanks [@jxom](https://github.com/jxom)! - Fixed undefined `navigator` issue in MetaMask connector. + +## 2.6.18 + +### Patch Changes + +- [#3848](https://github.com/wevm/wagmi/pull/3848) [`dd40a41c`](https://github.com/wevm/wagmi/commit/dd40a41c526ab60a288aff2250ed8dba92a27b16) Thanks [@jxom](https://github.com/jxom)! - Updated MetaMask SDK. + +## 2.6.17 + +### Patch Changes + +- [#3822](https://github.com/wevm/wagmi/pull/3822) [`a97bfbae`](https://github.com/wevm/wagmi/commit/a97bfbaeb615cfef04665e5e7348d85d17f960f0) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where Wagmi would not correctly rehydrate the active chain when a persisted store was being used. + +## 2.6.16 + +### Patch Changes + +- [#3788](https://github.com/wevm/wagmi/pull/3788) [`42ad380d`](https://github.com/wevm/wagmi/commit/42ad380d9a5d8bc0f61d73612142dea9d098de5e) Thanks [@tmm](https://github.com/tmm)! - Refactored connectors to remove unnecessarily event listeners. + +## 2.6.15 + +### Patch Changes + +- [#3782](https://github.com/wevm/wagmi/pull/3782) [`b907d5ac`](https://github.com/wevm/wagmi/commit/b907d5ac3a746bcbccc06d1fe78c5bd8f9a7d685) Thanks [@jxom](https://github.com/jxom)! - Refactored injected connector connection logic. + +## 2.6.14 + +### Patch Changes + +- [#3777](https://github.com/wevm/wagmi/pull/3777) [`b3b54ef1`](https://github.com/wevm/wagmi/commit/b3b54ef179c5fa0d1694d38d4b808549a0550409) Thanks [@desfero](https://github.com/desfero)! - Fixed `writeContract` to forward `chainIn` when simulating contract call + +- [#3779](https://github.com/wevm/wagmi/pull/3779) [`3da20bb8`](https://github.com/wevm/wagmi/commit/3da20bb80e7c3efeef8227ced66ad615370fc242) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `eth_requestAccounts` would be called upon reconnect instead of `eth_accounts`. + +- [`a3d1858f`](https://github.com/wevm/wagmi/commit/a3d1858fce448d2b70e36ee692ef1589b74e9d3f) Thanks [@jxom](https://github.com/jxom)! - Fixed hydration conditional in `createConfig`. + +## 2.6.13 + +### Patch Changes + +- [`b80236dc`](https://github.com/wevm/wagmi/commit/b80236dc623095fe8f1e1d10957d7776fb6ab48b) Thanks [@jxom](https://github.com/jxom)! - Removed unneeded `uniqueBy` check on connectors state. + +## 2.6.12 + +### Patch Changes + +- [#3763](https://github.com/wevm/wagmi/pull/3763) [`a59069e9`](https://github.com/wevm/wagmi/commit/a59069e9fab45dd606bb89a7f829fe94c51a5494) Thanks [@tmm](https://github.com/tmm)! - Fixed `getConnectorClient` internal address comparison. + +- [#3608](https://github.com/wevm/wagmi/pull/3608) [`0acd3132`](https://github.com/wevm/wagmi/commit/0acd31320f534993af566be5490c2978b6184f66) Thanks [@mqklin](https://github.com/mqklin)! - Disabled `wallet_requestPermissions` prompt when `shimDisconnect` is `false`. + +## 2.6.11 + +### Patch Changes + +- [`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d) Thanks [@tmm](https://github.com/tmm)! - Deprecated `normalizeChainId`. Use `Number` instead. + +## 2.6.10 + +### Patch Changes + +- [`dbdca8fd`](https://github.com/wevm/wagmi/commit/dbdca8fd14b90c166222a66a373c1b33c06ce019) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where duplicate connectors could be instantiated if injected after page mount. + +## 2.6.9 + +### Patch Changes + +- [#3715](https://github.com/wevm/wagmi/pull/3715) [`d56edf4f`](https://github.com/wevm/wagmi/commit/d56edf4f27c52acc7a0f57114454b0d3e22cacd6) Thanks [@jxom](https://github.com/jxom)! - Fixed SSR hydration issues. + +## 2.6.8 + +### Patch Changes + +- [#3643](https://github.com/wevm/wagmi/pull/3643) [`e46bcd47`](https://github.com/wevm/wagmi/commit/e46bcd4738a18da15b53f6612b614379c1985374) Thanks [@TateB](https://github.com/TateB)! - Fixed race condition arising from `reconnect`. + +## 2.6.7 + +### Patch Changes + +- [#3642](https://github.com/wevm/wagmi/pull/3642) [`b479b5e8`](https://github.com/wevm/wagmi/commit/b479b5e8a5866cba792862f22e6352c4fb566137) Thanks [@johanneskares](https://github.com/johanneskares)! - Fixed a bug where minification caused the wrong functions to be called on the client. + +- [`f5648dd2`](https://github.com/wevm/wagmi/commit/f5648dd28b3576b628f57732b89287f55acbb1c1) Thanks [@jxom](https://github.com/jxom)! - Updated `prepareTransactionRequest` types for `viem@2.8.0`. + +- [`1c1fee6a`](https://github.com/wevm/wagmi/commit/1c1fee6ab8f01f7734ac6ce05093fa8e388beb3e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +- [#3653](https://github.com/wevm/wagmi/pull/3653) [`88a2d744`](https://github.com/wevm/wagmi/commit/88a2d744a1315908c9e54156026df3ad2435ad44) Thanks [@tash-2s](https://github.com/tash-2s)! - Fixed error occurring when adding chains without explorers to MetaMask. + +## 2.6.6 + +### Patch Changes + +- [#3644](https://github.com/wevm/wagmi/pull/3644) [`a91c0b64`](https://github.com/wevm/wagmi/commit/a91c0b64ba8b3e6537a560e69724eb601f26af27) Thanks [@nishuzumi](https://github.com/nishuzumi)! - Exported types + +## 2.6.5 + +### Patch Changes + +- [#3580](https://github.com/wevm/wagmi/pull/3580) [`c677dcd2`](https://github.com/wevm/wagmi/commit/c677dcd245dccdf69289a3d66dded237b09570a2) Thanks [@tmm](https://github.com/tmm)! - Updated internals. + +## 2.6.4 + +### Patch Changes + +- [#3571](https://github.com/wevm/wagmi/pull/3571) [`7c6618e6`](https://github.com/wevm/wagmi/commit/7c6618e6a0eb1ff39cf8f66b34d3ddc14be538fe) Thanks [@tmm](https://github.com/tmm)! - Fixed `getClient` passthrough properties from `createConfig`. + +- [#3558](https://github.com/wevm/wagmi/pull/3558) [`895f28e8`](https://github.com/wevm/wagmi/commit/895f28e873af7c8eda5ca85734ff67c8979fd950) Thanks [@tmm](https://github.com/tmm)! - Fixed connector warnings. + +## 2.6.3 + +### Patch Changes + +- [#3533](https://github.com/wevm/wagmi/pull/3533) [`9c3b85dd`](https://github.com/wevm/wagmi/commit/9c3b85dd0a9a4a593e1d7e029345275735330e32) Thanks [@tmm](https://github.com/tmm)! - Fixed `account` property passthrough for actions. + +- [`2a72214a`](https://github.com/wevm/wagmi/commit/2a72214a2901d6b6ddd39f80238aa0bd4db670a7) Thanks [@tmm](https://github.com/tmm)! - Shimmed EIP-1193 `removeListener` for injected since some wallets do not follow spec. + +## 2.6.2 + +### Patch Changes + +- [#3519](https://github.com/wevm/wagmi/pull/3519) [`414eb048`](https://github.com/wevm/wagmi/commit/414eb048af492caac70c0e874dfc87c30702804a) Thanks [@tmm](https://github.com/tmm)! - Fixed multicall passing through all properties to Viem method. + +- [#3518](https://github.com/wevm/wagmi/pull/3518) [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d) Thanks [@tmm](https://github.com/tmm)! - Fixed internal store migration between versions. + +## 2.6.1 + +### Patch Changes + +- [#3510](https://github.com/wevm/wagmi/pull/3510) [`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where connectors returning multiple addresses didn't checksum correctly. + +- [#3433](https://github.com/wevm/wagmi/pull/3433) [`101a7dd1`](https://github.com/wevm/wagmi/commit/101a7dd131b0cae2dc25579ecab9044290efd37b) Thanks [@tmm](https://github.com/tmm)! - Fixed `getClient` and `getPublicClient` throwing when used with unconfigured `chainId`. + +## 2.6.0 + +### Minor Changes + +- [#3496](https://github.com/wevm/wagmi/pull/3496) [`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b) Thanks [@tmm](https://github.com/tmm)! - Updated action internals to resolve Viem Client actions. + +## 2.5.0 + +### Minor Changes + +- [#3461](https://github.com/wevm/wagmi/pull/3461) [`ca98041d`](https://github.com/wevm/wagmi/commit/ca98041d1b39893d90246929485f4db0d1c6f9f7) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `getTransactionConfirmations` action. + +## 2.4.0 + +### Minor Changes + +- [#3427](https://github.com/wevm/wagmi/pull/3427) [`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `prepareTransactionRequest` action. + +## 2.3.1 + +### Patch Changes + +- [#3476](https://github.com/wevm/wagmi/pull/3476) [`3be5bb7b`](https://github.com/wevm/wagmi/commit/3be5bb7b0b38646e12e6da5c762ef74dff66bcc2) Thanks [@jxom](https://github.com/jxom)! - Modified persist strategy to only store "critical" properties that are needed before hydration. + +## 2.3.0 + +### Minor Changes + +- [#3459](https://github.com/wevm/wagmi/pull/3459) [`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `getEnsText` action. + +### Patch Changes + +- [#3467](https://github.com/wevm/wagmi/pull/3467) [`90ef39bb`](https://github.com/wevm/wagmi/commit/90ef39bb0f4ecb3c914d317875348e35ba0f4524) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where connectors that share the same provider instance could reconnect when they have never been connected before. + +- [`1cfb6e5a`](https://github.com/wevm/wagmi/commit/1cfb6e5a875e707abcee00dd5739e87da05e8c90) Thanks [@jxom](https://github.com/jxom)! - Bumped listener limit on WalletConnect connector. + +## 2.2.1 + +### Patch Changes + +- [#3447](https://github.com/wevm/wagmi/pull/3447) [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a) Thanks [@tmm](https://github.com/tmm)! - Fixed account typing. + +- [#3443](https://github.com/wevm/wagmi/pull/3443) [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb) Thanks [@jmrossy](https://github.com/jmrossy)! - Fixed invalid `chainId` parameter passed through actions to Viem. + +## 2.2.0 + +### Minor Changes + +- [#3434](https://github.com/wevm/wagmi/pull/3434) [`00bf10a4`](https://github.com/wevm/wagmi/commit/00bf10a428b0d1c5dac35ebf25b19571e033ac26) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `getBytecode` and `getStorageAt` actions. + +- [#3416](https://github.com/wevm/wagmi/pull/3416) [`64c073f6`](https://github.com/wevm/wagmi/commit/64c073f6c2720961e2d6aff986670b73dbfab9c3) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `getTransactionReceipt` action. + +- [#3408](https://github.com/wevm/wagmi/pull/3408) [`fb6c4148`](https://github.com/wevm/wagmi/commit/fb6c4148d9e9e2fccfbe74c8f343b444dc68dec5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `getProof` action. + +## 2.1.2 + +### Patch Changes + +- [#3407](https://github.com/wevm/wagmi/pull/3407) [`e00b8205`](https://github.com/wevm/wagmi/commit/e00b82058685751637edfa9a6b2d196a12549fe7) Thanks [@jxom](https://github.com/jxom)! - Added a prelude gas estimate check to `sendTransaction`/`useSendTransaction`. + +## 2.1.1 + +### Patch Changes + +- [#3402](https://github.com/wevm/wagmi/pull/3402) [`64b82282`](https://github.com/wevm/wagmi/commit/64b82282c1e57e77c25aa0814673780e4d11edd4) Thanks [@Songkeys](https://github.com/Songkeys)! - Fixed SSR cookie support for cookies that have special characters, e.g. `=`. + +- [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37) Thanks [@tmm](https://github.com/tmm)! - Added note to `metaMask` connector. + +## 2.1.0 + +### Minor Changes + +- [#3387](https://github.com/wevm/wagmi/pull/3387) [`c9cd302e`](https://github.com/wevm/wagmi/commit/c9cd302e1c65c980deaee2e12567c2a8ec08b399) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `call` action. + +## 2.0.2 + +### Patch Changes + +- [#3384](https://github.com/wevm/wagmi/pull/3384) [`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844) Thanks [@tmm](https://github.com/tmm)! - Fixed connectors not bubbling error when connecting with `chainId` and subsequent user rejection. + +## 2.0.1 + +### Major Changes + +- [#3333](https://github.com/wevm/wagmi/pull/3333) [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a) Thanks [@tmm](https://github.com/tmm)! - Wagmi Core 2.0 featuring: + + - Full TanStack Query support + queryKeys + - Connect multiple connectors + - Switch chains while disconnected + - EIP-6963 enabled + - Strongly typed chainId and chain properties + - Smaller bundle size + - Miscellaneous improvements and bug fixes + + [Breaking Changes & Migration Guide](https://wagmi.sh/core/guides/migrate-from-v1-to-v2) + +## 1.4.13 + +### Patch Changes + +- Updated dependencies [[`bbbbf587`](https://github.com/wevm/wagmi/commit/bbbbf587e41bae12b072b7a7c897d580fc07cd2b)]: + - @wagmi/connectors@3.1.11 + +## 1.4.12 + +### Patch Changes + +- [`53ca1f7e`](https://github.com/wevm/wagmi/commit/53ca1f7eb411d912e11fcce7e03bd61ed067959c) Thanks [@tmm](https://github.com/tmm)! - Removed LedgerConnector due to security vulnerability + +- Updated dependencies [[`53ca1f7e`](https://github.com/wevm/wagmi/commit/53ca1f7eb411d912e11fcce7e03bd61ed067959c)]: + - @wagmi/connectors@3.1.10 + +## 1.4.11 + +### Patch Changes + +- [#3299](https://github.com/wevm/wagmi/pull/3299) [`b02020b3`](https://github.com/wevm/wagmi/commit/b02020b3724e0228198f35817611bb063295906e) Thanks [@dasanra](https://github.com/dasanra)! - Fixed issue with [Safe SDK](https://github.com/wevm/viem/issues/579) by bumping `@safe-global/safe-apps-provider@0.18.1` + +- Updated dependencies [[`51eca0fb`](https://github.com/wevm/wagmi/commit/51eca0fbaea6932f31a5b8b4213f0252280053e2), [`b02020b3`](https://github.com/wevm/wagmi/commit/b02020b3724e0228198f35817611bb063295906e)]: + - @wagmi/connectors@3.1.9 + +## 1.4.10 + +### Patch Changes + +- Updated dependencies [[`e8f7bcbc`](https://github.com/wevm/wagmi/commit/e8f7bcbcd9c038a901c29e71769682c088efe2ac)]: + - @wagmi/connectors@3.1.8 + +## 1.4.9 + +### Patch Changes + +- [#3276](https://github.com/wevm/wagmi/pull/3276) [`83223a06`](https://github.com/wevm/wagmi/commit/83223a0659e2f675d897a1d3374c7af752c16abf) Thanks [@glitch-txs](https://github.com/glitch-txs)! - Removed required namespaces from WalletConnect connector + +- Updated dependencies [[`83223a06`](https://github.com/wevm/wagmi/commit/83223a0659e2f675d897a1d3374c7af752c16abf)]: + - @wagmi/connectors@3.1.7 + +## 1.4.8 + +### Patch Changes + +- Updated dependencies [[`cc7e18f2`](https://github.com/wevm/wagmi/commit/cc7e18f2e7f6b8b989f60f0b05aee70e996a9975), [`cc7e18f2`](https://github.com/wevm/wagmi/commit/cc7e18f2e7f6b8b989f60f0b05aee70e996a9975)]: + - @wagmi/connectors@3.1.6 + +## 1.4.7 + +### Patch Changes + +- Updated dependencies [[`a1950449`](https://github.com/wagmi-dev/wagmi/commit/a1950449127ddf72fff8ecd1fc34c3690befbb05)]: + - @wagmi/connectors@3.1.5 + +## 1.4.6 + +### Patch Changes + +- Updated dependencies [[`4e6ec415`](https://github.com/wagmi-dev/wagmi/commit/4e6ec4151baece94e940e227e0e3711c7f8534d9)]: + - @wagmi/connectors@3.1.4 + +## 1.4.5 + +### Patch Changes + +- Updated dependencies [[`e78aa337`](https://github.com/wagmi-dev/wagmi/commit/e78aa337c454f04b41a3cbd381d25270dd4a0afd)]: + - @wagmi/connectors@3.1.3 + +## 1.4.4 + +### Patch Changes + +- [#3125](https://github.com/wagmi-dev/wagmi/pull/3125) [`725e73fe`](https://github.com/wagmi-dev/wagmi/commit/725e73feb9143dbaa6d540bb76d2009cef29da0b) Thanks [@lukasrosario](https://github.com/lukasrosario)! - Fixed an issue where `dataSuffix` was not being passed down into viem's `simulateContract`, causing the data to be omitted from requests. + +## 1.4.3 + +### Patch Changes + +- [#3076](https://github.com/wagmi-dev/wagmi/pull/3076) [`4c36831b`](https://github.com/wagmi-dev/wagmi/commit/4c36831b7aa44d03b5c0decf64dcd20faae28a67) Thanks [@jxom](https://github.com/jxom)! - Pass `chain` to viem `sendTransaction`/`writeContract`. + +- [#3006](https://github.com/wagmi-dev/wagmi/pull/3006) [`f2ddce23`](https://github.com/wagmi-dev/wagmi/commit/f2ddce23324aff0a91e066100918dac552dc3b4a) Thanks [@jxom](https://github.com/jxom)! - Changed `normalize` to a dynamic import. + +## 1.4.2 + +### Patch Changes + +- Updated dependencies [[`3aaba328`](https://github.com/wagmi-dev/wagmi/commit/3aaba32808ddb4035ec885f96992c91078056715)]: + - @wagmi/connectors@3.1.2 + +## 1.4.1 + +### Patch Changes + +- Updated dependencies [[`bf831bb3`](https://github.com/wagmi-dev/wagmi/commit/bf831bb30df8037cc4312342d0fe3c045408c2fe)]: + - @wagmi/connectors@3.1.1 + +## 1.4.0 + +### Minor Changes + +- [#2956](https://github.com/wagmi-dev/wagmi/pull/2956) [`2abeb285`](https://github.com/wagmi-dev/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895) Thanks [@tmm](https://github.com/tmm)! - Replaced `@wagmi/chains` with `viem/chains`. + +### Patch Changes + +- Updated dependencies [[`2abeb285`](https://github.com/wagmi-dev/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895)]: + - @wagmi/connectors@3.1.0 + +## 1.3.10 + +### Patch Changes + +- [`557e6400`](https://github.com/wagmi-dev/wagmi/commit/557e6400b9cef3b2c5131739143956c37d7c934a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.9 + +### Patch Changes + +- [`247c5d11`](https://github.com/wagmi-dev/wagmi/commit/247c5d113e83acf3a6894264c00d4b125d455107) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.8 + +### Patch Changes + +- [#2741](https://github.com/wagmi-dev/wagmi/pull/2741) [`5b1453d9`](https://github.com/wagmi-dev/wagmi/commit/5b1453d95973ed51f1c235a919fffb707eab9b70) Thanks [@jxom](https://github.com/jxom)! - Updated references + +## 1.3.7 + +### Patch Changes + +- [#2700](https://github.com/wagmi-dev/wagmi/pull/2700) [`30118e97`](https://github.com/wagmi-dev/wagmi/commit/30118e979b1b00302e035f31f58c15d1aed911d5) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.6 + +### Patch Changes + +- [`7ad2fdb8`](https://github.com/wagmi-dev/wagmi/commit/7ad2fdb81c7734d0c8107670800c68390e3bad99) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.5 + +### Patch Changes + +- [`aab63fc1`](https://github.com/wagmi-dev/wagmi/commit/aab63fc1f8949004573978ecd8574fada3360758) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.4 + +### Patch Changes + +- [`22246d98`](https://github.com/wagmi-dev/wagmi/commit/22246d9884277d28ccad6ca2d9529b96b67d47fc) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.3 + +### Patch Changes + +- [`1946aa43`](https://github.com/wagmi-dev/wagmi/commit/1946aa43a65b684ef41b7b4c43c67bf29c13e854) Thanks [@jxom](https://github.com/jxom)! - Updated references + +## 1.3.2 + +### Patch Changes + +- [`e86d0940`](https://github.com/wagmi-dev/wagmi/commit/e86d09409bb20b64d24e1263abcf0291314f03c7) Thanks [@jxom](https://github.com/jxom)! - Updated references + +## 1.3.1 + +### Patch Changes + +- [`964042fa`](https://github.com/wagmi-dev/wagmi/commit/964042fa94d682977923c595820c58283fb9244a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.3.0 + +### Minor Changes + +- [#2619](https://github.com/wagmi-dev/wagmi/pull/2619) [`0d79748c`](https://github.com/wagmi-dev/wagmi/commit/0d79748cec2b6ac2410ad2c9816cc662f2b70962) Thanks [@jxom](https://github.com/jxom)! - Updated references: + - Updated `@safe-global/safe-apps-sdk` to `^8.0.0` (the one with `viem` support) + +## 1.2.2 + +### Patch Changes + +- [#2611](https://github.com/wagmi-dev/wagmi/pull/2611) [`6d1ed7a1`](https://github.com/wagmi-dev/wagmi/commit/6d1ed7a156729b4df5d66fef3ae9a8b5762a2d34) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.2.1 + +### Patch Changes + +- [#2589](https://github.com/wagmi-dev/wagmi/pull/2589) [`9680c347`](https://github.com/wagmi-dev/wagmi/commit/9680c347476500d28ceca20d23eeaed7931cb6e0) Thanks [@jxom](https://github.com/jxom)! - Fixed `writeContract` parameters to be compatible with `prepareWriteContract`. + +- [#2587](https://github.com/wagmi-dev/wagmi/pull/2587) [`cfff9994`](https://github.com/wagmi-dev/wagmi/commit/cfff999459384ac644ff7e62f53a7b787cf37507) Thanks [@jxom](https://github.com/jxom)! - Updated references + +## 1.2.0 + +### Minor Changes + +- [#2536](https://github.com/wagmi-dev/wagmi/pull/2536) [`85e9760a`](https://github.com/wagmi-dev/wagmi/commit/85e9760a140cb169ac6236d9466b96e2105dd193) Thanks [@tmm](https://github.com/tmm)! - Changed `Address` type import from ABIType to viem. + +### Patch Changes + +- [#2539](https://github.com/wagmi-dev/wagmi/pull/2539) [`96319c64`](https://github.com/wagmi-dev/wagmi/commit/96319c640b9d07b375821c08a5c213355d8c290b) Thanks [@jxom](https://github.com/jxom)! - Updated references + +## 1.1.1 + +### Patch Changes + +- [`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787) Thanks [@jxom](https://github.com/jxom)! - Updated `viem` peer dependency. + +- [`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.1.0 + +### Minor Changes + +- [#2482](https://github.com/wagmi-dev/wagmi/pull/2482) [`8764b54a`](https://github.com/wagmi-dev/wagmi/commit/8764b54aab68020063946112e8fe52aff650c99c) Thanks [@tmm](https://github.com/tmm)! - Bumped minimum TypeScript version to v5.0.4. + +### Patch Changes + +- [#2484](https://github.com/wagmi-dev/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated `abitype` to 0.8.7 + +- [#2484](https://github.com/wagmi-dev/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.0.8 + +### Patch Changes + +- [#2441](https://github.com/wagmi-dev/wagmi/pull/2441) [`326edee4`](https://github.com/wagmi-dev/wagmi/commit/326edee4bc85db84a7a4e3768e33785849ab8d8e) Thanks [@tmm](https://github.com/tmm)! - Fixed internal type issue + +## 1.0.7 + +### Patch Changes + +- [#2433](https://github.com/wagmi-dev/wagmi/pull/2433) [`54fcff5f`](https://github.com/wagmi-dev/wagmi/commit/54fcff5f02f6933bbbe045ee0c83c5a78b6bba49) Thanks [@jxom](https://github.com/jxom)! - Added ability to pass an `account` to `writeContract`/`prepareWriteContract`. + +## 1.0.6 + +### Patch Changes + +- [`ca2e1e96`](https://github.com/wagmi-dev/wagmi/commit/ca2e1e96149b87a7dc42c9db07e1f1ad2bb02c4a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- [#2401](https://github.com/wagmi-dev/wagmi/pull/2401) [`0f9dc875`](https://github.com/wagmi-dev/wagmi/commit/0f9dc875e90cfdd7a2028e04b7204caf9ea313b2) Thanks [@jxom](https://github.com/jxom)! - Exposed `account` on `readContract`/`useContractRead`. + +## 1.0.5 + +### Patch Changes + +- [`90e2b3b3`](https://github.com/wagmi-dev/wagmi/commit/90e2b3b39efe0585fe28645ac2264109be17362a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +## 1.0.4 + +### Patch Changes + +- [#2344](https://github.com/wagmi-dev/wagmi/pull/2344) [`8a725458`](https://github.com/wagmi-dev/wagmi/commit/8a72545853ae1024acd9efd18c06142e8c6c5750) Thanks [@jxom](https://github.com/jxom)! - Added gas estimation back into `prepareSendTransaction`. + +## 1.0.3 + +### Patch Changes + +- [#2338](https://github.com/wagmi-dev/wagmi/pull/2338) [`92bfdc2c`](https://github.com/wagmi-dev/wagmi/commit/92bfdc2c744539558ba93c95f140b46ad331cee4) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where synchronous switch chain behavior (WalletConnect v2) would encounter chain id race conditions in `watchWalletClient`. + +## 1.0.2 + +### Patch Changes + +- [#2304](https://github.com/wevm/wagmi/pull/2304) [`09a4fd38`](https://github.com/wevm/wagmi/commit/09a4fd38f44eb176797925fd85314be17b610cd4) Thanks [@jxom](https://github.com/jxom)! - Removed assert chain workaround. + +## 1.0.1 + +### Patch Changes + +- [`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af) Thanks [@jxom](https://github.com/jxom)! - Downgraded abitype. + +- Updated dependencies [[`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af)]: + - @wagmi/connectors@1.0.1 + +## 1.0.0 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`5be0655c`](https://github.com/wevm/wagmi/commit/5be0655c8e48b25d38009022461fbf611af54349) Thanks [@jxom](https://github.com/jxom)! - Released v1. Read [Migration Guide](https://next.wagmi.sh/react/migration-guide#1xx-breaking-changes). + +## 1.0.0-next.7 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de) Thanks [@jxom](https://github.com/jxom)! - Added `config.setPublicClient` & `config.setWebSocketPublicClient` + +- Updated references. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@1.0.0-next.5 + +## 1.0.0-next.6 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de) Thanks [@jxom](https://github.com/jxom)! - Added `config.setPublicClient` & `config.setWebSocketPublicClient` + +- Added `config.setConnectors` + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@1.0.0-next.6 + +## 1.0.0-next.5 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de) Thanks [@jxom](https://github.com/jxom)! - Added `config.setPublicClient` & `config.setWebSocketPublicClient` + +## 1.0.0-next.4 + +### Major Changes + +- Updated viem. + Removed `goerli` export from main entrypoint. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@1.0.0-next.5 + +## 1.0.0-next.3 + +### Major Changes + +- Updated references. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@1.0.0-next.4 + +## 1.0.0-next.2 + +### Major Changes + +- **Breaking:** Renamed `createClient` to `createConfig` +- **Breaking:** Renamed `getClient` to `getConfig` +- **Breaking:** Removed `request` as an argument to `prepareSendTransaction` & `sendTransaction`. Arguments now belong on the root level of the Action. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/chains@1.0.0-next.0 + - @wagmi/connectors@1.0.0-next.3 + +## 1.0.0-next.1 + +### Major Changes + +- updated viem + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@1.0.0-next.2 + +## 1.0.0-next.0 + +### Major Changes + +- [`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb) Thanks [@jxom](https://github.com/jxom)! - Released v1. + +### Patch Changes + +- Updated dependencies [[`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb)]: + - @wagmi/connectors@1.0.0-next.1 + +## 0.10.11 + +### Patch Changes + +- [#2270](https://github.com/wevm/wagmi/pull/2270) [`6d1fa9df`](https://github.com/wevm/wagmi/commit/6d1fa9df790287729c3b33d4f01fd23c2f8153f1) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies []: + - @wagmi/connectors@0.3.19 + +## 0.10.10 + +### Patch Changes + +- [#2208](https://github.com/wevm/wagmi/pull/2208) [`cfc696d8`](https://github.com/wevm/wagmi/commit/cfc696d83c6f768a2e1a29c5197efeed7f1d40a1) Thanks [@bangtoven](https://github.com/bangtoven)! - Bumped references to apply coinbase wallet sdk updates + +- Updated dependencies []: + - @wagmi/connectors@0.3.16 + +## 0.10.9 + +### Patch Changes + +- [#2143](https://github.com/wevm/wagmi/pull/2143) [`26dc5326`](https://github.com/wevm/wagmi/commit/26dc53260fde1d3278018c0b20a6d48a093d9427) Thanks [@tmm](https://github.com/tmm)! - Exported Sepolia Chain. + +- [#2146](https://github.com/wevm/wagmi/pull/2146) [`21b6842e`](https://github.com/wevm/wagmi/commit/21b6842e8c296a0bbe71ebe0780d898abc4cf4a8) Thanks [@tmm](https://github.com/tmm)! - Bumped references + +- Updated dependencies []: + - @wagmi/connectors@0.3.12 + +## 0.10.8 + +### Patch Changes + +- [#2099](https://github.com/wevm/wagmi/pull/2099) [`f1fee5b3`](https://github.com/wevm/wagmi/commit/f1fee5b30a1bd13b5e66118bf9cdc44b0dc003a1) Thanks [@jxom](https://github.com/jxom)! - Added chains: + + - `nexi` + - `polygonZkEvm` + - `xdc` + - `xdcTestnet` + +- [#2085](https://github.com/wevm/wagmi/pull/2085) [`7d64e3f5`](https://github.com/wevm/wagmi/commit/7d64e3f538a6149777bfa84ea9435769b2a7db58) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where multicall would not throw if the target chain was not configured on the wagmi client. + +- Updated dependencies []: + - @wagmi/connectors@0.3.10 + +## 0.10.7 + +### Patch Changes + +- [#2082](https://github.com/wevm/wagmi/pull/2082) [`2ccc8a25`](https://github.com/wevm/wagmi/commit/2ccc8a255e93f0a2bb7b22101656b3905ec59abd) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies []: + - @wagmi/connectors@0.3.10 + +## 0.10.6 + +### Patch Changes + +- [#2056](https://github.com/wevm/wagmi/pull/2056) [`944f6513`](https://github.com/wevm/wagmi/commit/944f6513adf09a6f0b3bd34f591d3bbd1f1ffd2e) Thanks [@tmm](https://github.com/tmm)! - Bumped references. + +- Updated dependencies []: + - @wagmi/connectors@0.3.8 + +## 0.10.5 + +### Patch Changes + +- [#2053](https://github.com/wevm/wagmi/pull/2053) [`665df1bf`](https://github.com/wevm/wagmi/commit/665df1bf2afccb533102069def395e19fb7194dd) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where you add a new chain to MetaMask, but the switch after is rejected. + +- Updated dependencies []: + - @wagmi/connectors@0.3.7 + +## 0.10.4 + +### Patch Changes + +- [#2046](https://github.com/wevm/wagmi/pull/2046) [`90d8e9b8`](https://github.com/wevm/wagmi/commit/90d8e9b87962b72c54311649537e91a953660f9b) Thanks [@tmm](https://github.com/tmm)! - Exported internal type. + +- Updated dependencies []: + - @wagmi/connectors@0.3.6 + +## 0.10.3 + +### Patch Changes + +- [#2039](https://github.com/wevm/wagmi/pull/2039) [`bac893ab`](https://github.com/wevm/wagmi/commit/bac893ab26012d4d8741c4f80e8b8813aee26f0c) Thanks [@tmm](https://github.com/tmm)! - Updated references. + +- [#2043](https://github.com/wevm/wagmi/pull/2043) [`49a58320`](https://github.com/wevm/wagmi/commit/49a58320ab5f1f13bc4de25abcc028c8335e98f0) Thanks [@tmm](https://github.com/tmm)! - Removed `InjectedConnector` `shimChainChangedDisconnect` shim (no longer necessary). + +- Updated dependencies []: + - @wagmi/connectors@0.3.6 + +## 0.10.2 + +### Patch Changes + +- [#2016](https://github.com/wevm/wagmi/pull/2016) [`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab) Thanks [@jxom](https://github.com/jxom)! - Added chains: + + - `boba` + - `chronos` + - `crossbell` + - `dfk` + - `dogechain` + - `flare` + - `flareTestnet` + - `klaytn` + - `scrollTestnet` + - `shardeumSphinx` + - `skaleCalypso` + - `skaleCalypsoTestnet` + - `skaleChaosTestnet` + - `skaleCryptoBlades` + - `skaleCryptoColosseum` + - `skaleEuropa` + - `skaleEuropaTestnet` + - `skaleExorde` + - `skaleHumanProtocol` + - `skaleNebula` + - `skaleNebulaTestnet` + - `skaleRazor` + - `skaleTitan` + - `skaleTitanTestnet` + - `songbird` + - `songbirdTestnet` + - `titan` + - `titanTestnet` + - `wanchain` + - `wanchainTestnet` + +- [#2016](https://github.com/wevm/wagmi/pull/2016) [`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab) Thanks [@jxom](https://github.com/jxom)! - Updated references/ submodule. + +- Updated dependencies []: + - @wagmi/connectors@0.3.4 + +## 0.10.0 + +### Minor Changes + +- [#1902](https://github.com/wevm/wagmi/pull/1902) [`0994e896`](https://github.com/wevm/wagmi/commit/0994e8966349b8811db0a5886db3831dafc99245) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** Removed the `version` config option for `WalletConnectConnector`. + + `WalletConnectConnector` now uses WalletConnect v2 by default. WalletConnect v1 is now `WalletConnectLegacyConnector`. + + ### WalletConnect v2 + + ```diff + import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + + const connector = new WalletConnectConnector({ + options: { + - version: '2', + projectId: 'abc', + }, + }) + ``` + + ### WalletConnect v1 + + ```diff + -import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + +import { WalletConnectConnector } from 'wagmi/connectors/walletConnectLegacy' + + -const connector = new WalletConnectConnector({ + +const connector = new WalletConnectLegacyConnector({ + options: { + qrcode: true, + }, + }) + ``` + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@0.3.2 + +## 0.9.7 + +### Patch Changes + +- [#1907](https://github.com/wevm/wagmi/pull/1907) [`cc4e74ee`](https://github.com/wevm/wagmi/commit/cc4e74ee19665eccb3767052dab6ab956ff4e676) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - `baseGoerli` + - `harmonyOne` + - `polygonZkEvmTestnet` + +- Updated dependencies []: + - @wagmi/connectors@0.2.7 + +## 0.9.6 + +### Patch Changes + +- [#1882](https://github.com/wevm/wagmi/pull/1882) [`282cc1b0`](https://github.com/wevm/wagmi/commit/282cc1b02003684d582cea411b11792a59c26fd0) Thanks [@tmm](https://github.com/tmm)! - Updated references. + +- Updated dependencies []: + - @wagmi/connectors@0.2.6 + +## 0.9.5 + +### Patch Changes + +- [#1812](https://github.com/wevm/wagmi/pull/1812) [`c7fd7fbd`](https://github.com/wevm/wagmi/commit/c7fd7fbde6f6c69a3a9a4f89d948c4dfb1d22679) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - `filecoinCalibration` + - `moonbaseAlpha` + - `moonbeam` + - `moonriver` + +- Updated dependencies []: + - @wagmi/connectors@0.2.5 + +## 0.9.4 + +### Patch Changes + +- [#1786](https://github.com/wevm/wagmi/pull/1786) [`b173a431`](https://github.com/wevm/wagmi/commit/b173a43165c7925a4e56ce1e0327a31917e7edc5) Thanks [@tmm](https://github.com/tmm)! - Locked ethers peer dependency version to >=5.5.1 <6 + +- [#1787](https://github.com/wevm/wagmi/pull/1787) [`f023fd8f`](https://github.com/wevm/wagmi/commit/f023fd8f66befb78b9a4df5ca971ceaa64e37ab4) Thanks [@tmm](https://github.com/tmm)! - Added `SafeConnector` + +- Updated dependencies []: + - @wagmi/connectors@0.2.4 + +## 0.9.3 + +### Patch Changes + +- [#1773](https://github.com/wevm/wagmi/pull/1773) [`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/universal-provider` on `WalletConnectConnector` v2. + Added more signable methods to `WalletConnectConnector` v2. + +- [#1773](https://github.com/wevm/wagmi/pull/1773) [`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c) Thanks [@jxom](https://github.com/jxom)! - Added Telos to the `wagmi/chains` entrypoint. Thanks @donnyquixotic! + +- Updated dependencies []: + - @wagmi/connectors@0.2.3 + +## 0.9.2 + +### Patch Changes + +- [#1756](https://github.com/wevm/wagmi/pull/1756) [`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d) Thanks [@jxom](https://github.com/jxom)! - Added OKC Chain. Thanks @clark-cui! + +- [#1756](https://github.com/wevm/wagmi/pull/1756) [`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d) Thanks [@jxom](https://github.com/jxom)! - Fixed race condition between `switchNetwork` and mutation Actions that use `chainId` (e.g. `sendTransaction`). Thanks @DanInTheD4rk! + +- Updated dependencies []: + - @wagmi/connectors@0.2.2 + +## 0.9.1 + +### Patch Changes + +- [#1752](https://github.com/wevm/wagmi/pull/1752) [`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4) Thanks [@jxom](https://github.com/jxom)! - Improved `WalletConnectConnector` (v2) initialization & updated dependencies. + +- [#1752](https://github.com/wevm/wagmi/pull/1752) [`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - Aurora – thanks @salil-naik + - Bronos – thanks @chedetinaveen + - Canto – thanks @tster + - Celo – thanks @aaronmgdr + +- Updated dependencies []: + - @wagmi/connectors@0.2.1 + +## 0.9.0 + +### Minor Changes + +- [#1732](https://github.com/wevm/wagmi/pull/1732) [`01e21897`](https://github.com/wevm/wagmi/commit/01e2189747a5c22dc758c6d719b4145adc2a643c) Thanks [@tmm](https://github.com/tmm)! - Bumped minimum TypeScript version to typescript@>=4.9.4. TypeScript 5.0 is coming soon and has some great features we are excited to bring into wagmi. To prepare for this, update your TypeScript version to 4.9.4 or higher. There are likely no [breaking changes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#correctness-fixes-and-breaking-changes) if you are coming from typescript@4.7.x || typescript@4.8.x. + +## 0.8.19 + +### Patch Changes + +- [#1718](https://github.com/wevm/wagmi/pull/1718) [`e62b5ef8`](https://github.com/wevm/wagmi/commit/e62b5ef8aaa8063abb5264790768899ea35bbd31) Thanks [@tmm](https://github.com/tmm)! - Updated references + +## 0.8.18 + +### Patch Changes + +- [#1708](https://github.com/wevm/wagmi/pull/1708) [`07fc3801`](https://github.com/wevm/wagmi/commit/07fc3801fa13c2cb5f7cf9b86ba8320b05a6a135) Thanks [@jxom](https://github.com/jxom)! - Updated `references/` submodule. + +## 0.8.17 + +### Patch Changes + +- [#1705](https://github.com/wevm/wagmi/pull/1705) [`9ff797dc`](https://github.com/wevm/wagmi/commit/9ff797dcb979dc86b798a432b74c98598165430d) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `@wagmi/core/chains` entrypoint: + + - `crossbell` (thanks @Songkeys) + - `filecoin` & `filecoinHyperspace` (thanks @neil0x46dc) + - `gnosisChiado` (thanks @theNvN) + - `metis` & `metisGoerli` (thanks @CookedCookee) + +## 0.8.16 + +### Patch Changes + +- [#1699](https://github.com/wevm/wagmi/pull/1699) [`2f1e7950`](https://github.com/wevm/wagmi/commit/2f1e7950e55550d9b50ef5ccb97cb609f4af39b1) Thanks [@tmm](https://github.com/tmm)! - Added public RPC URL property to Chain + +## 0.8.15 + +### Patch Changes + +- [#1685](https://github.com/wevm/wagmi/pull/1685) [`917f5bc1`](https://github.com/wevm/wagmi/commit/917f5bc1fad578e35a8c6ee787e339bfdc156bab) Thanks [@jxom](https://github.com/jxom)! - Replaced qrcodemodal with web3modal for the WalletConnect v2 Connector. + +## 0.8.14 + +### Patch Changes + +- [#1646](https://github.com/wevm/wagmi/pull/1646) [`fcdbe353`](https://github.com/wevm/wagmi/commit/fcdbe3531e6d05cda4a4a511bae1ad4c9e426d88) Thanks [@jxom](https://github.com/jxom)! - Upgraded `zustand` to v4.3.1. + +## 0.8.13 + +### Patch Changes + +- [#1639](https://github.com/wevm/wagmi/pull/1639) [`c6869f06`](https://github.com/wevm/wagmi/commit/c6869f0604fffb197752a08256f31db77f52e746) Thanks [@jxom](https://github.com/jxom)! - Added `isRainbow` flag to `InjectedConnector`. + +## 0.8.12 + +### Patch Changes + +- [#1636](https://github.com/wevm/wagmi/pull/1636) [`025f6771`](https://github.com/wevm/wagmi/commit/025f6771b32ff7eed22f527be81c5141ddaf9c3d) Thanks [@DanielSinclair](https://github.com/DanielSinclair)! - Added `isRainbow` flag to injected `window.ethereum` types. + +## 0.8.11 + +### Patch Changes + +- [#1621](https://github.com/wevm/wagmi/pull/1621) [`5812b590`](https://github.com/wevm/wagmi/commit/5812b5909277bf2862cb57a31d52465b47291410) Thanks [@tmm](https://github.com/tmm)! - Bumped @wagmi/connectors + +## 0.8.10 + +### Patch Changes + +- [#1598](https://github.com/wevm/wagmi/pull/1598) [`fc10ebe6`](https://github.com/wevm/wagmi/commit/fc10ebe659dd5f3b7a8e00581f094652280a779b) Thanks [@jxom](https://github.com/jxom)! - Fixed CJS dependency version range + +## 0.8.9 + +### Patch Changes + +- [#1593](https://github.com/wevm/wagmi/pull/1593) [`216d555c`](https://github.com/wevm/wagmi/commit/216d555c62bd95c3c7c8f8e20f7269f6c8504610) Thanks [@jxom](https://github.com/jxom)! - Added CJS escape hatch bundle under the "cjs" tag. + +## 0.8.8 + +### Patch Changes + +- [#1573](https://github.com/wevm/wagmi/pull/1573) [`ef380d9c`](https://github.com/wevm/wagmi/commit/ef380d9c6d51ae0495b9c35925d2843c75d97fd4) Thanks [@tmm](https://github.com/tmm)! - Updated internal types. + +## 0.8.7 + +### Patch Changes + +- [#1570](https://github.com/wevm/wagmi/pull/1570) [`216f585b`](https://github.com/wevm/wagmi/commit/216f585be8a9e3a56e3243f49ccd54d655b5a6dd) Thanks [@wslyvh](https://github.com/wslyvh)! - Added `watchPendingTransactions` + +- [#1470](https://github.com/wevm/wagmi/pull/1470) [`3a1a6c9f`](https://github.com/wevm/wagmi/commit/3a1a6c9fe5db5c360adfd116f9a03a1238b5720c) Thanks [@jxom](https://github.com/jxom)! - The `WalletConnectConnector` now supports WalletConnect v2. + + It can be enabled by setting `version` to `'2'` and supplying a [WalletConnect Cloud `projectId`](https://cloud.walletconnect.com/sign-in). + +## 0.8.6 + +### Patch Changes + +- [#1539](https://github.com/wevm/wagmi/pull/1539) [`732da004`](https://github.com/wevm/wagmi/commit/732da0042c7e28091b2e36a484ea8239971306f5) Thanks [@0xFlicker](https://github.com/0xFlicker)! - All Providers (ie. Alchemy, Infura, Public) now use the ENS Registry address on the wagmi `Chain` object (`chain.contracts.ensRegistry`). + +- [#1574](https://github.com/wevm/wagmi/pull/1574) [`ecde3d10`](https://github.com/wevm/wagmi/commit/ecde3d1029ccdf90e2853ba0e9ae4f5f4ebb9c4c) Thanks [@jxom](https://github.com/jxom)! - Added the following chains: + + - `iotex` + - `iotexTestnet` + - `zkSync` + - `zkSyncTestnet` + +## 0.8.5 + +### Patch Changes + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Added the following chains: + + - `evmos` + - `evmosTestnet` + - `gnosis` + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Updated Goerli symbol to `"ETH"`. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Updated Arbitrum Goerli RPC and Block Explorer. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where connecting to MetaMask may return with a stale address. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Removed ENS registry for Sepolia. + +## 0.8.4 + +### Patch Changes + +- [#1508](https://github.com/wevm/wagmi/pull/1508) [`0b50b62f`](https://github.com/wevm/wagmi/commit/0b50b62f7389619e429509a3e337e451e823b059) Thanks [@jxom](https://github.com/jxom)! - Updated `@wagmi/chains` to `0.1.3`. + +- [#1504](https://github.com/wevm/wagmi/pull/1504) [`11b8b794`](https://github.com/wevm/wagmi/commit/11b8b794fbfd4a2b40f39962e2758e9fbf48cb54) Thanks [@tmm](https://github.com/tmm)! - Converted ethers custom "ACTION_REJECTED" error to standard RPC Error. + +## 0.8.3 + +### Patch Changes + +- [#1431](https://github.com/wevm/wagmi/pull/1431) [`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc) Thanks [@jxom](https://github.com/jxom)! - Re-export connectors from `@wagmi/connectors` + +- [#1431](https://github.com/wevm/wagmi/pull/1431) [`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc) Thanks [@jxom](https://github.com/jxom)! - Added `LedgerConnector` connector + +## 0.8.2 + +### Patch Changes + +- [#1442](https://github.com/wevm/wagmi/pull/1442) [`cde15289`](https://github.com/wevm/wagmi/commit/cde152899c758dea10787412b0aef669ed7202b2) Thanks [@0xproflupin](https://github.com/0xproflupin)! - Added Phantom wallet support to `InjectedConnector` + +- [#1448](https://github.com/wevm/wagmi/pull/1448) [`c6075f3a`](https://github.com/wevm/wagmi/commit/c6075f3a16885d850ad2656272351f9517c9f67b) Thanks [@tmm](https://github.com/tmm)! - Updated [ABIType](https://github.com/wevm/abitype) version. + +- [#1444](https://github.com/wevm/wagmi/pull/1444) [`310a8bc4`](https://github.com/wevm/wagmi/commit/310a8bc428ce4e7f68377f581b45dcdd64381cce) Thanks [@jxom](https://github.com/jxom)! - Assert that a `connector` exists before invoking the callback in `watchSigner`. + +- [#1434](https://github.com/wevm/wagmi/pull/1434) [`100e2a3b`](https://github.com/wevm/wagmi/commit/100e2a3b22f4602716554487b1d98738e053be76) Thanks [@tmm](https://github.com/tmm)! - Updated `MockConnector` `chainId` behavior to default to first chain from `chains` if not provided in `options`. + +## 0.8.1 + +### Patch Changes + +- [#1437](https://github.com/wevm/wagmi/pull/1437) [`c34a3dc6`](https://github.com/wevm/wagmi/commit/c34a3dc6396e6473d9f0505fad88ec910f8f5275) Thanks [@jxom](https://github.com/jxom)! - Omitted `"EIP712Domain"` type from `signTypedData` `types` arg since ethers throws an [internal error](https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/hash/src.ts/typed-data.ts#L466) if you include it. + +- [#1445](https://github.com/wevm/wagmi/pull/1445) [`51dd53cb`](https://github.com/wevm/wagmi/commit/51dd53cba3fe0f79fa1393270b738194577ddf54) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where the wagmi client wouldn't rehydrate the store in local storage when `autoConnect` is truthy. + +## 0.8.0 + +### Minor Changes + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: the shape of the `Chain` type has been modified. + + #### RPC URLs + + The `rpcUrls` shape has changed to include an array of URLs, and also the transport method (`http` or `webSocket`): + + ```diff + type Chain = { + ... + rpcUrls: { + - [key: string]: string + + [key: string]: { + + http: string[] + + webSocket: string[] + + } + } + ... + } + ``` + + Note that you will also need to ensure that usage is migrated: + + ```diff + - const rpcUrl = mainnet.rpcUrls.alchemy + + const rpcUrl = mainnet.rpcUrls.alchemy.http[0] + ``` + + #### Contracts + + The `multicall` and `ens` attributes have been moved into the `contracts` object: + + ```diff + type Contract = { + address: Address + blockCreated?: number + } + + type Chain = { + ... + - multicall: Contract + - ens: Contract + + contracts: { + + multicall3: Contract + + ensRegistry: Contract + + } + ... + } + ``` + + Note that you will also need to ensure that usage is migrated: + + ```diff + - const multicallContract = mainnet.multicall + + const multicallContract = mainnet.contracts.multicall3 + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: Upgraded `@coinbase/wallet-sdk` peer dependency to `3.6.0`. + + **Migration steps**: Update `@coinbase/wallet-sdk` to `^3.6.0`. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: Removed the `wait` argument on `waitForTransaction`. Use the transaction `hash` instead. + + ```diff + const data = await waitForTransaction({ + - wait: transaction.wait + + hash: transaction.hash + }) + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: With the introduction of the [`@wagmi/core/chains` entrypoint](/core/chains), `@wagmi/core` no longer exports the following: + + - `chain` + - `allChains` + - `defaultChains` + - `defaultL2Chains` + - `chainId` + - `etherscanBlockExplorers` + - `alchemyRpcUrls`, `infuraRpcUrls`, `publicRpcUrls` + + Read below for migration steps. + + #### Removed `chain` + + The `chain` export has been removed. `@wagmi/core` now only exports the `mainnet` & `goerli` chains. If you need to use an alternative chain (`polygon`, `optimism`, etc), you will need to import it from the [`@wagmi/core/chains` entrypoint](/core/chains). + + ```diff + import { + - chain + configureChains + } from '@wagmi/core' + + import { mainnet, polygon, optimism } from '@wagmi/core/chains' + + const { ... } = configureChains( + - [chain.mainnet, chain.polygon, chain.optimism], + + [mainnet, polygon, optimism], + { + ... + } + ) + ``` + + #### Removed `allChains` + + The `allChains` export has been removed. If you need a list of all chains, you can utilize [`@wagmi/core/chains` entrypoint](/core/chains). + + ```diff + - import { allChains } from '@wagmi/core' + + import * as allChains from '@wagmi/core/chains' + + const { ... } = configureChains(allChains, ...) + ``` + + #### Removed `defaultChains` & `defaultL2Chains` + + The `defaultChains` & `defaultL2Chains` exports have been removed. If you still need the `defaultChains` or `defaultL2Chains` exports, you can build them yourself: + + ```diff + - import { defaultChains } from '@wagmi/core' + + import { mainnet, goerli } from '@wagmi/core/chains' + + + const defaultChains = [mainnet, goerli] + ``` + + > The `defaultChains` export was previously populated with `mainnet` & `goerli`. + + ```diff + - import { defaultL2Chains } from '@wagmi/core' + + import { + + arbitrum, + + arbitrumGoerli, + + polygon, + + polygonMumbai, + + optimism, + + optimismGoerli + + } from '@wagmi/core/chains' + + + const defaultL2Chains = [ + + arbitrum, + + arbitrumGoerli, + + polygon, + + polygonMumbai, + + optimism + + optimismGoerli + + ] + ``` + + > The `defaultL2Chains` export was previously populated with `arbitrum` & `optimism`. + + #### Removed `chainId` + + The `chainId` export has been removed. You can extract a chain ID from the chain itself. + + ```diff + - import { chainId } from '@wagmi/core' + + import { mainnet, polygon, optimism } from '@wagmi/core/chains' + + -const mainnetChainId = chainId.mainnet + -const polygonChainId = chainId.polygon + -const optimismChainId = chainId.optimism + +const mainnetChainId = mainnet.chainId + +const polygonChainId = polygon.chainId + +const optimismChainId = optimism.chainId + ``` + + #### Removed `etherscanBlockExplorers` + + The `etherscanBlockExplorers` export has been removed. You can extract a block explorer from the chain itself. + + ```diff + - import { etherscanBlockExplorers } from '@wagmi/core' + + import { mainnet, polygon, optimism } from '@wagmi/core/chains' + + -const mainnetEtherscanBlockExplorer = etherscanBlockExplorers.mainnet + -const polygonEtherscanBlockExplorer = etherscanBlockExplorers.polygon + -const optimismEtherscanBlockExplorer = etherscanBlockExplorers.optimism + +const mainnetEtherscanBlockExplorer = mainnet.blockExplorer + +const polygonEtherscanBlockExplorer = polygon.blockExplorer + +const optimismEtherscanBlockExplorer = optimism.blockExplorer + ``` + + #### Removed `alchemyRpcUrls`, `infuraRpcUrls` & `publicRpcUrls` + + The `alchemyRpcUrls`, `infuraRpcUrls` & `publicRpcUrls` exports have been removed. You can extract a RPC URL from the chain itself. + + ```diff + - import { alchemyRpcUrls, infuraRpcUrls, publicRpcUrls } from '@wagmi/core' + + import { mainnet } from '@wagmi/core/chains' + + -const mainnetAlchemyRpcUrl = alchemyRpcUrls.mainnet + -const mainnetInfuraRpcUrl = infuraRpcUrls.mainnet + -const mainnetOptimismRpcUrl = publicRpcUrls.mainnet + +const mainnetAlchemyRpcUrl = mainnet.rpcUrls.alchemy + +const mainnetInfuraRpcUrl = mainnet.rpcUrls.infura + +const mainnetOptimismRpcUrl = mainnet.rpcUrls.optimism + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: Changed `waitForTransaction` behavior to throw an error if the transaction reverted. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - Updated errors to use `cause` instead of `internal` + +### Patch Changes + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - `waitForTransaction` now respects repriced (sped up) transactions. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - Export `getClient` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - `waitForTransaction` now throws an error for cancelled or replaced transactions. + +## 0.7.9 + +### Patch Changes + +- [#1411](https://github.com/wevm/wagmi/pull/1411) [`659be184`](https://github.com/wevm/wagmi/commit/659be1840c613ce9f7aca9ac96694c4f60da4a66) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where block invalidation was not properly disabled when setting `enabled: false`. + +## 0.7.8 + +### Patch Changes + +- [#1406](https://github.com/wevm/wagmi/pull/1406) [`4f18c450`](https://github.com/wevm/wagmi/commit/4f18c450a4d7952bfcfa6c533348ffbe55893d3c) Thanks [@tmm](https://github.com/tmm)! - Function for selecting the [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Provider to target. Defaults to `() => typeof window !== 'undefined' ? window.ethereum : undefined`. + + ```ts + import { InjectedConnector } from "@wagmi/core/connectors/injected"; + + const connector = new InjectedConnector({ + options: { + name: "My Injected Wallet", + getProvider: () => + typeof window !== "undefined" ? window.myInjectedWallet : undefined, + }, + }); + ``` + +## 0.7.7 + +### Patch Changes + +- [#1386](https://github.com/wevm/wagmi/pull/1386) [`206a2adb`](https://github.com/wevm/wagmi/commit/206a2adbb4ee5149a364543b34612050ccf78c21) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `persister` would still use `window.localStorage` instead of the wagmi `storage`. + +- [#1376](https://github.com/wevm/wagmi/pull/1376) [`a70a9528`](https://github.com/wevm/wagmi/commit/a70a9528f93f4d7fea28b7652751dfef2dcacf9b) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `switchChain` on `WalletConnectConnector` would not resolve. + +- [#1386](https://github.com/wevm/wagmi/pull/1386) [`206a2adb`](https://github.com/wevm/wagmi/commit/206a2adbb4ee5149a364543b34612050ccf78c21) Thanks [@jxom](https://github.com/jxom)! - Added `serialize`/`deserialize` as config options to `createStorage`. + +- [#1392](https://github.com/wevm/wagmi/pull/1392) [`88afc849`](https://github.com/wevm/wagmi/commit/88afc84978afe9689ab7364633e4422ecd7699ea) Thanks [@tmm](https://github.com/tmm)! - Added check for active connector when connecting + +## 0.7.6 + +### Patch Changes + +- [#1384](https://github.com/wevm/wagmi/pull/1384) [`027e88d6`](https://github.com/wevm/wagmi/commit/027e88d6e5f8d028d46ee78aec8500701e0173d9) Thanks [@tmm](https://github.com/tmm)! - Fixed issue reconnecting after disconnect with `MetaMaskConnector` in MetaMask mobile browser. + +## 0.7.5 + +### Patch Changes + +- [`1169914a`](https://github.com/wevm/wagmi/commit/1169914a0f0ad2810ca1c536b1f1bc6c20f2c1be) Thanks [@jxom](https://github.com/jxom)! - Use `get_accounts` for `getSigner` in InjectedConnector + +## 0.7.4 + +### Patch Changes + +- [#1309](https://github.com/wevm/wagmi/pull/1309) [`1f4a4261`](https://github.com/wevm/wagmi/commit/1f4a4261247b1d3a90e3123157bc851a35d49b9c) Thanks [@tmm](https://github.com/tmm)! - Fixed internal type + +## 0.7.3 + +### Patch Changes + +- [#1294](https://github.com/wevm/wagmi/pull/1294) [`b2f88949`](https://github.com/wevm/wagmi/commit/b2f88949f32aabaf13f318472648cd51a8b7f2e7) Thanks [@tmm](https://github.com/tmm)! - Set `abi` return type value for `prepareContractWrite` as more permissive when not inferrable as `Abi`. + +## 0.7.2 + +### Patch Changes + +- [`e9f806b6`](https://github.com/wevm/wagmi/commit/e9f806b652ba62effb3ddac464815e447fc287f6) Thanks [@tmm](https://github.com/tmm)! - Bumped abitype and zustand versions. + +## 0.7.1 + +### Patch Changes + +- [#1272](https://github.com/wevm/wagmi/pull/1272) [`1f7fc41`](https://github.com/wevm/wagmi/commit/1f7fc419f7960bbdc51dfa85c2f33b89f1ecc1bf) Thanks [@tmm](https://github.com/tmm)! - Fixed ethers import path + +## 0.7.0 + +### Minor Changes + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Removed the following deprecated chains: + + - `ropsten` + - `rinkeby` + - `kovan` + - `optimismKovan` + - `arbitrumRinkeby` + + If you feel you still need to include one of these testnets in your application, you will have to define it manually: + + ```diff + -import { rinkeby } from 'wagmi' + +import { Chain } from 'wagmi' + + +export const rinkeby: Chain = { + + id: 4, + + name: 'Rinkeby', + + network: 'rinkeby', + + nativeCurrency: { name: 'Rinkeby Ether', symbol: 'ETH', decimals: 18 }, + + rpcUrls: { + + alchemy: 'https://eth-rinkeby.alchemyapi.io/v2', + + default: 'https://rpc.ankr.com/eth_rinkeby', + + infura: 'https://rinkeby.infura.io/v3', + + public: 'https://rpc.ankr.com/eth_rinkeby', + + }, + + blockExplorers: { + + etherscan: 'https://rinkeby.etherscan.io', + + default: 'https://rinkeby.etherscan.io', + + }, + + ens: { + + address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + + }, + + multicall: { + + address: '0xca11bde05977b3631167028862be2a173976ca11', + + blockCreated: 10299530, + + }, + + testnet: true, + } + ``` + + You can reference these removed chains [here](https://github.com/wevm/wagmi/blob/389765f7d9af063ab0df07389a2bbfbc10a41060/packages/core/src/constants/chains.ts). + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `addressOrName` renamed to `address` for `fetchBalance` and `fetchEnsAvatar`. + + ```diff + const result = await fetchBalance({ + - addressOrName: '0x…', + + address: '0x…', + }) + ``` + + If you were using an ENS name instead of an address, you can resolve the name to an address before passing it to the action. + + ```diff + + const { data: address } = await fetchEnsAddress({ name: 'example.eth' }) + const result = await fetchBalance({ + - addressOrName: 'example.eth', + + address, + }) + ``` + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Made `apiKey` required on `infuraProvider` and `alchemyProvider`. + + ```diff + import { configureChains } from 'wagmi' + + const config = configureChains(defaultChains, [ + - alchemyProvider(), + + alchemyProvider({ apiKey: process.env.ALCHEMY_API_KEY }) + ]) + ``` + + You can find your Alchemy API key from the [Alchemy Dashboard](https://dashboard.alchemyapi.io/), or your Infura API key from the [Infura Dashboard](https://infura.io/login). + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - Removed CommonJS support + +## 0.6.12 + +### Patch Changes + +- [#1250](https://github.com/wevm/wagmi/pull/1250) [`ce2e0f4`](https://github.com/wevm/wagmi/commit/ce2e0f4a46b8fd1c509ead552012ef4c072a525b) Thanks [@tmm](https://github.com/tmm)! - Added support for Trust Wallet browser extension. + +## 0.6.11 + +### Patch Changes + +- [#1234](https://github.com/wevm/wagmi/pull/1234) [`3ff9303`](https://github.com/wevm/wagmi/commit/3ff930349250f62137cca4ca3b382522882abf8a) Thanks [@tmm](https://github.com/tmm)! - Fixed issue with adding chain to wallet without block explorer URL. + +## 0.6.10 + +### Patch Changes + +- [#1232](https://github.com/wevm/wagmi/pull/1232) [`c0ca509`](https://github.com/wevm/wagmi/commit/c0ca509506dcf6d98b058df549dc761c9a5f3d1c) Thanks [@tmm](https://github.com/tmm)! - Added validation to check that chain is configured for connector when accessing `Signer`. + +## 0.6.9 + +### Patch Changes + +- [#1207](https://github.com/wevm/wagmi/pull/1207) [`c73d463`](https://github.com/wevm/wagmi/commit/c73d463d65c9dbfcfe709187e47323a769589741) Thanks [@lvshaoping007](https://github.com/lvshaoping007)! - Added Kucoin wallet support to `InjectedConnector` + +## 0.6.8 + +### Patch Changes + +- [#1132](https://github.com/wevm/wagmi/pull/1132) [`d41c0d6`](https://github.com/wevm/wagmi/commit/d41c0d650f8c0e54145758685b7604b8909d7ae0) Thanks [@toniocodo](https://github.com/toniocodo)! - Added ERC-4626 ABI + +- [#1201](https://github.com/wevm/wagmi/pull/1201) [`9a07efa`](https://github.com/wevm/wagmi/commit/9a07efaa397d3ba03f2edbe527c359f21e22139a) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where non-checksum addresses did not resolve with an ENS name + +## 0.6.7 + +### Patch Changes + +- [#1174](https://github.com/wevm/wagmi/pull/1174) [`196a458`](https://github.com/wevm/wagmi/commit/196a458f64141e8a9f39c1b1e1af5937f692cb39) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `client.chains` (active connector chains) would be populated when there is no active connector (disconnected user). + +- [#1176](https://github.com/wevm/wagmi/pull/1176) [`389765f`](https://github.com/wevm/wagmi/commit/389765f7d9af063ab0df07389a2bbfbc10a41060) Thanks [@jxom](https://github.com/jxom)! - Migrate away from Alchemy RPC URLs in the public RPC URL list + +## 0.6.6 + +### Patch Changes + +- [`81ce9e6`](https://github.com/wevm/wagmi/commit/81ce9e64d85f7d01370324c1a529988a0919894f) Thanks [@jxom](https://github.com/jxom)! - Add `isPortal` to injected MetaMask flags. + +- [`c2c0109`](https://github.com/wevm/wagmi/commit/c2c01096ef4cd0ffadbb49062969c208604c6194) Thanks [@jxom](https://github.com/jxom)! - Add etherscan block explorer to Optimism Goerli + +## 0.6.5 + +### Patch Changes + +- [#1162](https://github.com/wevm/wagmi/pull/1162) [`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where non-indexed event parameter types were set to `null`. + +- [#1162](https://github.com/wevm/wagmi/pull/1162) [`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where `useContractReads` and `useContractInfiniteReads` types were slowing down TypeScript compiler. + +## 0.6.4 + +### Patch Changes + +- [#1103](https://github.com/wevm/wagmi/pull/1103) [`651eda0`](https://github.com/wevm/wagmi/commit/651eda06384bd0955268427f898e9337b2dc5a31) Thanks [@tmm](https://github.com/tmm)! - Bumped `abitype` dependency. + +## 0.6.3 + +### Patch Changes + +- [#1086](https://github.com/wevm/wagmi/pull/1086) [`4e28d2a`](https://github.com/wevm/wagmi/commit/4e28d2ad4c2e6b3479b728563040b9529463cbcf) Thanks [@tmm](https://github.com/tmm)! - Exposed module types. + +## 0.6.2 + +### Patch Changes + +- [#1080](https://github.com/wevm/wagmi/pull/1080) [`3be5e8b`](https://github.com/wevm/wagmi/commit/3be5e8b01e58ed40cc9dab7ef9533c0197cb74d0) Thanks [@tmm](https://github.com/tmm)! - Added `abitype` to `dependencies` so types ship correctly. + +## 0.6.1 + +### Patch Changes + +- [#1074](https://github.com/wevm/wagmi/pull/1074) [`8db807f`](https://github.com/wevm/wagmi/commit/8db807f16149aa278c2a7db9ee5245431db12173) Thanks [@IljaDaderko](https://github.com/IljaDaderko)! - Exported `EventListener` type + +## 0.6.0 + +### Minor Changes + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `watchSigner` now requires an arguments object (that accepts an optional `chainId`) as it's first parameter. + + ```diff + import { watchSigner } from `@wagmi/core` + + -watchSigner(signer => { + +watchSigner({}, signer => { + console.log('new signer!', signer) + }) + ``` + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `prepareSendTransaction` now throws when a `chainId` is specified and the end-user is on a different chain id (the wrong network). + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `addressOrName` and `contractInterface` renamed to `address` and `abi` respectively for contract actions: `getContract`, `multicall`, `prepareWriteContract`, `readContract`, `readContracts`, `watchContractEvent`, `watchMulticall`, `watchReadContract`, `watchReadContracts`, `writeContract`. + + ```diff + import { readContract } from '@wagmi/core' + + const result = await readContract({ + - addressOrName: '0x…', + + address: '0x…', + - contractInterface: […] as const, + + abi: […] as const, + functionName: 'balanceOf', + args: ['0x…'], + }) + ``` + + If you were using an ENS name instead of an address, you can resolve the name to an address before passing it to the action. + + ```diff + - import { readContract } from '@wagmi/core' + + import { fetchEnsAddress, readContract } from '@wagmi/core' + + + const address = await fetchEnsAddress('example.eth') + const result = await readContract({ + - addressOrName: 'example.eth', + + address, + abi: […] as const, + functionName: 'balanceOf', + args: ['0x…'], + }) + ``` + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `prepareWriteContract` now throws when a `chainId` is specified and the end-user is on a different chain id (the wrong network). + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `prepareSendTransaction` now only accepts a `signer` instead of `signerOrProvider`. + + This is to reach parity with `prepareWriteContract`. + + If no `signer` is provided, wagmi will use the signer that is currently connected. If no user is connected, then `prepareWriteContract` will throw an error. + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `args` config option must now be an array for the following actions: `readContract`, `writeContract`, `prepareWriteContract`, `multicall`, `readContracts`, `watchMulticall`, and `watchReadContracts`. + + ```diff + import { readContract } from '@wagmi/core' + + const result = await readContract({ + address: '0x…', + abi: […], + functionName: 'balanceOf', + - args: '0x…', + + args: ['0x…'], + }) + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `watchContractEvent` now accepts a configuration object and callback instead of positional arguments. + + ```diff + import { watchContractEvent } from '@wagmi/core' + + - const unsubscribe = watchContractEvent( + - { + - address: '0x…', + - abi: […], + - }, + - 'Transfer', + - (from, to, tokenId) => { + - // ... + - }, + - { once: true }, + - ) + + const unsubscribe = watchContractEvent( + + { + + address: '0x…', + + abi: […], + + eventName: 'Transfer', + + once: true, + + }, + + (from, to, tokenId) => { + + // ... + + }, + + ) + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Updated TypeScript version to `typescript@>=4.7.4`. + + `@wagmi/core` can now infer types based on [ABI](https://docs.soliditylang.org/en/v0.8.15/abi-spec.html#json) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data definitions, giving you full end-to-end type-safety from your contracts to your frontend and incredible developer experience (e.g. autocomplete contract function names and catch misspellings, type contract function arguments, etc.). + + For this to work, you must upgrade to `typescript@>=4.7.4`. Why is TypeScript v4.7.4 or greater necessary? TypeScript 4.7.4 introduced the ability to [extend constraints on inferred type variables](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#extends-constraints-on-infer-type-variables), which is used extensively to help narrow types for ABIs. Good news! When upgrading TypeScript from 4.6 to 4.7 there are likely no [breaking changes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#breaking-changes) for your set up. + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Updated TypeScript generics for contract interaction and typed data actions. + + Adding a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to `abi` allows TypeScript to infer `functionName`, `args`, `overrides`, and return types for functions, and `eventName` and `listener` types for events. + + ```diff + import { readContract } from '@wagmi/core' + + const result = await readContract({ + address: '0x…', + - abi: […], + + abi: […] as const, + functionName: 'balanceOf', // will autocomplete and catch typos + args: ['0x…'], // inferred based on `functionName` + }) + result // inferred based on `functionName` + ``` + + This works for the following actions: `readContract`, `writeContract`, `prepareWriteContract`, `multicall`, `readContracts`, `watchMulticall`, `watchReadContracts`, and `watchContractEvent`. + + Adding a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to `signTypedData`'s config option, `types`, allows TypeScript to infer `value`. + + ```diff + import { signTypedData } from '@wagmi/core' + + const result = await signTypedData({ + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + - }, + + } as const, + value: { // `value` is inferred based on `types` + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + }) + ``` + +### Patch Changes + +- [#1061](https://github.com/wevm/wagmi/pull/1061) [`a4ffe8b`](https://github.com/wevm/wagmi/commit/a4ffe8b25516d5504685ae94579da4cd8c409329) Thanks [@alecananian](https://github.com/alecananian)! - Added Arbitrum Goerli Arbiscan block explorer + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - The `fetchSigner` action now accepts an optional `chainId` to use for signer initialization as an argument. + + ```tsx + import { fetchSigner } from "@wagmi/core"; + import { optimism } from "@wagmi/core/chains"; + + // ... + + fetchSigner({ chainId: optimism.id }); + ``` + +- [#1048](https://github.com/wevm/wagmi/pull/1048) [`ed13074`](https://github.com/wevm/wagmi/commit/ed130747c0f28c1d9980a1328883e4000a60455e) Thanks [@Max-3-7](https://github.com/Max-3-7)! - Added support for Avalanche core wallet + +- [#1046](https://github.com/wevm/wagmi/pull/1046) [`ab9ecaa`](https://github.com/wevm/wagmi/commit/ab9ecaa74dfa4324279e167dd7e348319ef7d35d) Thanks [@jxom](https://github.com/jxom)! - make ethers block format validator compatible with Celo + +- [#1050](https://github.com/wevm/wagmi/pull/1050) [`73d4d47`](https://github.com/wevm/wagmi/commit/73d4d47bc679f4f9a1cf46010fe2bf858c9d0b5c) Thanks [@jxom](https://github.com/jxom)! - update dependencies + + - `zustand@4.1.1` + +## 0.5.8 + +### Patch Changes + +- [`8cb07462`](https://github.com/wevm/wagmi/commit/8cb07462acc3c5637398d11d2451f8b8e330d553) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` as an argument to `watchBlockNumber`. + +* [`53c1a474`](https://github.com/wevm/wagmi/commit/53c1a4747d03b685e8cfbf55361fc2a56777fb06) Thanks [@tmm](https://github.com/tmm)! - Added missing `decimals` option to `Connector` `watchAsset` + +- [`4d74dd4f`](https://github.com/wevm/wagmi/commit/4d74dd4ff827ba5c43c3546a218f38cee45ea76a) Thanks [@jxom](https://github.com/jxom)! - Support ERC20 contracts that represent strings as bytes32 + +## 0.5.7 + +### Patch Changes + +- [`aa51bc4d`](https://github.com/wevm/wagmi/commit/aa51bc4dc5683bf0178597d2fdb8f2e9d82e7970) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue in `CoinbaseWalletConnector` where the browser extension would unintendedly reset the network when the browser is refreshed. + +* [#955](https://github.com/wevm/wagmi/pull/955) [`e326cd80`](https://github.com/wevm/wagmi/commit/e326cd80fe65267db623eb6c80ccdd75572914cf) Thanks [@0xFlicker](https://github.com/0xFlicker)! - Added Infura RPC URL for Sepolia + +- [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useProvider` & `getProvider` were not returning referentially equal providers. + +* [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where the `watch` option was not respecting the neighboring `chainId` option in `useBlockNumber`. + +- [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where block listeners (via `watch`) were firing excessively on L2 chains. + +## 0.5.6 + +### Patch Changes + +- [#936](https://github.com/wevm/wagmi/pull/936) [`3329d1f`](https://github.com/wevm/wagmi/commit/3329d1f5880431566e14ac1640f48d0975aec4c2) Thanks [@jxom](https://github.com/jxom)! - Added the ability to provide a custom logger to override how logs are broadcasted to the consumer in wagmi. + + A custom logger can be provided to the wagmi client via `logger`. + + ### API + + ```tsx + logger?: { + warn: typeof console.warn | null + } + ``` + + ### Examples + + **Passing in a custom logger** + + You can pass in a function to define your own custom logger. + + ```diff + + import { logWarn } from './logger'; + + const client = createClient({ + ... + + logger: { + + warn: message => logWarn(message) + + } + ... + }) + ``` + + **Disabling a logger** + + You can disable a logger by passing `null` as the value. + + ```diff + const client = createClient({ + ... + + logger: { + + warn: null + + } + ... + }) + ``` + +* [#889](https://github.com/wevm/wagmi/pull/889) [`27788ed`](https://github.com/wevm/wagmi/commit/27788ed989b5dc26849c7945fb91a92e56766018) Thanks [@jxom](https://github.com/jxom)! - Make multicall & readContracts more error robust + +## 0.5.5 + +### Patch Changes + +- [#912](https://github.com/wevm/wagmi/pull/912) [`e529e12`](https://github.com/wevm/wagmi/commit/e529e125c713ed3ef24a59c6bf226fe4deee7ac9) Thanks [@zouhangwithsweet](https://github.com/zouhangwithsweet)! - Added BitKeep to injected flags + +- [#912](https://github.com/wevm/wagmi/pull/910) Thanks [@mytangying](https://github.com/zouhangwithsweet)! - Added MathWallet to injected flags + +- [#904](https://github.com/wevm/wagmi/pull/904) [`c231058`](https://github.com/wevm/wagmi/commit/c23105850f335f8798031e14c7098b7dee8c2975) Thanks [@jxom](https://github.com/jxom)! - Minimized contract interface returned from `prepareWriteContract`. + +## 0.5.4 + +### Patch Changes + +- [#852](https://github.com/wevm/wagmi/pull/852) [`c3192d0`](https://github.com/wevm/wagmi/commit/c3192d0663aa332ae9edfd9dd49b333454013ab7) Thanks [@skeithc](https://github.com/skeithc)! - Added support for the Sepolia testnet + +## 0.5.3 + +### Patch Changes + +- [#835](https://github.com/wevm/wagmi/pull/835) [`1b85e54`](https://github.com/wevm/wagmi/commit/1b85e54ae654e2564cf5bc2dae6411fe0a25875c) Thanks [@jxom](https://github.com/jxom)! - Update `@coinbase/wallet-sdk` to `3.4.1` + +* [#834](https://github.com/wevm/wagmi/pull/834) [`9655879`](https://github.com/wevm/wagmi/commit/96558793b0319df47aefafa6b7b9c959068d491b) Thanks [@jxom](https://github.com/jxom)! - Update zustand to `4.0.0` + +## 0.5.2 + +### Patch Changes + +- [#823](https://github.com/wevm/wagmi/pull/823) [`10b8b78`](https://github.com/wevm/wagmi/commit/10b8b78605b7246b2c55b8d69f96663906e5cd20) Thanks [@tmm](https://github.com/tmm)! - Add Optimism Goerli to `chain` lookup. + +## 0.5.1 + +### Patch Changes + +- [#767](https://github.com/wevm/wagmi/pull/767) [`e9392f3`](https://github.com/wevm/wagmi/commit/e9392f396e48e928bd9d2522e3ad671c589f08cb) Thanks [@klyap](https://github.com/klyap)! - Add Optimism Goerli chain ahead of [Kovan deprecation](https://dev.optimism.io/kovan-to-goerli). + +* [#817](https://github.com/wevm/wagmi/pull/817) [`7e5cac7`](https://github.com/wevm/wagmi/commit/7e5cac75815dcd8aa563462342a4853fc5207735) Thanks [@alecananian](https://github.com/alecananian)! - Added custom name mapping for 1inch Wallet injected provider + +- [#806](https://github.com/wevm/wagmi/pull/806) [`0b34e56`](https://github.com/wevm/wagmi/commit/0b34e56db97e6dcdb71088e0149b2d55ebc604a5) Thanks [@vmichalik](https://github.com/vmichalik)! - Fix canonical testnet native asset symbols by changing them to ETH + +* [#778](https://github.com/wevm/wagmi/pull/778) [`0892908`](https://github.com/wevm/wagmi/commit/08929084eeeba1a3a55aa098fa9d92a243685ad5) Thanks [@0xcadams](https://github.com/0xcadams)! - Add Arbitrum Goerli chain. + +## 0.5.0 + +### Minor Changes + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The configuration passed to the `sendTransaction` action now needs to be: + + - prepared with the `prepareSendTransaction` action **(new functionality)**, or + - recklessly unprepared **(previous functionality)** + + > Why? [Read here](https://wagmi.sh/docs/prepare-hooks) + + ### Prepared usage + + ```diff + import { prepareSendTransaction, sendTransaction } from '@wagmi/core' + + +const config = await prepareSendTransaction({ + + request: { + + to: 'moxey.eth', + + value: parseEther('1'), + + } + +}) + + const result = await sendTransaction({ + - request: { + - to: 'moxey.eth', + - value: parseEther('1') + - } + + ...config + }) + ``` + + ### Recklessly unprepared usage + + It is possible to use `sendTransaction` without preparing the configuration first by passing `mode: 'recklesslyUnprepared'`. + + ```diff + import { sendTransaction } from '@wagmi/core' + + const result = await sendTransaction({ + + mode: 'recklesslyUnprepared', + request: { + to: 'moxey.eth', + value: parseEther('1'), + } + }) + ``` + +* [#760](https://github.com/wevm/wagmi/pull/760) [`d8af6bf`](https://github.com/wevm/wagmi/commit/d8af6bf50885aec110ae4d64716642453aa27896) Thanks [@tmm](https://github.com/tmm)! - **Breaking:** `alchemyProvider` and `infuraProvider` now use a generic `apiKey` configuration option instead of `alchemyId` and `infuraId`. + + ```diff + import { alchemyProvider } from '@wagmi/core/providers/alchemy' + import { infuraProvider } from '@wagmi/core/providers/infura' + + alchemyProvider({ + - alchemyId: 'yourAlchemyApiKey', + + apiKey: 'yourAlchemyApiKey', + }) + + infuraProvider({ + - infuraId: 'yourInfuraApiKey', + + apiKey: 'yourInfuraApiKey', + }) + ``` + +- [#727](https://github.com/wevm/wagmi/pull/727) [`ac3b9b8`](https://github.com/wevm/wagmi/commit/ac3b9b87f80cb45b65d003f09d916d7d1427a62e) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Moved the `pollingInterval` config option from the chain provider config to `configureChains` config. + + ```diff + const { chains, provider } = configureChains( + [chain.mainnet, chain.polygon], + [ + - alchemyProvider({ apiKey, pollingInterval: 5000 }), + - publicProvider({ pollingInterval: 5000 }) + + alchemyProvider({ apiKey }), + + publicProvider() + ], + + { pollingInterval: 5000 } + ) + ``` + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** The `sendTransaction` action now returns an object only consisting of `hash` & `wait`, and not the full [`TransactionResponse`](https://docs.ethers.io/v5/api/providers/types/#providers-TransactionResponse). + + If you require the full `TransactionResponse`, you can use `fetchTransaction`: + + ```diff + import { sendTransaction, fetchTransaction } from '@wagmi/core' + + const { + hash, + wait, + - ...transaction + } = sendTransaction(...) + + +const transaction = fetchTransaction({ hash }) + ``` + + > Why? The old implementation of `sendTransaction` created a long-running async task, causing [UX pitfalls](https://wagmi.sh/docs/prepare-hooks#ux-pitfalls-without-prepare-hooks) when invoked in a click handler. + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: If a `chainId` is passed to `writeContract` or `sendTransaction`, it will no longer attempt to switch chain before sending the transaction. Instead, it will throw an error if the user is on the wrong chain. + + > Why? + > + > - Eagerly prompting to switch chain in these actions created a long-running async task that that makes [iOS App Links](https://wagmi.sh/docs/prepare-hooks#ios-app-link-constraints) vulnerable. + > - Not all wallets support programmatic chain switching. + +### Patch Changes + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The configuration passed to the `writeContract` action now needs to be: + + - prepared with the `prepareWriteContract` action **(new functionality)**, or + - recklessly unprepared **(previous functionality)** + + > Why? [Read here](https://wagmi.sh/docs/prepare-hooks) + + ### Prepared usage + + ```diff + import { prepareWriteContract, writeContract } from '@wagmi/core' + + const tokenId = 69 + + +const config = await prepareWriteContract({ + + addressOrName: '0x...', + + contractInterface: wagmiAbi, + + functionName: 'mint', + + args: [tokenId] + +}) + + const result = await writeContract({ + - addressOrName: '0x...', + - contractInterface: wagmiAbi, + - functionName: 'mint', + - args: [tokenId], + + ...config + }) + ``` + + ### Recklessly unprepared usage + + It is possible to use `writeContract` without preparing the configuration first by passing `mode: 'recklesslyUnprepared'`. + + ```diff + import { writeContract } from '@wagmi/core' + + const tokenId = 69 + + const result = await writeContract({ + + mode: 'recklesslyUnprepared', + addressOrName: '0x...', + contractInterface: wagmiAbi, + functionName: 'mint', + args: [tokenId], + }) + ``` + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - Added the `prepareSendTransaction` hook that prepares the parameters required for sending a transaction. + + It returns config to be passed through to `sendTransaction`. + + ```ts + import { prepareSendTransaction, sendTransaction } from "@wagmi/core"; + + const config = await prepareSendTransaction({ + request: { + to: "moxey.eth", + value: parseEther("1"), + }, + }); + const result = await sendTransaction(config); + ``` + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - Added the `prepareWriteContract` hook that prepares the parameters required for a contract write transaction. + + It returns config to be passed through to `writeContract`. + + Example: + + ```tsx + import { prepareWriteContract, writeContract } from "@wagmi/core"; + + const config = await prepareWriteContract({ + addressOrName: "0x...", + contractInterface: wagmiAbi, + functionName: "mint", + }); + const result = await writeContract(config); + ``` + +* [#739](https://github.com/wevm/wagmi/pull/739) [`c2295a5`](https://github.com/wevm/wagmi/commit/c2295a56cc86d02cc6602e2b4557b8ab9a091a3f) Thanks [@tmm](https://github.com/tmm)! - Fix balance formatting for tokens that do not have 18 decimals. + +- [#759](https://github.com/wevm/wagmi/pull/759) [`959953d`](https://github.com/wevm/wagmi/commit/959953d1f5b3e8189bac56de245c62333470d18e) Thanks [@tmm](https://github.com/tmm)! - Added `fetchTransaction` action: + + ```ts + import { fetchTransaction } from "@wagmi/core"; + + const transaction = await fetchTransaction({ + hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", + }); + ``` + +## 0.4.9 + +### Patch Changes + +- [#721](https://github.com/tmm/wagmi/pull/721) [`abea25f`](https://github.com/tmm/wagmi/commit/abea25fd15d81d1ecaec9d3fbd687042ab29b1e6) Thanks [@tmm](https://github.com/tmm)! - Stay connected to existing `client.connector` when `connect` action fails to connect to new connector. + +* [#721](https://github.com/tmm/wagmi/pull/721) [`abea25f`](https://github.com/tmm/wagmi/commit/abea25fd15d81d1ecaec9d3fbd687042ab29b1e6) Thanks [@tmm](https://github.com/tmm)! - Switch `fetchToken` action to multicall and add `name` output property. + +## 0.4.8 + +### Patch Changes + +- [#693](https://github.com/tmm/wagmi/pull/693) [`56e468c`](https://github.com/tmm/wagmi/commit/56e468c3617ec222527bb3c02eadec3ebeff923a) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Fix import errors with Coinbase Wallet SDK in Vite + +## 0.4.7 + +### Patch Changes + +- [#677](https://github.com/tmm/wagmi/pull/677) [`35e4219`](https://github.com/tmm/wagmi/commit/35e42199af9dd346549c1718e144728f55b8d7dd) Thanks [@jxom](https://github.com/jxom)! - Move `parseContractResult` to `@wagmi/core` + +## 0.4.6 + +### Patch Changes + +- [#670](https://github.com/tmm/wagmi/pull/670) [`29a0d21`](https://github.com/tmm/wagmi/commit/29a0d21ee83995559f63542778dfa805f15e7441) Thanks [@tmm](https://github.com/tmm)! - Added ethers-compatible `deepEqual` function. + +## 0.4.5 + +### Patch Changes + +- [#654](https://github.com/tmm/wagmi/pull/654) [`e66530b`](https://github.com/tmm/wagmi/commit/e66530bf4881b3533c528f8c5a5f41be0eab0a64) Thanks [@jxom](https://github.com/jxom)! - fix `multicall` returning nullish data for all calls unexpectedly + +## 0.4.4 + +### Patch Changes + +- [#616](https://github.com/tmm/wagmi/pull/616) [`7a7a17a`](https://github.com/tmm/wagmi/commit/7a7a17a46d4c9e6465cc46a111b5fe8a56109f1b) Thanks [@tmm](https://github.com/tmm)! - Adds `UNSTABLE_shimOnConnectSelectAccount` flag. With this flag and "disconnected" with `shimDisconnect` enabled, the user is prompted to select a different MetaMask account (than the currently connected account) when trying to connect (e.g. `useConnect`/`connect` action). + +## 0.4.3 + +### Patch Changes + +- [#631](https://github.com/tmm/wagmi/pull/631) [`a780e32`](https://github.com/tmm/wagmi/commit/a780e32e91a0072c795fa0b5a6111302768e2a01) Thanks [@tmm](https://github.com/tmm)! - Fix WalletConnect stale session + +## 0.4.2 + +### Patch Changes + +- [#624](https://github.com/tmm/wagmi/pull/624) [`416fa7e`](https://github.com/tmm/wagmi/commit/416fa7ee1f8019ab86e33fb93783ffddecc02c49) Thanks [@jxom](https://github.com/jxom)! - Fix broken `WebSocketProvider` type defs + +## 0.4.1 + +### Patch Changes + +- [#622](https://github.com/tmm/wagmi/pull/622) [`d171581`](https://github.com/tmm/wagmi/commit/d171581464891dd870d97b6232205da0cb152d9b) Thanks [@tmm](https://github.com/tmm)! - Use `domain.chainId` to validate and switch chain before signing in `signTypedData`. + +* [#618](https://github.com/tmm/wagmi/pull/618) [`a5138e8`](https://github.com/tmm/wagmi/commit/a5138e82a00e4d9469ad78c97b2d34200d7f1fbe) Thanks [@tmm](https://github.com/tmm)! - Fix adding chains when using MetaMask mobile app, add `publicRpcUrls` constant, and default to public endpoint when adding chain. + +## 0.4.0 + +### Minor Changes + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `provider` config option is now required on `createClient`. It is recommended to pass the [`provider` given from `configureChains`](https://wagmi.sh/docs/providers/configuring-chains). + + ```diff + import { + createClient, + + defaultChains, + + configureChains + } from 'wagmi' + +import { publicProvider } from 'wagmi/providers/publicProvider' + + +const { provider } = configureChains(defaultChains, [ + + publicProvider + +]) + + const client = createClient({ + + provider + }) + ``` + + If you previously used an ethers.js Provider, you now need to provide your `chains` on the Provider instance: + + ```diff + import { + createClient, + + defaultChains + } from 'wagmi' + import ethers from 'ethers' + + const client = createClient({ + - provider: getDefaultProvider() + + provider: Object.assign(getDefaultProvider(), { chains: defaultChains }) + }) + ``` + +* [`4f8f3c0`](https://github.com/tmm/wagmi/commit/4f8f3c0d65383bd8bbdfc3f1033adfdb11d80ebb) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Use ethereum-lists chains symbols + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** Removed the `chainId` parameter from `connectors` function on `createClient`. + + ```diff + const client = createClient({ + - connectors({ chainId }) { + + connectors() { + ... + } + }) + ``` + + If you previously derived RPC URLs from the `chainId` on `connectors`, you can now remove that logic as `wagmi` now handles RPC URLs internally when used with `configureChains`. + + ```diff + import { + chain, + + configureChains, + createClient + } from 'wagmi'; + + +import { publicProvider } from 'wagmi/providers/public' + + import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet' + import { InjectedConnector } from 'wagmi/connectors/injected' + import { MetaMaskConnector } from 'wagmi/connectors/metaMask' + import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + + +const { chains } = configureChains( + + [chain.mainnet], + + [publicProvider()] + +); + + const client = createClient({ + - connectors({ chainId }) { + - const chain = chains.find((x) => x.id === chainId) ?? defaultChain + - const rpcUrl = chain.rpcUrls.alchemy + - ? `${chain.rpcUrls.alchemy}/${alchemyId}` + - : chain.rpcUrls.default + - return [ + + connectors: [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: 'wagmi', + - chainId: chain.id, + - jsonRpcUrl: rpcUrl, + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + - rpc: { [chain.id]: rpcUrl }, + }, + }), + new InjectedConnector({ + chains, + options: { name: 'Injected' }, + }), + ] + - }, + }) + ``` + +* [#611](https://github.com/tmm/wagmi/pull/611) [`3089c34`](https://github.com/tmm/wagmi/commit/3089c34196d4034acabac031e0a2f7ee63ae30cc) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `Connector`s `getProvider` method no longer supports the `create` config parameter. Use the `chainId` config option instead. + +- [#596](https://github.com/tmm/wagmi/pull/596) [`a770af7`](https://github.com/tmm/wagmi/commit/a770af7d2cb214b6620d5341115f1e938e1e77ff) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `TypedDataDomain` and `TypedDataField` types were removed and incorporated into `SignTypedDataArgs`. + +* [`4f8f3c0`](https://github.com/tmm/wagmi/commit/4f8f3c0d65383bd8bbdfc3f1033adfdb11d80ebb) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Update symbols to match ethereum-lists/chains + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `writeContract` function parameters have been consolidated into a singular config parameter. + + Before: + + ```tsx + writeContract( + { + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + }, + "feed", + ); + ``` + + After: + + ```tsx + readContract({ + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + functionName: "feed", + }); + ``` + +### Patch Changes + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `readContract` & `watchReadContract` function parameters have been consolidated into a singular config parameter. + + Before: + + ```tsx + readContract( + { + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + }, + "getHunger", + { args: [0] }, + ); + + watchReadContract( + { + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + }, + "getHunger", + { args: [0] }, + (result) => {}, + ); + ``` + + After: + + ```tsx + readContract({ + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + functionName: "getHunger", + args: [0], + }); + + watchReadContract( + { + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + functionName: "getHunger", + args: [0], + }, + (result) => {}, + ); + ``` + +* [#598](https://github.com/tmm/wagmi/pull/598) [`fef26bf`](https://github.com/tmm/wagmi/commit/fef26bf8aef76fc9621e3cd54d4e0ca8f69abb38) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Update `@coinbase/wallet-sdk` peer dependency to `>=3.3.0` to fix errors when connecting with older versions of the Coinbase Wallet extension and mobile app. + +- [#611](https://github.com/tmm/wagmi/pull/611) [`3089c34`](https://github.com/tmm/wagmi/commit/3089c34196d4034acabac031e0a2f7ee63ae30cc) Thanks [@tmm](https://github.com/tmm)! - Added `chainId` config parameter for `writeContract` and `sendTransaction`. + + If `chainId` is provided, the connector will validate that `chainId` is the active chain before sending a transaction (and switch to `chainId` if necessary). + +* [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where the wagmi client's `status` would not update from `"disconnected"` to `"connecting" -> "connected"` when the `connect` action is invoked. + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - Added a `multicall` & `watchMulticall` action that provides multicall support. + + Internally uses the [`multicall3` contract](https://github.com/mds1/multicall). + + [See example usage](https://github.com/tmm/wagmi/blob/194866032985fdd3f49bc46bf1b14181d7cb61d1/packages/core/src/actions/contracts/multicall.test.ts) + +* [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - Added a `readContracts` & `watchReadContracts` action that provides the ability to batch up multiple ethers Contract read-only methods. + +## 0.3.8 + +### Patch Changes + +- [#570](https://github.com/tmm/wagmi/pull/570) [`0e3fe15`](https://github.com/tmm/wagmi/commit/0e3fe15445377f35d6f4142b49bf1c96bfeb62cd) Thanks [@tmm](https://github.com/tmm)! - adds chain for [Foundry](https://github.com/foundry-rs) + +## 0.3.7 + +### Patch Changes + +- [#550](https://github.com/tmm/wagmi/pull/550) [`2a5313e`](https://github.com/tmm/wagmi/commit/2a5313e8cbc9ba6335e8e4b85e43862c9b711bd3) Thanks [@tmm](https://github.com/tmm)! - fix `CoinbaseWalletConnector` possible type error + +* [#548](https://github.com/tmm/wagmi/pull/548) [`0c48719`](https://github.com/tmm/wagmi/commit/0c487199f2421f042abc1f1d139468ccbbc5646a) Thanks [@dohaki](https://github.com/dohaki)! - add ensAddress to Chain type + +- [#549](https://github.com/tmm/wagmi/pull/549) [`89b3a74`](https://github.com/tmm/wagmi/commit/89b3a74ead4234daacd0dcf8506659887ebf0553) Thanks [@tmm](https://github.com/tmm)! - Turns on [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess=) and [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks=) for better runtime safety. + +## 0.3.6 + +### Patch Changes + +- [#526](https://github.com/tmm/wagmi/pull/526) [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f) Thanks [@jxom](https://github.com/jxom)! - Added `shimChainChangedDisconnect` option to `InjectedConnector`. Defaults to `true` for `MetaMaskConnector`. + +* [#526](https://github.com/tmm/wagmi/pull/526) [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f) Thanks [@jxom](https://github.com/jxom)! - Added `lastUsedChainId` property to the wagmi `Client`. + +- [#526](https://github.com/tmm/wagmi/pull/526) [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` config option to the `connect` action. + + Example: + + ```tsx + import { connect } from "@wagmi/core"; + + await connect({ chainId: 69 }); + ``` + +## 0.3.5 + +### Patch Changes + +- [#543](https://github.com/tmm/wagmi/pull/543) [`4d489fd`](https://github.com/tmm/wagmi/commit/4d489fd630dd8c00440bdaf4d646de662c41ff52) Thanks [@tmm](https://github.com/tmm)! - fix fee data formatting for null values + +## 0.3.4 + +### Patch Changes + +- [`c4deb66`](https://github.com/tmm/wagmi/commit/c4deb6655a52e4cc4e5b3fd82202db11d6106848) Thanks [@jxom](https://github.com/jxom)! - infer `options.chainId` config from `chains` on WalletConnectConnector + +## 0.3.3 + +### Patch Changes + +- [#486](https://github.com/tmm/wagmi/pull/486) [`dbfe3dd`](https://github.com/tmm/wagmi/commit/dbfe3dd320d178d6854a8096101200c1508786bb) Thanks [@tmm](https://github.com/tmm)! - add chains entrypoint + +## 0.3.2 + +### Patch Changes + +- [`17212da`](https://github.com/tmm/wagmi/commit/17212da601640110d2835300e6433d1433db212e) Thanks [@jxom](https://github.com/jxom)! - Made the `defaultChains` type generic in `configureChains`. + +## 0.3.1 + +### Patch Changes + +- [#484](https://github.com/tmm/wagmi/pull/484) [`1b9a503`](https://github.com/tmm/wagmi/commit/1b9a5033d51c6655b4f6570c490da6e0e9a29da9) Thanks [@tmm](https://github.com/tmm)! - export React Context + +## 0.3.0 + +### Minor Changes + +- [#408](https://github.com/tmm/wagmi/pull/408) [`bfcc3a5`](https://github.com/tmm/wagmi/commit/bfcc3a51bbb1551753e3ccde6af134e9fd4fec9a) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** The `connectors` option on `createClient` no longer reacts to chain switching. + + **Passing a function to `connectors` has been deprecated.** + + If you previously derived an RPC URL from the `chainId` in `connectors`, you will need to migrate to use the [`configureChains` API](https://wagmi.sh/docs/providers/configuring-chains). + + ### Before + + ```tsx + import { providers } from "ethers"; + import { chain, createClient, defaultChains } from "wagmi"; + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const chains = defaultChains; + const defaultChain = chain.mainnet; + + const client = createClient({ + autoConnect: true, + connectors({ chainId }) { + const chain = chains.find((x) => x.id === chainId) ?? defaultChain; + const rpcUrl = chain.rpcUrls.alchemy + ? `${chain.rpcUrls.alchemy}/${alchemyId}` + : chain.rpcUrls.default; + return [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + chainId: chain.id, + jsonRpcUrl: rpcUrl, + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + rpc: { [chain.id]: rpcUrl }, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ]; + }, + }); + ``` + + ### After + + ```tsx + import { chain, createClient, defaultChains } from "wagmi"; + + import { alchemyProvider } from "wagmi/providers/alchemy"; + import { publicProvider } from "wagmi/providers/public"; + + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const { chains } = configureChains(defaultChains, [ + alchemyProvider({ alchemyId }), + publicProvider(), + ]); + + const client = createClient({ + autoConnect: true, + connectors: [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ], + }); + ``` + +* [#468](https://github.com/tmm/wagmi/pull/468) [`44a884b`](https://github.com/tmm/wagmi/commit/44a884b84171c418f57701e80ef8de972948ef0b) Thanks [@tmm](https://github.com/tmm)! - **Breaking:** Duplicate exports with different names and the same functionality were removed to simplify the public API. In addition, confusing exports were renamed to be more descriptive. + + - `createWagmiClient` alias was removed. Use `createClient` instead. + - `useWagmiClient` alias was removed. Use `useClient` instead. + - `WagmiClient` alias was removed. Use `Client` instead. + - `createWagmiStorage` alias was removed. Use `createStorage` instead. + - `Provider` was renamed and `WagmiProvider` alias was removed. Use `WagmiConfig` instead. + +- [#408](https://github.com/tmm/wagmi/pull/408) [`bfcc3a5`](https://github.com/tmm/wagmi/commit/bfcc3a51bbb1551753e3ccde6af134e9fd4fec9a) Thanks [@jxom](https://github.com/jxom)! - Add `configureChains` API. + + The `configureChains` function allows you to configure your chains with a selected provider (Alchemy, Infura, JSON RPC, Public RPC URLs). This means you don't have to worry about deriving your own RPC URLs for each chain, or instantiating a Ethereum Provider. + + `configureChains` accepts 3 parameters: an array of chains, and an array of providers, and a config object. + + [Learn more about configuring chains & providers.](https://wagmi.sh/docs/providers/configuring-chains) + + ### Before + + ```tsx + import { providers } from "ethers"; + import { chain, createClient, defaultChains } from "wagmi"; + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const chains = defaultChains; + const defaultChain = chain.mainnet; + + const client = createClient({ + autoConnect: true, + connectors({ chainId }) { + const chain = chains.find((x) => x.id === chainId) ?? defaultChain; + const rpcUrl = chain.rpcUrls.alchemy + ? `${chain.rpcUrls.alchemy}/${alchemyId}` + : chain.rpcUrls.default; + return [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + chainId: chain.id, + jsonRpcUrl: rpcUrl, + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + rpc: { [chain.id]: rpcUrl }, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ]; + }, + provider: ({ chainId }) => + new providers.AlchemyProvider(chainId, alchemyId), + }); + ``` + + ### After + + ```tsx + import { chain, createClient, defaultChains } from "wagmi"; + + import { alchemyProvider } from "wagmi/providers/alchemy"; + import { publicProvider } from "wagmi/providers/public"; + + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const { chains, provider, webSocketProvider } = configureChains( + defaultChains, + [alchemyProvider({ alchemyId }), publicProvider()], + ); + + const client = createClient({ + autoConnect: true, + connectors: [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ], + provider, + webSocketProvider, + }); + ``` + +### Patch Changes + +- [#404](https://github.com/tmm/wagmi/pull/404) [`f81c156`](https://github.com/tmm/wagmi/commit/f81c15665e2e71534f84ada3fa705f2d78627472) Thanks [@holic](https://github.com/holic)! - Add `ProviderRpcError` and `RpcError` error classes. + + Certain wagmi-standardized errors may wrap `ProviderRpcError` or `RpcError`. For these cases, you can access the original provider rpc or rpc error value using the `internal` property. + +* [#459](https://github.com/tmm/wagmi/pull/459) [`72dcf7c`](https://github.com/tmm/wagmi/commit/72dcf7c09e814261b2e43a8fa364c57675c472de) Thanks [@tmm](https://github.com/tmm)! - update dependencies + +- [#473](https://github.com/tmm/wagmi/pull/473) [`a54f3e2`](https://github.com/tmm/wagmi/commit/a54f3e23ea385ed8aa4ad188128d7089ba20f83e) Thanks [@cesargdm](https://github.com/cesargdm)! - Add workaround for CoinbaseWalletSDK import on esbuild + +* [#447](https://github.com/tmm/wagmi/pull/447) [`b9ebf78`](https://github.com/tmm/wagmi/commit/b9ebf782e0900725bcb76483686b30f09d357ebd) Thanks [@tmm](https://github.com/tmm)! - Fix case where connector disconnected while app was closed and stale data was returned when autoconnecting. For example, [MetaMask was locked](https://github.com/tmm/wagmi/issues/444) when page was closed. + +## 0.2.5 + +### Patch Changes + +- [`4e03666`](https://github.com/tmm/wagmi/commit/4e03666428d42fc9186c617001b5eb356229677e) Thanks [@tmm](https://github.com/tmm)! - bump dependencies #429 + add imToken support for WC switch chains #432 + fix MetaMask and Brave Wallet collision #436 + +## 0.2.4 + +### Patch Changes + +- [#421](https://github.com/tmm/wagmi/pull/421) [`a232b3f`](https://github.com/tmm/wagmi/commit/a232b3ff5cc41e882c4d2a34c599a8cb670edd2b) Thanks [@tmm](https://github.com/tmm)! - fix erc721 abi + +## 0.2.3 + +### Patch Changes + +- [#412](https://github.com/tmm/wagmi/pull/412) [`80bef4f`](https://github.com/tmm/wagmi/commit/80bef4ff3f714b0b8f896f1b4b658acc7266299b) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Import providers from `ethers` peer dependency rather than `@ethersproject/providers` to avoid multiple conflicting versions being installed + +## 0.2.2 + +### Patch Changes + +- [`018c2a1`](https://github.com/tmm/wagmi/commit/018c2a11b22ee513571cc7f83fd63f7eb169ee70) Thanks [@tmm](https://github.com/tmm)! - - warn and fallback to default client #380 + + - remove signerOrProvider option from read contract #390 + + - MetaMaskConnector #391 + +## 0.2.1 + +### Patch Changes + +- [`afc4607`](https://github.com/tmm/wagmi/commit/afc46071e91601ab8a2b465524da796cd60b6ad4) Thanks [@tmm](https://github.com/tmm)! - - Fix time scaling e9593df + - Use fully-specified path for use-sync-external-store import 7b235c1 + - Update serialize 236fc17 + +## 0.2.0 + +### Minor Changes + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - don't persist account data when `autoConnect` is falsy + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - fix(@wagmi/core): persist connector chains to local storage + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - Favour `message` event over `connecting` event to conform to EIP-1193 + - Export `useWaitForTransaction` + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Initial 0.3.0 release + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Add `cacheOnBlock` config for `useContractRead` + Update `react-query` to v4 + Fix `watchBlockNumber` listener leak + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - add `connecting` event to connectors + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Fix issue where `getProvider` was not being awaited in `getSigner` + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - remove storage persistence of `connector` + - add `chains` to client state + +### Patch Changes + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - add chainId to actions and hooks + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - showtime + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - improve type support for ethers providers + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update zustand + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update babel target + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update block explorers and rpc urls structure + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - keep previous data when watching + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - republish + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - fix stale connectors when switching chains + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - last beta + +## 0.2.0-next.18 + +### Patch Changes + +- showtime + +## 0.2.0-next.17 + +### Patch Changes + +- update block explorers and rpc urls structure + +## 0.2.0-next.16 + +### Patch Changes + +- last beta + +## 0.2.0-next.15 + +### Patch Changes + +- update zustand + +## 0.2.0-next.14 + +### Minor Changes + +- Add `cacheOnBlock` config for `useContractRead` +- Update `react-query` to v4 +- Fix `watchBlockNumber` listener leak + +## 0.2.0-next.13 + +### Patch Changes + +- keep previous data when watching + +## 0.2.0-next.12 + +### Patch Changes + +- add chainId to actions and hooks + +## 0.2.0-next.11 + +### Patch Changes + +- fix stale connectors when switching chains + +## 0.2.0-next.10 + +### Patch Changes + +- republish + +## 0.2.0-next.9 + +### Patch Changes + +- improve type support for ethers providers + +## 0.2.0-next.8 + +### Patch Changes + +- update babel target + +## 0.2.0-next.7 + +### Minor Changes + +- - Favour `message` event over `connecting` event to conform to EIP-1193 + - Export `useWaitForTransaction` + +## 0.2.0-next.6 + +### Minor Changes + +- add `connecting` event to connectors + +## 0.2.0-next.5 + +### Minor Changes + +- don't persist account data when `autoConnect` is falsy + +## 0.2.0-next.4 + +### Minor Changes + +- remove storage persistence of `connector` +- add `chains` to client state + +## 0.2.0-next.3 + +### Minor Changes + +- Fix issue where `getProvider` was not being awaited in `getSigner` + +## 0.2.0-next.2 + +### Minor Changes + +- fix: persist connector chains to local storage + +## 0.2.0-next.1 + +### Minor Changes + +- Initial 0.3.0 release + +## 0.1.22 + +### Patch Changes + +- [`747d895`](https://github.com/tmm/wagmi/commit/747d895a54b562958afde34b1d34e81ab5039e2c) Thanks [@tmm](https://github.com/tmm)! - add warning to WalletLinkConnector + +## 0.1.21 + +### Patch Changes + +- [`c858c51`](https://github.com/tmm/wagmi/commit/c858c51b44d9039f1d0db5bcf016639f47d1931f) Thanks [@tmm](https://github.com/tmm)! - update coinbase connector + +## 0.1.20 + +### Patch Changes + +- [#326](https://github.com/tmm/wagmi/pull/326) [`36e6989`](https://github.com/tmm/wagmi/commit/36e69894f4c27aaad7fb6d678476c8bb870244bb) Thanks [@0xGabi](https://github.com/0xGabi)! - Add Gnosis Chain + +## 0.1.19 + +### Patch Changes + +- [`d467df6`](https://github.com/tmm/wagmi/commit/d467df6374210dbc4b016788b4beb4fded54cb4d) Thanks [@tmm](https://github.com/tmm)! - fix global type leaking + +## 0.1.18 + +### Patch Changes + +- [#294](https://github.com/tmm/wagmi/pull/294) [`1d253f3`](https://github.com/tmm/wagmi/commit/1d253f3a59b61d24c88d25c99decd84a6c734e5d) Thanks [@tmm](https://github.com/tmm)! - change babel target + +## 0.1.17 + +### Patch Changes + +- [#292](https://github.com/tmm/wagmi/pull/292) [`53c9be1`](https://github.com/tmm/wagmi/commit/53c9be17ee0c2ae6b8f34f2351b8858257b3f5f2) Thanks [@tmm](https://github.com/tmm)! - fix private fields + +## 0.1.16 + +### Patch Changes + +- [`79a2499`](https://github.com/tmm/wagmi/commit/79a249989029f818c32c0e84c0dd2c75e8aa990a) Thanks [@tmm](https://github.com/tmm)! - update build target to es2021 + +## 0.1.15 + +### Patch Changes + +- [`f9790b5`](https://github.com/tmm/wagmi/commit/f9790b55600df09c77bb8ca349c5a3457df1b07c) Thanks [@tmm](https://github.com/tmm)! - fix WalletConnect issue + +## 0.1.14 + +### Patch Changes + +- [#236](https://github.com/tmm/wagmi/pull/236) [`53bad61`](https://github.com/tmm/wagmi/commit/53bad615788764e31121678083c382c1bd042fe8) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Updated `@walletconnect/ethereum-provider` to [v1.7.4](https://github.com/WalletConnect/walletconnect-monorepo/releases/tag/1.7.4) + +## 0.1.13 + +### Patch Changes + +- [`8e9412a`](https://github.com/tmm/wagmi/commit/8e9412af71958301ae2f9748febb936e79900aa0) Thanks [@tmm](https://github.com/tmm)! - bump walletlink + +## 0.1.12 + +### Patch Changes + +- [#210](https://github.com/tmm/wagmi/pull/210) [`684468a`](https://github.com/tmm/wagmi/commit/684468aee3e42a1ce2b4b599f3f17d1819213de8) Thanks [@tmm](https://github.com/tmm)! - update chains to match chainslist.org + +## 0.1.11 + +### Patch Changes + +- [#195](https://github.com/tmm/wagmi/pull/195) [`25b6083`](https://github.com/tmm/wagmi/commit/25b6083a662a0236794d1765343467691421c14b) Thanks [@tmm](https://github.com/tmm)! - rename wagmi-private to wagmi-core + +## 0.1.10 + +### Patch Changes + +- [#192](https://github.com/tmm/wagmi/pull/192) [`428cedb`](https://github.com/tmm/wagmi/commit/428cedb3dec4e3e4b9f4559c8e65932e05f94e05) Thanks [@tmm](https://github.com/tmm)! - rename core and testing packages + +## 0.1.9 + +### Patch Changes + +- [#190](https://github.com/tmm/wagmi/pull/190) [`7034bb8`](https://github.com/tmm/wagmi/commit/7034bb868412b9f481b206371280e84c2d52706d) Thanks [@tmm](https://github.com/tmm)! - add shim for metamask chain changed to prevent disconnect + +## 0.1.8 + +### Patch Changes + +- [#137](https://github.com/tmm/wagmi/pull/137) [`dceeb43`](https://github.com/tmm/wagmi/commit/dceeb430d9021fbf98366859cb1cd0149e80c55c) Thanks [@tmm](https://github.com/tmm)! - add siwe guide + +## 0.1.7 + +### Patch Changes + +- [#127](https://github.com/tmm/wagmi/pull/127) [`f05b031`](https://github.com/tmm/wagmi/commit/f05b0310f7f7e6447e9b6c81cedbb27dcf2f3649) Thanks [@tmm](https://github.com/tmm)! - update switch chain return type + +## 0.1.6 + +### Patch Changes + +- [`1412eed`](https://github.com/tmm/wagmi/commit/1412eed0d1494bb4f8c6845a0e890f79e4e68e03) Thanks [@tmm](https://github.com/tmm)! - add frame to injected + +## 0.1.5 + +### Patch Changes + +- [`e338c3b`](https://github.com/tmm/wagmi/commit/e338c3b6cc255742be6a67593aa5da6c17e90fbd) Thanks [@tmm](https://github.com/tmm)! - checksum connector address on change events + + add shim to injected connector for simulating disconnect + +## 0.1.4 + +### Patch Changes + +- [`0176c4e`](https://github.com/tmm/wagmi/commit/0176c4e83fb0c5f159c3c802a1da3d6deb2184ae) Thanks [@tmm](https://github.com/tmm)! - added switchChain to WalletConnect and WalletLink connectors + +## 0.1.3 + +### Patch Changes + +- [`071d7fb`](https://github.com/tmm/wagmi/commit/071d7fbca35ec4832700b5343661ceb2dae20598) Thanks [@tmm](https://github.com/tmm)! - add hardhat chain + +## 0.1.2 + +### Patch Changes + +- [`78bade9`](https://github.com/tmm/wagmi/commit/78bade9d0da97ab38a7e6594c34e3841ec1c8fe6) Thanks [@tmm](https://github.com/tmm)! - add type definitions + +## 0.1.1 + +### Patch Changes + +- [#56](https://github.com/tmm/wagmi/pull/56) [`2ebfd8e`](https://github.com/tmm/wagmi/commit/2ebfd8e85b560f25cd46cff04619c84643cab297) Thanks [@tmm](https://github.com/tmm)! - add chain support status + +## 0.1.0 + +### Minor Changes + +- [#52](https://github.com/tmm/wagmi/pull/52) [`da7a3a6`](https://github.com/tmm/wagmi/commit/da7a3a615def2443f65c041999100ce35e9774cc) Thanks [@tmm](https://github.com/tmm)! - Moves connectors to their own entrypoints to reduce bundle size. + + ```ts + // old - WalletLinkConnector unused, but still in final bundle + import { InjectedConnector, WalletConnectConnector } from "wagmi"; + + // new - WalletLinkConnector not in final bundle + import { InjectedConnector } from "wagmi/connectors/injected"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + ``` + +## 0.0.17 + +### Patch Changes + +- [#25](https://github.com/tmm/wagmi/pull/25) [`9a7dab7`](https://github.com/tmm/wagmi/commit/9a7dab78b3518658bc7d85dc397990f0d28da175) Thanks [@tmm](https://github.com/tmm)! - update response types + +## 0.0.16 + +### Patch Changes + +- [`d1574cf`](https://github.com/tmm/wagmi/commit/d1574cf5f7a578ccd480889c2e375134145a4aba) Thanks [@tmm](https://github.com/tmm)! - add better type information for contract results + +## 0.0.15 + +### Patch Changes + +- [`3909624`](https://github.com/tmm/wagmi/commit/39096249c1fa9516beabb11735beb67c94032879) Thanks [@tmm](https://github.com/tmm)! - make contract read and write execute overrides param optional + +## 0.0.14 + +### Patch Changes + +- [`63312e2`](https://github.com/tmm/wagmi/commit/63312e2b06b8d835abc2908cba399d941ca79408) Thanks [@tmm](https://github.com/tmm)! - add once to contract event + +## 0.0.13 + +### Patch Changes + +- [`6f890b0`](https://github.com/tmm/wagmi/commit/6f890b0dabbdbea913ec91cb8bfc970c05ed0a93) Thanks [@tmm](https://github.com/tmm)! - update readme + +## 0.0.12 + +### Patch Changes + +- [#19](https://github.com/tmm/wagmi/pull/19) [`7bc1c47`](https://github.com/tmm/wagmi/commit/7bc1c47875e9ef24e9c79cfafc6b23e7a838b5bc) Thanks [@tmm](https://github.com/tmm)! - remove console log from walletlink connector + +## 0.0.11 + +### Patch Changes + +- [#17](https://github.com/tmm/wagmi/pull/17) [`571648b`](https://github.com/tmm/wagmi/commit/571648b754f7f538536bafc9387bd3104657ea49) Thanks [@tmm](https://github.com/tmm)! - standardize connector provider + +## 0.0.10 + +### Patch Changes + +- [#15](https://github.com/tmm/wagmi/pull/15) [`5f7675c`](https://github.com/tmm/wagmi/commit/5f7675c3ffd848522d4117c07c1f62b17dfc6616) Thanks [@tmm](https://github.com/tmm)! - read and write contract functions + +## 0.0.9 + +### Patch Changes + +- [#13](https://github.com/tmm/wagmi/pull/13) [`e5545f5`](https://github.com/tmm/wagmi/commit/e5545f5565cf0bbf5e62ec7ccab3051705b1d313) Thanks [@tmm](https://github.com/tmm)! - add testing package + +## 0.0.8 + +### Patch Changes + +- [`5332500`](https://github.com/tmm/wagmi/commit/5332500918ac240d29ffe4d2aed8566a8ac001e4) Thanks [@tmm](https://github.com/tmm)! - update signing + +## 0.0.7 + +### Patch Changes + +- [`0bff89a`](https://github.com/tmm/wagmi/commit/0bff89ab2ad28b2cb9b346d1ac870e859d9278bc) Thanks [@tmm](https://github.com/tmm)! - update injected connector + +## 0.0.6 + +### Patch Changes + +- [`37d39d1`](https://github.com/tmm/wagmi/commit/37d39d174ddfa122462bbe2d02141cd61eb9db4a) Thanks [@tmm](https://github.com/tmm)! - add message signing + +## 0.0.5 + +### Patch Changes + +- [`d7d94f0`](https://github.com/tmm/wagmi/commit/d7d94f06f7d30468e5e39d64db63124c6315cf82) Thanks [@tmm](https://github.com/tmm)! - fix injected connector name + +## 0.0.4 + +### Patch Changes + +- [`29fbe29`](https://github.com/tmm/wagmi/commit/29fbe2920046b9e87a34faa04500ccf3c4f83748) Thanks [@tmm](https://github.com/tmm)! - fix external deps + +## 0.0.3 + +### Patch Changes + +- [#6](https://github.com/tmm/wagmi/pull/6) [`8dc3a5d`](https://github.com/tmm/wagmi/commit/8dc3a5d5f418813b09663534fe585d9bcf94dbeb) Thanks [@tmm](https://github.com/tmm)! - clean up deps + +## 0.0.2 + +### Patch Changes + +- [#4](https://github.com/tmm/wagmi/pull/4) [`2fbd821`](https://github.com/tmm/wagmi/commit/2fbd8216379bd03c9cc5c06b10b75637e75cb7d8) Thanks [@tmm](https://github.com/tmm)! - init changesets diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000000..b46e39c559 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,13 @@ +# @wagmi/core + +VanillaJS library for Ethereum + +## Installation + +```bash +pnpm add @wagmi/core viem +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000000..00589ee839 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,100 @@ +{ + "name": "@wagmi/core", + "description": "VanillaJS library for Ethereum", + "version": "2.17.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/core" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen experimental internal query", + "test:build": "publint --strict && attw --pack --ignore-rules cjs-resolves-to-esm" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "/actions", + "/chains", + "/experimental", + "/internal", + "/query" + ], + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./actions": { + "types": "./dist/types/exports/actions.d.ts", + "default": "./dist/esm/exports/actions.js" + }, + "./chains": { + "types": "./dist/types/exports/chains.d.ts", + "default": "./dist/esm/exports/chains.js" + }, + "./codegen": { + "types": "./dist/types/exports/codegen.d.ts", + "default": "./dist/esm/exports/codegen.js" + }, + "./experimental": { + "types": "./dist/types/exports/experimental.d.ts", + "default": "./dist/esm/exports/experimental.js" + }, + "./internal": { + "types": "./dist/types/exports/internal.d.ts", + "default": "./dist/esm/exports/internal.js" + }, + "./query": { + "types": "./dist/types/exports/query.d.ts", + "default": "./dist/esm/exports/query.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "actions": ["./dist/types/exports/actions.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "codegen": ["./dist/types/exports/codegen.d.ts"], + "experimental": ["./dist/types/exports/experimental.d.ts"], + "internal": ["./dist/types/exports/internal.d.ts"], + "query": ["./dist/types/exports/query.d.ts"] + } + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "devDependencies": { + "@tanstack/query-core": "catalog:" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": ["wagmi", "eth", "ethereum", "dapps", "wallet", "web3"] +} diff --git a/packages/core/src/actions/call.test.ts b/packages/core/src/actions/call.test.ts new file mode 100644 index 0000000000..2ef01160da --- /dev/null +++ b/packages/core/src/actions/call.test.ts @@ -0,0 +1,149 @@ +import { accounts, address, config } from '@wagmi/test' +import { parseEther, parseGwei } from 'viem' +import { expect, test } from 'vitest' + +import { call } from './call.js' + +const name4bytes = '0x06fdde03' +const mint4bytes = '0x1249c58b' +const mintWithParams4bytes = '0xa0712d68' +const fourTwenty = + '00000000000000000000000000000000000000000000000000000000000001a4' + +const account = accounts[0] + +test('default', async () => { + await expect( + call(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + }), + ).resolves.toMatchInlineSnapshot(` + { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + } + `) +}) + +test('zero data', async () => { + await expect( + call(config, { + account, + data: mint4bytes, + to: address.wagmiMintExample, + }), + ).resolves.toMatchInlineSnapshot(` + { + "data": undefined, + } + `) +}) + +// TODO: Re-enable +test.skip('parameters: blockNumber', async () => { + await expect( + call(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + blockNumber: 16280770n, + }), + ).resolves.toMatchInlineSnapshot(` + { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + } + `) +}) + +test('insufficient funds', async () => { + await expect( + call(config, { + account, + to: accounts[1], + value: parseEther('100000'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account. + + This error could arise when the account does not have enough funds to: + - pay for the total gas fee, + - pay for the value to send. + + The cost of the transaction is calculated as \`gas * gas fee + value\`, where: + - \`gas\` is the amount of gas needed for transaction to execute, + - \`gas fee\` is the gas fee, + - \`value\` is the amount of ether to send to the recipient. + + Raw Call Arguments: + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + to: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 + value: 100000 ETH + + Details: Insufficient funds for gas * price + value + Version: viem@2.29.2] + `) +}) + +test('maxFeePerGas less than maxPriorityFeePerGas', async () => { + await expect( + call(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('22'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: The provided tip (\`maxPriorityFeePerGas\` = 22 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 20 gwei). + + Raw Call Arguments: + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 + data: 0x06fdde03 + maxFeePerGas: 20 gwei + maxPriorityFeePerGas: 22 gwei + + Version: viem@2.29.2] + `) +}) + +test('contract revert (contract error)', async () => { + await expect( + call(config, { + account, + data: `${mintWithParams4bytes}${fourTwenty}`, + to: address.wagmiMintExample, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: Execution reverted with reason: Token ID is taken. + + Raw Call Arguments: + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 + data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4 + + Details: execution reverted: Token ID is taken + Version: viem@2.29.2] + `) +}) + +test('contract revert (insufficient params)', async () => { + await expect( + call(config, { + account, + data: mintWithParams4bytes, + to: address.wagmiMintExample, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: Execution reverted for an unknown reason. + + Raw Call Arguments: + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 + data: 0xa0712d68 + + Details: execution reverted + Version: viem@2.29.2] + `) +}) diff --git a/packages/core/src/actions/call.ts b/packages/core/src/actions/call.ts new file mode 100644 index 0000000000..90e6c1ae20 --- /dev/null +++ b/packages/core/src/actions/call.ts @@ -0,0 +1,27 @@ +import type { + CallErrorType as viem_CallErrorType, + CallParameters as viem_CallParameters, + CallReturnType as viem_CallReturnType, +} from 'viem' +import { call as viem_call } from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import { getAction } from '../utils/getAction.js' + +export type CallParameters = + viem_CallParameters & ChainIdParameter + +export type CallReturnType = viem_CallReturnType + +export type CallErrorType = viem_CallErrorType + +export async function call( + config: config, + parameters: CallParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_call, 'call') + return action(rest) +} diff --git a/packages/core/src/actions/codegen/createReadContract.test-d.ts b/packages/core/src/actions/codegen/createReadContract.test-d.ts new file mode 100644 index 0000000000..f5c9dd302b --- /dev/null +++ b/packages/core/src/actions/codegen/createReadContract.test-d.ts @@ -0,0 +1,130 @@ +import { abi, config, mainnet, optimism } from '@wagmi/test' +import { assertType, expectTypeOf, test } from 'vitest' + +import { createReadContract } from './createReadContract.js' + +test('default', async () => { + const readErc20 = createReadContract({ + abi: abi.erc20, + address: '0x', + }) + + const result = await readErc20(config, { + functionName: 'balanceOf', + args: ['0x'], + chainId: 1, + }) + expectTypeOf(result).toEqualTypeOf() +}) + +test('multichain address', async () => { + const readErc20 = createReadContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const result = await readErc20(config, { + functionName: 'balanceOf', + args: ['0x'], + chainId: mainnet.id, + // ^? + }) + assertType(result) + + readErc20(config, { + functionName: 'balanceOf', + args: ['0x'], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + readErc20(config, { + functionName: 'balanceOf', + args: ['0x'], + // @ts-expect-error address not allowed + address: '0x', + }) +}) + +test('overloads', async () => { + const readViewOverloads = createReadContract({ + abi: abi.viewOverloads, + address: '0x', + }) + + const result1 = await readViewOverloads(config, { + functionName: 'foo', + }) + assertType(result1) + + const result2 = await readViewOverloads(config, { + functionName: 'foo', + args: [], + }) + assertType(result2) + + const result3 = await readViewOverloads(config, { + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3) + + const result4 = await readViewOverloads(config, { + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType<{ + foo: `0x${string}` + bar: `0x${string}` + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + }>(result4) +}) + +test('functionName', async () => { + const readErc20BalanceOf = createReadContract({ + abi: abi.erc20, + address: '0x', + functionName: 'balanceOf', + }) + + const result = await readErc20BalanceOf(config, { + args: ['0x'], + chainId: 1, + }) + expectTypeOf(result).toEqualTypeOf() +}) + +test('functionName with overloads', async () => { + const readViewOverloads = createReadContract({ + abi: abi.viewOverloads, + address: '0x', + functionName: 'foo', + }) + + const result1 = await readViewOverloads(config, {}) + assertType(result1) + + const result2 = await readViewOverloads(config, { + args: [], + }) + assertType(result2) + + const result3 = await readViewOverloads(config, { + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3) + + const result4 = await readViewOverloads(config, { + args: ['0x', '0x'], + }) + assertType<{ + foo: `0x${string}` + bar: `0x${string}` + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + }>(result4) +}) diff --git a/packages/core/src/actions/codegen/createReadContract.test.ts b/packages/core/src/actions/codegen/createReadContract.test.ts new file mode 100644 index 0000000000..9de7ae718f --- /dev/null +++ b/packages/core/src/actions/codegen/createReadContract.test.ts @@ -0,0 +1,50 @@ +import { abi, address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { createReadContract } from './createReadContract.js' + +test('default', async () => { + const readWagmiMintExample = createReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + }) + + await expect( + readWagmiMintExample(config, { + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ).resolves.toMatchInlineSnapshot('4n') +}) + +test('multichain', async () => { + const readWagmiMintExample = createReadContract({ + address: { + [chain.mainnet.id]: address.wagmiMintExample, + [chain.mainnet2.id]: address.wagmiMintExample, + }, + abi: abi.wagmiMintExample, + }) + + await expect( + readWagmiMintExample(config, { + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + chainId: chain.mainnet2.id, + }), + ).resolves.toMatchInlineSnapshot('4n') +}) + +test('functionName', async () => { + const readWagmiMintExampleBalanceOf = createReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + }) + + await expect( + readWagmiMintExampleBalanceOf(config, { + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ).resolves.toMatchInlineSnapshot('4n') +}) diff --git a/packages/core/src/actions/codegen/createReadContract.ts b/packages/core/src/actions/codegen/createReadContract.ts new file mode 100644 index 0000000000..f7dbfbed45 --- /dev/null +++ b/packages/core/src/actions/codegen/createReadContract.ts @@ -0,0 +1,100 @@ +import type { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' + +import type { Config } from '../../createConfig.js' +import type { UnionCompute, UnionStrictOmit } from '../../types/utils.js' +import { getAccount } from '../getAccount.js' +import { getChainId } from '../getChainId.js' +import { + type ReadContractParameters, + type ReadContractReturnType, + readContract, +} from '../readContract.js' + +type stateMutability = 'pure' | 'view' + +export type CreateReadContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateReadContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, + /// + omittedProperties extends 'abi' | 'address' | 'chainId' | 'functionName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (address extends Record ? 'chainId' : never) + | (functionName extends undefined ? never : 'functionName'), +> = < + config extends Config, + name extends functionName extends ContractFunctionName + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, +>( + config: config, + parameters: UnionCompute< + UnionStrictOmit< + ReadContractParameters, + omittedProperties + > + > & + (address extends Record + ? { chainId?: keyof address | undefined } + : unknown), +) => Promise> + +export function createReadContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + c: CreateReadContractParameters, +): CreateReadContractReturnType { + if (c.address !== undefined && typeof c.address === 'object') + return (config, parameters) => { + const configChainId = getChainId(config) + const account = getAccount(config) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return readContract(config, { + ...(parameters as any), + ...(c.functionName ? { functionName: c.functionName } : {}), + address: c.address?.[chainId], + abi: c.abi, + }) + } + + return (config, parameters) => { + return readContract(config, { + ...(parameters as any), + ...(c.address ? { address: c.address } : {}), + ...(c.functionName ? { functionName: c.functionName } : {}), + abi: c.abi, + }) + } +} diff --git a/packages/core/src/actions/codegen/createSimulateContract.test-d.ts b/packages/core/src/actions/codegen/createSimulateContract.test-d.ts new file mode 100644 index 0000000000..91e5998977 --- /dev/null +++ b/packages/core/src/actions/codegen/createSimulateContract.test-d.ts @@ -0,0 +1,211 @@ +import { abi, config, mainnet, optimism } from '@wagmi/test' +import { http, type Address } from 'viem' +import { celo } from 'viem/chains' +import { assertType, expectTypeOf, test } from 'vitest' + +import { createConfig } from '../../createConfig.js' +import { createSimulateContract } from './createSimulateContract.js' + +test('default', async () => { + const simulateErc20 = createSimulateContract({ + abi: abi.erc20, + address: '0x', + }) + + const result = await simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + + expectTypeOf(result).toMatchTypeOf<{ + result: boolean + request: { + chainId: 1 + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + }>() +}) + +test('multichain address', async () => { + const simulateErc20 = createSimulateContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const result = await simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: optimism.id, + }) + expectTypeOf(result.result).toEqualTypeOf() + + simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error address not allowed + address: '0x', + }) +}) + +test('overloads', async () => { + const simulateWriteOverloads = createSimulateContract({ + abi: abi.writeOverloads, + address: '0x', + }) + + const result1 = await simulateWriteOverloads(config, { + functionName: 'foo', + }) + assertType(result1.result) + + const result2 = await simulateWriteOverloads(config, { + functionName: 'foo', + args: [], + }) + assertType(result2.result) + + const result3 = await simulateWriteOverloads(config, { + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.result) + + const result4 = await simulateWriteOverloads(config, { + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.result) +}) + +test('functionName', async () => { + const simulateErc20 = createSimulateContract({ + abi: abi.erc20, + address: '0x', + functionName: 'transferFrom', + }) + + const result = await simulateErc20(config, { + args: ['0x', '0x', 123n], + chainId: 1, + }) + + expectTypeOf(result).toMatchTypeOf<{ + result: boolean + request: { + chainId: 1 + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + }>() +}) + +test('functionName with overloads', async () => { + const simulateWriteOverloads = createSimulateContract({ + abi: abi.writeOverloads, + address: '0x', + functionName: 'foo', + }) + + const result1 = await simulateWriteOverloads(config, {}) + assertType(result1.result) + + const result2 = await simulateWriteOverloads(config, { + args: [], + }) + assertType(result2.result) + + const result3 = await simulateWriteOverloads(config, { + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.result) + + const result4 = await simulateWriteOverloads(config, { + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.result) +}) + +test('chain formatters', async () => { + const simulateErc20 = createSimulateContract({ + abi: abi.erc20, + address: '0x', + }) + + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + const response = await simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + }) + if (response.chainId === celo.id) { + expectTypeOf(response.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + const response2 = await simulateErc20(config, { + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: celo.id, + }) + expectTypeOf(response2.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() +}) diff --git a/packages/core/src/actions/codegen/createSimulateContract.test.ts b/packages/core/src/actions/codegen/createSimulateContract.test.ts new file mode 100644 index 0000000000..bd37d13770 --- /dev/null +++ b/packages/core/src/actions/codegen/createSimulateContract.test.ts @@ -0,0 +1,137 @@ +import { abi, address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from '../connect.js' +import { disconnect } from '../disconnect.js' +import { createSimulateContract } from './createSimulateContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const simulateWagmiMintExample = createSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + }) + + await expect( + simulateWagmiMintExample(config, { + functionName: 'mint', + }), + ).resolves.toMatchInlineSnapshot(` + { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": undefined, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) + + await disconnect(config, { connector }) +}) + +test('multichain', async () => { + await connect(config, { connector }) + + const simulateWagmiMintExample = createSimulateContract({ + address: { + [chain.mainnet.id]: address.wagmiMintExample, + [chain.mainnet2.id]: address.wagmiMintExample, + }, + abi: abi.wagmiMintExample, + }) + + await expect( + simulateWagmiMintExample(config, { + functionName: 'mint', + chainId: chain.mainnet2.id, + }), + ).resolves.toMatchInlineSnapshot(` + { + "chainId": 456, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 456, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) + + await disconnect(config, { connector }) +}) + +test('functionName', async () => { + await connect(config, { connector }) + + const simulateWagmiMintExample = createSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + }) + + await expect( + simulateWagmiMintExample(config, {}), + ).resolves.toMatchInlineSnapshot(` + { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": undefined, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) + + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/codegen/createSimulateContract.ts b/packages/core/src/actions/codegen/createSimulateContract.ts new file mode 100644 index 0000000000..016384e245 --- /dev/null +++ b/packages/core/src/actions/codegen/createSimulateContract.ts @@ -0,0 +1,122 @@ +import type { + Abi, + Account, + Address, + Chain, + ContractFunctionArgs, + ContractFunctionName, + SimulateContractParameters as viem_SimulateContractParameters, +} from 'viem' + +import type { Config } from '../../createConfig.js' +import type { SelectChains } from '../../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../../types/properties.js' +import type { UnionCompute, UnionStrictOmit } from '../../types/utils.js' +import { getAccount } from '../getAccount.js' +import { getChainId } from '../getChainId.js' +import { + type SimulateContractReturnType, + simulateContract, +} from '../simulateContract.js' + +type stateMutability = 'nonpayable' | 'payable' + +export type CreateSimulateContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateSimulateContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, +> = < + config extends Config, + name extends functionName extends ContractFunctionName + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + chainId extends config['chains'][number]['id'] | undefined = undefined, + /// + chains extends readonly Chain[] = SelectChains, +>( + config: config, + parameters: { + [key in keyof chains]: UnionCompute< + UnionStrictOmit< + viem_SimulateContractParameters< + abi, + name, + args, + chains[key], + chains[key], + Account | Address + >, + | 'abi' + | 'chain' + | (address extends undefined ? never : 'address') + | (functionName extends undefined ? never : 'functionName') + > + > & + ChainIdParameter & + ConnectorParameter & { + chainId?: address extends Record + ? + | keyof address + | (chainId extends keyof address ? chainId : never) + | undefined + : chainId | number | undefined + } + }[number], +) => Promise> + +export function createSimulateContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + c: CreateSimulateContractParameters, +): CreateSimulateContractReturnType { + if (c.address !== undefined && typeof c.address === 'object') + return (config, parameters) => { + const configChainId = getChainId(config) + const account = getAccount(config) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return simulateContract(config, { + ...(parameters as any), + ...(c.functionName ? { functionName: c.functionName } : {}), + address: c.address?.[chainId], + abi: c.abi, + }) + } + + return (config, parameters) => { + return simulateContract(config, { + ...(parameters as any), + ...(c.address ? { address: c.address } : {}), + ...(c.functionName ? { functionName: c.functionName } : {}), + abi: c.abi, + }) + } +} diff --git a/packages/core/src/actions/codegen/createWatchContractEvent.test-d.ts b/packages/core/src/actions/codegen/createWatchContractEvent.test-d.ts new file mode 100644 index 0000000000..6c0485f39d --- /dev/null +++ b/packages/core/src/actions/codegen/createWatchContractEvent.test-d.ts @@ -0,0 +1,123 @@ +import { abi, config, mainnet, optimism } from '@wagmi/test' +import { http, webSocket } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../../createConfig.js' +import { createWatchContractEvent } from './createWatchContractEvent.js' + +test('default', () => { + const watchErc20Event = createWatchContractEvent({ + abi: abi.erc20, + }) + + watchErc20Event(config, { + eventName: 'Transfer', + chainId: 1, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) + +test('multichain address', () => { + const watchErc20Event = createWatchContractEvent({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + watchErc20Event(config, { + eventName: 'Transfer', + chainId: mainnet.id, + // ^? + onLogs() {}, + }) + + watchErc20Event(config, { + eventName: 'Transfer', + // @ts-expect-error chain id must match address keys + chainId: 420, + onLogs() {}, + }) + + watchErc20Event(config, { + eventName: 'Transfer', + // @ts-expect-error chain id must match address keys + address: '0x', + onLogs() {}, + }) +}) + +test('differing transports', () => { + const watchErc20Event = createWatchContractEvent({ + abi: abi.erc20, + }) + + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + watchErc20Event(config, { + poll: false, + address: '0x', + onLogs() {}, + }) + + watchErc20Event(config, { + chainId: mainnet.id, + poll: true, + address: '0x', + onLogs() {}, + }) + watchErc20Event(config, { + config, + chainId: mainnet.id, + // @ts-expect-error poll required since http transport + poll: false, + address: '0x', + onLogs() {}, + }) + + watchErc20Event(config, { + chainId: optimism.id, + poll: true, + address: '0x', + onLogs() {}, + }) + watchErc20Event(config, { + chainId: optimism.id, + poll: false, + address: '0x', + onLogs() {}, + }) +}) + +test('eventName', () => { + const watchErc20Event = createWatchContractEvent({ + abi: abi.erc20, + eventName: 'Transfer', + }) + + watchErc20Event(config, { + chainId: 1, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) diff --git a/packages/core/src/actions/codegen/createWatchContractEvent.test.ts b/packages/core/src/actions/codegen/createWatchContractEvent.test.ts new file mode 100644 index 0000000000..19ba09b2a2 --- /dev/null +++ b/packages/core/src/actions/codegen/createWatchContractEvent.test.ts @@ -0,0 +1,41 @@ +import { abi, address, chain, config } from '@wagmi/test' +import type { WatchEventOnLogsParameter } from 'viem' +import { test } from 'vitest' + +import { createWatchContractEvent } from './createWatchContractEvent.js' + +test('default', async () => { + const watchErc20Event = createWatchContractEvent({ + address: address.usdc, + abi: abi.wagmiMintExample, + }) + + let logs: WatchEventOnLogsParameter = [] + const unwatch = watchErc20Event(config, { + eventName: 'Transfer', + onLogs(next) { + logs = logs.concat(next) + }, + }) + unwatch() +}) + +test('multichain', async () => { + const watchErc20Event = createWatchContractEvent({ + address: { + [chain.mainnet.id]: address.usdc, + [chain.mainnet2.id]: address.usdc, + }, + abi: abi.wagmiMintExample, + }) + + let logs: WatchEventOnLogsParameter = [] + const unwatch = watchErc20Event(config, { + eventName: 'Transfer', + chainId: chain.mainnet2.id, + onLogs(next) { + logs = logs.concat(next) + }, + }) + unwatch() +}) diff --git a/packages/core/src/actions/codegen/createWatchContractEvent.ts b/packages/core/src/actions/codegen/createWatchContractEvent.ts new file mode 100644 index 0000000000..2817efc704 --- /dev/null +++ b/packages/core/src/actions/codegen/createWatchContractEvent.ts @@ -0,0 +1,88 @@ +import type { Abi, Address, ContractEventName } from 'viem' + +import type { Config } from '../../createConfig.js' +import type { UnionCompute, UnionStrictOmit } from '../../types/utils.js' +import { getAccount } from '../getAccount.js' +import { getChainId } from '../getChainId.js' +import { + type WatchContractEventParameters, + type WatchContractEventReturnType, + watchContractEvent, +} from '../watchContractEvent.js' + +export type CreateWatchContractEventParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + eventName extends ContractEventName | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + eventName?: eventName | ContractEventName | undefined +} + +export type CreateWatchContractEventReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + eventName extends ContractEventName | undefined, + /// + omittedProperties extends 'abi' | 'address' | 'chainId' | 'eventName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (address extends Record ? 'chainId' : never) + | (eventName extends undefined ? never : 'eventName'), +> = < + config extends Config, + name extends eventName extends ContractEventName + ? eventName + : ContractEventName, + strict extends boolean | undefined = undefined, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: UnionCompute< + UnionStrictOmit< + WatchContractEventParameters, + omittedProperties + > + > & + (address extends Record + ? { chainId?: keyof address | undefined } + : unknown), +) => WatchContractEventReturnType + +export function createWatchContractEvent< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + eventName extends ContractEventName | undefined = undefined, +>( + c: CreateWatchContractEventParameters, +): CreateWatchContractEventReturnType { + if (c.address !== undefined && typeof c.address === 'object') + return (config, parameters) => { + const configChainId = getChainId(config) + const account = getAccount(config) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return watchContractEvent(config, { + ...(parameters as any), + ...(c.eventName ? { eventName: c.eventName } : {}), + address: c.address?.[chainId], + abi: c.abi, + }) + } + + return (config, parameters) => { + return watchContractEvent(config, { + ...(parameters as any), + ...(c.address ? { address: c.address } : {}), + ...(c.eventName ? { eventName: c.eventName } : {}), + abi: c.abi, + }) + } +} diff --git a/packages/core/src/actions/codegen/createWriteContract.test-d.ts b/packages/core/src/actions/codegen/createWriteContract.test-d.ts new file mode 100644 index 0000000000..9c69ede5bf --- /dev/null +++ b/packages/core/src/actions/codegen/createWriteContract.test-d.ts @@ -0,0 +1,129 @@ +import { abi, config, mainnet, optimism } from '@wagmi/test' +import { test } from 'vitest' + +import { simulateContract } from '../simulateContract.js' +import { createWriteContract } from './createWriteContract.js' + +test('default', () => { + const writeErc20 = createWriteContract({ + abi: abi.erc20, + address: '0x', + }) + + writeErc20(config, { + functionName: 'transfer', + args: ['0x', 123n], + }) +}) + +test('multichain address', () => { + const writeErc20 = createWriteContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + writeErc20(config, { + functionName: 'transfer', + args: ['0x', 123n], + chainId: mainnet.id, + // ^? + }) + + writeErc20(config, { + functionName: 'transfer', + args: ['0x', 123n], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + writeErc20(config, { + // @ts-expect-error address not allowed + address: '0x', + functionName: 'transfer', + args: ['0x', 123n], + }) +}) + +test('overloads', () => { + const writeOverloads = createWriteContract({ + abi: abi.writeOverloads, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + writeOverloads(config, { + functionName: 'foo', + args: [], + }) + + writeOverloads(config, { + functionName: 'foo', + args: ['0x'], + }) + + writeOverloads(config, { + functionName: 'foo', + args: ['0x', '0x'], + }) +}) + +test('useSimulateContract', async () => { + const writeErc20 = createWriteContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const { request } = await simulateContract(config, { + account: '0x', + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + + writeErc20(config, request) +}) + +test('functionName', () => { + const writeErc20 = createWriteContract({ + abi: abi.erc20, + address: '0x', + functionName: 'transfer', + }) + + writeErc20(config, { + args: ['0x', 123n], + }) +}) + +test('functionName with overloads', () => { + const writeOverloads = createWriteContract({ + abi: abi.writeOverloads, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + functionName: 'foo', + }) + + writeOverloads(config, { + args: [], + }) + + writeOverloads(config, { + args: ['0x'], + }) + + writeOverloads(config, { + args: ['0x', '0x'], + }) +}) diff --git a/packages/core/src/actions/codegen/createWriteContract.test.ts b/packages/core/src/actions/codegen/createWriteContract.test.ts new file mode 100644 index 0000000000..04511d13ab --- /dev/null +++ b/packages/core/src/actions/codegen/createWriteContract.test.ts @@ -0,0 +1,11 @@ +import { abi } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { createWriteContract } from './createWriteContract.js' + +test('default', () => { + const writeErc20 = createWriteContract({ + abi: abi.erc20, + }) + expect(writeErc20).toBeDefined() +}) diff --git a/packages/core/src/actions/codegen/createWriteContract.ts b/packages/core/src/actions/codegen/createWriteContract.ts new file mode 100644 index 0000000000..6c1d891a3f --- /dev/null +++ b/packages/core/src/actions/codegen/createWriteContract.ts @@ -0,0 +1,145 @@ +import type { + Abi, + Account, + Address, + Chain, + ContractFunctionArgs, + ContractFunctionName, + WriteContractParameters as viem_WriteContractParameters, +} from 'viem' + +import type { Config } from '../../createConfig.js' +import type { SelectChains } from '../../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../../types/properties.js' +import type { + Compute, + UnionCompute, + UnionStrictOmit, +} from '../../types/utils.js' +import { getAccount } from '../getAccount.js' +import { getChainId } from '../getChainId.js' +import { + type WriteContractReturnType, + writeContract, +} from '../writeContract.js' + +type stateMutability = 'nonpayable' | 'payable' + +export type CreateWriteContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateWriteContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, +> = < + config extends Config, + name extends functionName extends ContractFunctionName + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + chainId extends config['chains'][number]['id'], + /// + allFunctionNames = ContractFunctionName, + chains extends readonly Chain[] = SelectChains, + omittedProperties extends 'abi' | 'address' | 'functionName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (functionName extends undefined ? never : 'functionName'), +>( + config: config, + parameters: UnionCompute< + { + [key in keyof chains]: UnionStrictOmit< + viem_WriteContractParameters< + abi, + name, + args, + chains[key], + Account, + chains[key], + allFunctionNames + >, + omittedProperties | 'chain' + > + }[number] & + (address extends Record + ? { + chainId?: + | keyof address + | (chainId extends keyof address ? chainId : never) + | undefined + } + : Compute>) & + ConnectorParameter & { + /** @deprecated */ + __mode?: 'prepared' + } + >, +) => Promise + +export function createWriteContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + c: CreateWriteContractParameters, +): CreateWriteContractReturnType { + if (c.address !== undefined && typeof c.address === 'object') + return (config, parameters) => { + const configChainId = getChainId(config) + const account = getAccount(config) + + let chainId: number | undefined + if (parameters.chainId) chainId = parameters.chainId + else if ( + (parameters as unknown as { account: Address | Account | undefined }) + .account && + (parameters as unknown as { account: Address | Account | undefined }) + .account === account.address + ) + chainId = account.chainId + else if ( + (parameters as unknown as { account: Address | Account | undefined }) + .account === undefined + ) + chainId = account.chainId + else chainId = configChainId + + return writeContract(config, { + ...(parameters as any), + ...(c.functionName ? { functionName: c.functionName } : {}), + address: chainId ? c.address?.[chainId] : undefined, + abi: c.abi, + }) + } + + return (config, parameters) => { + return writeContract(config, { + ...(parameters as any), + ...(c.address ? { address: c.address } : {}), + ...(c.functionName ? { functionName: c.functionName } : {}), + abi: c.abi, + }) + } +} diff --git a/packages/core/src/actions/connect.test-d.ts b/packages/core/src/actions/connect.test-d.ts new file mode 100644 index 0000000000..ad790b1209 --- /dev/null +++ b/packages/core/src/actions/connect.test-d.ts @@ -0,0 +1,48 @@ +import { accounts } from '@wagmi/test' +import { http } from 'viem' +import { mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import { mock } from '../connectors/mock.js' +import { type Connector, createConfig } from '../createConfig.js' +import { connect } from './connect.js' + +const config = createConfig({ + chains: [mainnet], + transports: { [mainnet.id]: http() }, +}) + +test('parameters: connector (ConnectorFn)', () => { + const connectorFn = mock({ accounts }) + + connect(config, { + connector: connectorFn, + foo: 'bar', + }) + expectTypeOf< + typeof connectorFn extends CreateConnectorFn ? true : false + >().toEqualTypeOf() + + type Result = NonNullable< + Parameters>[1] + > + expectTypeOf().toEqualTypeOf() +}) + +test('parameters: connector (Connector)', () => { + const connector = config._internal.connectors.setup(mock({ accounts })) + + connect(config, { + connector, + foo: 'bar', + }) + expectTypeOf< + typeof connector extends Connector ? true : false + >().toEqualTypeOf() + + type Result = NonNullable< + Parameters>[1] + > + expectTypeOf().toEqualTypeOf() +}) diff --git a/packages/core/src/actions/connect.test.ts b/packages/core/src/actions/connect.test.ts new file mode 100644 index 0000000000..eece8c3a4d --- /dev/null +++ b/packages/core/src/actions/connect.test.ts @@ -0,0 +1,71 @@ +import { accounts, chain, config } from '@wagmi/test' +import { beforeEach, expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' + +const connector = config._internal.connectors.setup(mock({ accounts })) + +beforeEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test('default', async () => { + await expect(connect(config, { connector })).resolves.toMatchObject( + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ) +}) + +test('parameters: chainId', async () => { + const chainId = chain.mainnet2.id + await expect(connect(config, { connector, chainId })).resolves.toMatchObject( + expect.objectContaining({ + accounts: expect.any(Array), + chainId, + }), + ) +}) + +test('parameters: connector', async () => { + const connector_ = config._internal.connectors.setup(mock({ accounts })) + await expect( + connect(config, { connector: connector_ }), + ).resolves.toMatchObject( + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ) + await disconnect(config, { connector: connector_ }) +}) + +test('behavior: user rejected request', async () => { + const connector_ = config._internal.connectors.setup( + mock({ + accounts, + features: { connectError: true }, + }), + ) + await expect( + connect(config, { connector: connector_ }), + ).rejects.toMatchInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to connect. + Version: viem@2.29.2] + `) +}) + +test('behavior: already connected', async () => { + await connect(config, { connector }) + await expect(connect(config, { connector })).rejects.toMatchInlineSnapshot(` + [ConnectorAlreadyConnectedError: Connector already connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/actions/connect.ts b/packages/core/src/actions/connect.ts new file mode 100644 index 0000000000..f69809e504 --- /dev/null +++ b/packages/core/src/actions/connect.ts @@ -0,0 +1,110 @@ +import type { + Address, + ResourceUnavailableRpcErrorType, + UserRejectedRequestErrorType, +} from 'viem' + +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import type { Config, Connector } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import { + ConnectorAlreadyConnectedError, + type ConnectorAlreadyConnectedErrorType, +} from '../errors/config.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' + +export type ConnectParameters< + config extends Config = Config, + connector extends Connector | CreateConnectorFn = + | Connector + | CreateConnectorFn, + /// + parameters extends unknown | undefined = + | (connector extends CreateConnectorFn + ? Omit< + NonNullable['connect']>[0]>, + 'isReconnecting' + > + : never) + | (connector extends Connector + ? Omit< + NonNullable[0]>, + 'isReconnecting' + > + : never), +> = Compute< + ChainIdParameter & { + connector: connector | CreateConnectorFn + } +> & + parameters + +export type ConnectReturnType = { + accounts: readonly [Address, ...Address[]] + chainId: + | config['chains'][number]['id'] + | (number extends config['chains'][number]['id'] ? number : number & {}) +} + +export type ConnectErrorType = + | ConnectorAlreadyConnectedErrorType + // connector.connect() + | UserRejectedRequestErrorType + | ResourceUnavailableRpcErrorType + // base + | BaseErrorType + | ErrorType + +/** https://wagmi.sh/core/api/actions/connect */ +export async function connect< + config extends Config, + connector extends Connector | CreateConnectorFn, +>( + config: config, + parameters: ConnectParameters, +): Promise> { + // "Register" connector if not already created + let connector: Connector + if (typeof parameters.connector === 'function') { + connector = config._internal.connectors.setup(parameters.connector) + } else connector = parameters.connector + + // Check if connector is already connected + if (connector.uid === config.state.current) + throw new ConnectorAlreadyConnectedError() + + try { + config.setState((x) => ({ ...x, status: 'connecting' })) + connector.emitter.emit('message', { type: 'connecting' }) + + const { connector: _, ...rest } = parameters + const data = await connector.connect(rest) + const accounts = data.accounts as readonly [Address, ...Address[]] + + connector.emitter.off('connect', config._internal.events.connect) + connector.emitter.on('change', config._internal.events.change) + connector.emitter.on('disconnect', config._internal.events.disconnect) + + await config.storage?.setItem('recentConnectorId', connector.id) + config.setState((x) => ({ + ...x, + connections: new Map(x.connections).set(connector.uid, { + accounts, + chainId: data.chainId, + connector: connector, + }), + current: connector.uid, + status: 'connected', + })) + + return { accounts, chainId: data.chainId } + } catch (error) { + config.setState((x) => ({ + ...x, + // Keep existing connector connected in case of error + status: x.current ? 'connected' : 'disconnected', + })) + throw error + } +} diff --git a/packages/core/src/actions/deployContract.test-d.ts b/packages/core/src/actions/deployContract.test-d.ts new file mode 100644 index 0000000000..b7a6a897d3 --- /dev/null +++ b/packages/core/src/actions/deployContract.test-d.ts @@ -0,0 +1,71 @@ +import { abi, bytecode, config } from '@wagmi/test' +import { http } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type DeployContractParameters, + deployContract, +} from './deployContract.js' + +test('default', async () => { + await deployContract(config, { + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + chainId: mainnet.id, + }) +}) + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = DeployContractParameters + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + deployContract(config, { + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + feeCurrency: '0x', + }) + + type Result2 = DeployContractParameters< + typeof abi.bayc, + typeof config, + typeof celo.id + > + expectTypeOf().toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + deployContract(config, { + chainId: celo.id, + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + feeCurrency: '0x', + }) + + type Result3 = DeployContractParameters< + typeof abi.bayc, + typeof config, + typeof mainnet.id + > + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + deployContract(config, { + chainId: mainnet.id, + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/actions/deployContract.test.ts b/packages/core/src/actions/deployContract.test.ts new file mode 100644 index 0000000000..bebf42342e --- /dev/null +++ b/packages/core/src/actions/deployContract.test.ts @@ -0,0 +1,67 @@ +import { + abi, + bytecode, + config, + testClient, + transactionHashRegex, +} from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { deployContract } from './deployContract.js' +import { disconnect } from './disconnect.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + deployContract(config, { + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + }), + ).resolves.toMatch(transactionHashRegex) + await disconnect(config, { connector }) +}) + +test('behavior: no funds', async () => { + const data = await connect(config, { connector }) + const connectedAddress = data.accounts[0] + + await testClient.mainnet.setBalance({ + address: connectedAddress, + value: parseEther('0'), + }) + + await expect( + deployContract(config, { + chainId: testClient.mainnet.chain.id, + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [TransactionExecutionError: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account. + + This error could arise when the account does not have enough funds to: + - pay for the total gas fee, + - pay for the value to send. + + The cost of the transaction is calculated as \`gas * gas fee + value\`, where: + - \`gas\` is the amount of gas needed for transaction to execute, + - \`gas fee\` is the gas fee, + - \`value\` is the amount of ether to send to the recipient. + + Request Arguments: + chain: undefined (id: 1) + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + data: 0x608060405260405180602001604052806000815250600b90805190602001906200002b92919062000484565b506000600f60006101000a81548160ff0219169083151502179055503480156200005457600080fd5b50604051620046d0380380620046d0833981810160405260808110156200007a57600080fd5b81019080805160405193929190846401000000008211156200009b57600080fd5b83820191506020820185811115620000b257600080fd5b8251866001820283011164010000000082111715620000d057600080fd5b8083526020830192505050908051906020019080838360005b8381101562000106578082015181840152602081019050620000e9565b50505050905090810190601f168015620001345780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200015857600080fd5b838201915060208201858111156200016f57600080fd5b82518660018202830111640100000000821117156200018d57600080fd5b8083526020830192505050908051906020019080838360005b83811015620001c3578082015181840152602081019050620001a6565b50505050905090810190601f168015620001f15780820380516001836020036101000a031916815260200191505b5060405260200180519060200190929190805190602001909291905050508383620002296301ffc9a760e01b6200037360201b60201c565b81600690805190602001906200024192919062000484565b5080600790805190602001906200025a92919062000484565b50620002736380ac58cd60e01b6200037360201b60201c565b6200028b635b5e139f60e01b6200037360201b60201c565b620002a363780e9d6360e01b6200037360201b60201c565b50506000620002b76200047c60201b60201c565b905080600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35081600e81905550620bdd808101601081905550505050506200052a565b63ffffffff60e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916141562000410576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4552433136353a20696e76616c696420696e746572666163652069640000000081525060200191505060405180910390fd5b6001600080837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060006101000a81548160ff02191690831515021790555050565b600033905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620004c757805160ff1916838001178555620004f8565b82800160010185558215620004f8579182015b82811115620004f7578251825591602001919060010190620004da565b5b5090506200050791906200050b565b5090565b5b80821115620005265760008160009055506001016200050c565b5090565b614196806200053a6000396000f3fe60806040526004361061021a5760003560e01c80636c0360eb11610123578063b0f67427116100ab578063e36d64981161006f578063e36d649814610ddf578063e985e9c514610e0a578063e986655014610e91578063eb8d244414610ea8578063f2fde38b14610ed55761021a565b8063b0f6742714610bac578063b88d4fde14610bc3578063bb8a16bd14610cd5578063c87b56dd14610d00578063cb774d4714610db45761021a565b80637d17fcbe116100f25780637d17fcbe14610a395780638da5cb5b14610a5057806395d89b4114610a91578063a22cb46514610b21578063a723533e14610b7e5761021a565b80636c0360eb1461090257806370a0823114610992578063715018a6146109f75780637a3f451e14610a0e5761021a565b80632f745c59116101a65780634f6ccce7116101755780634f6ccce7146106cb57806355f804b31461071a578063571dff3b146107e2578063607e20e31461080d5780636352211e1461089d5761021a565b80632f745c59146105b357806334918dfd146106225780633ccfd60b1461063957806342842e0e146106505761021a565b8063095ea7b3116101ed578063095ea7b3146103bf578063109695231461041a57806318160ddd146104e257806318e20a381461050d57806323b872dd146105385761021a565b8063018a2c371461021f57806301ffc9a71461025a57806306fdde03146102ca578063081812fc1461035a575b600080fd5b34801561022b57600080fd5b506102586004803603602081101561024257600080fd5b8101908080359060200190929190505050610f26565b005b34801561026657600080fd5b506102b26004803603602081101561027d57600080fd5b8101908080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610fdf565b60405180821515815260200191505060405180910390f35b3480156102d657600080fd5b506102df611046565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561031f578082015181840152602081019050610304565b50505050905090810190601f16801561034c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561036657600080fd5b506103936004803603602081101561037d57600080fd5b81019080803590602001909291905050506110e8565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156103cb57600080fd5b50610418600480360360408110156103e257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611183565b005b34801561042657600080fd5b506104e06004803603602081101561043d57600080fd5b810190808035906020019064010000000081111561045a57600080fd5b82018360208201111561046c57600080fd5b8035906020019184600183028401116401000000008311171561048e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506112c7565b005b3480156104ee57600080fd5b506104f7611390565b6040518082815260200191505060405180910390f35b34801561051957600080fd5b506105226113a1565b6040518082815260200191505060405180910390f35b34801561054457600080fd5b506105b16004803603606081101561055b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506113a7565b005b3480156105bf57600080fd5b5061060c600480360360408110156105d657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061141d565b6040518082815260200191505060405180910390f35b34801561062e57600080fd5b50610637611478565b005b34801561064557600080fd5b5061064e611553565b005b34801561065c57600080fd5b506106c96004803603606081101561067357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611651565b005b3480156106d757600080fd5b50610704600480360360208110156106ee57600080fd5b8101908080359060200190929190505050611671565b6040518082815260200191505060405180910390f35b34801561072657600080fd5b506107e06004803603602081101561073d57600080fd5b810190808035906020019064010000000081111561075a57600080fd5b82018360208201111561076c57600080fd5b8035906020019184600183028401116401000000008311171561078e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611694565b005b3480156107ee57600080fd5b506107f761174f565b6040518082815260200191505060405180910390f35b34801561081957600080fd5b50610822611754565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610862578082015181840152602081019050610847565b50505050905090810190601f16801561088f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108a957600080fd5b506108d6600480360360208110156108c057600080fd5b81019080803590602001909291905050506117f2565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561090e57600080fd5b50610917611829565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561095757808201518184015260208101905061093c565b50505050905090810190601f1680156109845780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561099e57600080fd5b506109e1600480360360208110156109b557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506118cb565b6040518082815260200191505060405180910390f35b348015610a0357600080fd5b50610a0c6119a0565b005b348015610a1a57600080fd5b50610a23611b10565b6040518082815260200191505060405180910390f35b348015610a4557600080fd5b50610a4e611b1c565b005b348015610a5c57600080fd5b50610a65611c4c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b348015610a9d57600080fd5b50610aa6611c76565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610ae6578082015181840152602081019050610acb565b50505050905090810190601f168015610b135780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610b2d57600080fd5b50610b7c60048036036040811015610b4457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803515159060200190929190505050611d18565b005b610baa60048036036020811015610b9457600080fd5b8101908080359060200190929190505050611ece565b005b348015610bb857600080fd5b50610bc1612127565b005b348015610bcf57600080fd5b50610cd360048036036080811015610be657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610c4d57600080fd5b820183602082011115610c5f57600080fd5b80359060200191846001830284011164010000000083111715610c8157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061220b565b005b348015610ce157600080fd5b50610cea612283565b6040518082815260200191505060405180910390f35b348015610d0c57600080fd5b50610d3960048036036020811015610d2357600080fd5b8101908080359060200190929190505050612289565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610d79578082015181840152602081019050610d5e565b50505050905090810190601f168015610da65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610dc057600080fd5b50610dc961255a565b6040518082815260200191505060405180910390f35b348015610deb57600080fd5b50610df4612560565b6040518082815260200191505060405180910390f35b348015610e1657600080fd5b50610e7960048036036040811015610e2d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612566565b60405180821515815260200191505060405180910390f35b348015610e9d57600080fd5b50610ea66125fa565b005b348015610eb457600080fd5b50610ebd612764565b60405180821515815260200191505060405180910390f35b348015610ee157600080fd5b50610f2460048036036020811015610ef857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612777565b005b610f2e61296c565b73ffffffffffffffffffffffffffffffffffffffff16610f4c611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614610fd5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b8060108190555050565b6000806000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060009054906101000a900460ff169050919050565b606060068054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156110de5780601f106110b3576101008083540402835291602001916110de565b820191906000526020600020905b8154815290600101906020018083116110c157829003601f168201915b5050505050905090565b60006110f382612974565b611148576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061408b602c913960400191505060405180910390fd5b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b600061118e826117f2565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611215576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061410f6021913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1661123461296c565b73ffffffffffffffffffffffffffffffffffffffff16148061126357506112628161125d61296c565b612566565b5b6112b8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526038815260200180613f956038913960400191505060405180910390fd5b6112c28383612991565b505050565b6112cf61296c565b73ffffffffffffffffffffffffffffffffffffffff166112ed611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611376576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b80600b908051906020019061138c929190613de6565b5050565b600061139c6002612a4a565b905090565b60105481565b6113b86113b261296c565b82612a5f565b61140d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260318152602001806141306031913960400191505060405180910390fd5b611418838383612b53565b505050565b600061147082600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d9690919063ffffffff16565b905092915050565b61148061296c565b73ffffffffffffffffffffffffffffffffffffffff1661149e611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611527576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600f60009054906101000a900460ff1615600f60006101000a81548160ff021916908315150217905550565b61155b61296c565b73ffffffffffffffffffffffffffffffffffffffff16611579611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611602576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b60004790503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801561164d573d6000803e3d6000fd5b5050565b61166c8383836040518060200160405280600081525061220b565b505050565b600080611688836002612db090919063ffffffff16565b50905080915050919050565b61169c61296c565b73ffffffffffffffffffffffffffffffffffffffff166116ba611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611743576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b61174c81612ddc565b50565b601481565b600b8054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156117ea5780601f106117bf576101008083540402835291602001916117ea565b820191906000526020600020905b8154815290600101906020018083116117cd57829003601f168201915b505050505081565b600061182282604051806060016040528060298152602001613ff7602991396002612df69092919063ffffffff16565b9050919050565b606060098054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156118c15780601f10611896576101008083540402835291602001916118c1565b820191906000526020600020905b8154815290600101906020018083116118a457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611952576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180613fcd602a913960400191505060405180910390fd5b611999600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612e15565b9050919050565b6119a861296c565b73ffffffffffffffffffffffffffffffffffffffff166119c6611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611a4f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a36000600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b67011c37937e08000081565b611b2461296c565b73ffffffffffffffffffffffffffffffffffffffff16611b42611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611bcb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b6000600d5414611c43576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f5374617274696e6720696e64657820697320616c72656164792073657400000081525060200191505060405180910390fd5b43600c81905550565b6000600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060078054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611d0e5780601f10611ce357610100808354040283529160200191611d0e565b820191906000526020600020905b815481529060010190602001808311611cf157829003601f168201915b5050505050905090565b611d2061296c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611dc1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f4552433732313a20617070726f766520746f2063616c6c65720000000000000081525060200191505060405180910390fd5b8060056000611dce61296c565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff16611e7b61296c565b73ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b600f60009054906101000a900460ff16611f50576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f53616c65206d7573742062652061637469766520746f206d696e74204170650081525060200191505060405180910390fd5b6014811115611faa576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180613f746021913960400191505060405180910390fd5b600e54611fc782611fb9611390565b612e2a90919063ffffffff16565b111561201e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806140426028913960400191505060405180910390fd5b3461203a8267011c37937e080000612eb290919063ffffffff16565b11156120ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45746865722076616c75652073656e74206973206e6f7420636f72726563740081525060200191505060405180910390fd5b60005b818110156120ef5760006120c3611390565b9050600e546120d0611390565b10156120e1576120e03382612f38565b5b5080806001019150506120b1565b506000600c541480156121175750600e54612108611390565b148061211657506010544210155b5b156121245743600c819055505b50565b61212f61296c565b73ffffffffffffffffffffffffffffffffffffffff1661214d611c4c565b73ffffffffffffffffffffffffffffffffffffffff16146121d6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b60006121e0611390565b905060005b601e811015612207576121fa33828401612f38565b80806001019150506121e5565b5050565b61221c61221661296c565b83612a5f565b612271576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260318152602001806141306031913960400191505060405180910390fd5b61227d84848484612f56565b50505050565b600e5481565b606061229482612974565b6122e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806140e0602f913960400191505060405180910390fd5b6060600860008481526020019081526020016000208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156123925780601f1061236757610100808354040283529160200191612392565b820191906000526020600020905b81548152906001019060200180831161237557829003601f168201915b5050505050905060606123a3611829565b90506000815114156123b9578192505050612555565b60008251111561248a5780826040516020018083805190602001908083835b602083106123fb57805182526020820191506020810190506020830392506123d8565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b6020831061244c5780518252602082019150602081019050602083039250612429565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050612555565b8061249485612fc8565b6040516020018083805190602001908083835b602083106124ca57805182526020820191506020810190506020830392506124a7565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b6020831061251b57805182526020820191506020810190506020830392506124f8565b6001836020036101000a03801982511681845116808217855250505050505090500192505050604051602081830303815290604052925050505b919050565b600d5481565b600c5481565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b6000600d5414612672576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f5374617274696e6720696e64657820697320616c72656164792073657400000081525060200191505060405180910390fd5b6000600c5414156126eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f5374617274696e6720696e64657820626c6f636b206d7573742062652073657481525060200191505060405180910390fd5b600e54600c544060001c816126fc57fe5b06600d8190555060ff61271a600c544361310f90919063ffffffff16565b111561273a57600e54600143034060001c8161273257fe5b06600d819055505b6000600d5414156127625761275b6001600d54612e2a90919063ffffffff16565b600d819055505b565b600f60009054906101000a900460ff1681565b61277f61296c565b73ffffffffffffffffffffffffffffffffffffffff1661279d611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614612826576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156128ac576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180613ed86026913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a380600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600033905090565b600061298a82600261319290919063ffffffff16565b9050919050565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16612a04836117f2565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000612a58826000016131ac565b9050919050565b6000612a6a82612974565b612abf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180613f48602c913960400191505060405180910390fd5b6000612aca836117f2565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480612b3957508373ffffffffffffffffffffffffffffffffffffffff16612b21846110e8565b73ffffffffffffffffffffffffffffffffffffffff16145b80612b4a5750612b498185612566565b5b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16612b73826117f2565b73ffffffffffffffffffffffffffffffffffffffff1614612bdf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806140b76029913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415612c65576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180613efe6024913960400191505060405180910390fd5b612c708383836131bd565b612c7b600082612991565b612ccc81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131c290919063ffffffff16565b50612d1e81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131dc90919063ffffffff16565b50612d35818360026131f69092919063ffffffff16565b50808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050565b6000612da5836000018361322b565b60001c905092915050565b600080600080612dc386600001866132ae565b915091508160001c8160001c9350935050509250929050565b8060099080519060200190612df2929190613de6565b5050565b6000612e09846000018460001b84613347565b60001c90509392505050565b6000612e238260000161343d565b9050919050565b600080828401905083811015612ea8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b600080831415612ec55760009050612f32565b6000828402905082848281612ed657fe5b0414612f2d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061406a6021913960400191505060405180910390fd5b809150505b92915050565b612f5282826040518060200160405280600081525061344e565b5050565b612f61848484612b53565b612f6d848484846134bf565b612fc2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180613ea66032913960400191505060405180910390fd5b50505050565b60606000821415613010576040518060400160405280600181526020017f3000000000000000000000000000000000000000000000000000000000000000815250905061310a565b600082905060005b6000821461303a578080600101915050600a828161303257fe5b049150613018565b60608167ffffffffffffffff8111801561305357600080fd5b506040519080825280601f01601f1916602001820160405280156130865781602001600182028036833780820191505090505b50905060006001830390508593505b6000841461310257600a84816130a757fe5b0660300160f81b828280600190039350815181106130c157fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a84816130fa57fe5b049350613095565b819450505050505b919050565b600082821115613187576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525060200191505060405180910390fd5b818303905092915050565b60006131a4836000018360001b6136d8565b905092915050565b600081600001805490509050919050565b505050565b60006131d4836000018360001b6136fb565b905092915050565b60006131ee836000018360001b6137e3565b905092915050565b6000613222846000018460001b8473ffffffffffffffffffffffffffffffffffffffff1660001b613853565b90509392505050565b60008183600001805490501161328c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180613e846022913960400191505060405180910390fd5b82600001828154811061329b57fe5b9060005260206000200154905092915050565b60008082846000018054905011613310576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806140206022913960400191505060405180910390fd5b600084600001848154811061332157fe5b906000526020600020906002020190508060000154816001015492509250509250929050565b6000808460010160008581526020019081526020016000205490506000811415839061340e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156133d35780820151818401526020810190506133b8565b50505050905090810190601f1680156134005780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5084600001600182038154811061342157fe5b9060005260206000209060020201600101549150509392505050565b600081600001805490509050919050565b613458838361392f565b61346560008484846134bf565b6134ba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180613ea66032913960400191505060405180910390fd5b505050565b60006134e08473ffffffffffffffffffffffffffffffffffffffff16613b23565b6134ed57600190506136d0565b606061365763150b7a0260e01b61350261296c565b888787604051602401808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561358657808201518184015260208101905061356b565b50505050905090810190601f1680156135b35780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051806060016040528060328152602001613ea6603291398773ffffffffffffffffffffffffffffffffffffffff16613b369092919063ffffffff16565b9050600081806020019051602081101561367057600080fd5b8101908080519060200190929190505050905063150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614925050505b949350505050565b600080836001016000848152602001908152602001600020541415905092915050565b600080836001016000848152602001908152602001600020549050600081146137d7576000600182039050600060018660000180549050039050600086600001828154811061374657fe5b906000526020600020015490508087600001848154811061376357fe5b906000526020600020018190555060018301876001016000838152602001908152602001600020819055508660000180548061379b57fe5b600190038181906000526020600020016000905590558660010160008781526020019081526020016000206000905560019450505050506137dd565b60009150505b92915050565b60006137ef8383613b4e565b61384857826000018290806001815401808255809150506001900390600052602060002001600090919091909150558260000180549050836001016000848152602001908152602001600020819055506001905061384d565b600090505b92915050565b60008084600101600085815260200190815260200160002054905060008114156138fa57846000016040518060400160405280868152602001858152509080600181540180825580915050600190039060005260206000209060020201600090919091909150600082015181600001556020820151816001015550508460000180549050856001016000868152602001908152602001600020819055506001915050613928565b8285600001600183038154811061390d57fe5b90600052602060002090600202016001018190555060009150505b9392505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156139d2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4552433732313a206d696e7420746f20746865207a65726f206164647265737381525060200191505060405180910390fd5b6139db81612974565b15613a4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000081525060200191505060405180910390fd5b613a5a600083836131bd565b613aab81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131dc90919063ffffffff16565b50613ac2818360026131f69092919063ffffffff16565b50808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a45050565b600080823b905060008111915050919050565b6060613b458484600085613b71565b90509392505050565b600080836001016000848152602001908152602001600020541415905092915050565b606082471015613bcc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180613f226026913960400191505060405180910390fd5b613bd585613b23565b613c47576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000081525060200191505060405180910390fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b60208310613c975780518252602082019150602081019050602083039250613c74565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114613cf9576040519150601f19603f3d011682016040523d82523d6000602084013e613cfe565b606091505b5091509150613d0e828286613d1a565b92505050949350505050565b60608315613d2a57829050613ddf565b600083511115613d3d5782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613da4578082015181840152602081019050613d89565b50505050905090810190601f168015613dd15780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613e2757805160ff1916838001178555613e55565b82800160010185558215613e55579182015b82811115613e54578251825591602001919060010190613e39565b5b509050613e629190613e66565b5090565b5b80821115613e7f576000816000905550600101613e67565b509056fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e64734552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573734552433732313a207472616e7366657220746f20746865207a65726f2061646472657373416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c4552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e43616e206f6e6c79206d696e7420323020746f6b656e7320617420612074696d654552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e456e756d657261626c654d61703a20696e646578206f7574206f6620626f756e6473507572636861736520776f756c6420657863656564206d617820737570706c79206f662041706573536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564a2646970667358221220b0e64d1fa6c4dbeb9c6f54607d7e1996943fe27624a80652f57b53fda084621b64736f6c63430007000033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000006080e6d70000000000000000000000000000000000000000000000000000000000000011426f7265644170655961636874436c756200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044241594300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000010f2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014426f72656420417065205761676d6920436c756200000000000000000000000000000000000000000000000000000000000000000000000000000000000000044241594300000000000000000000000000000000000000000000000000000000 + + Details: Insufficient funds for gas * price + value + Version: viem@2.29.2] + `) + + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/deployContract.ts b/packages/core/src/actions/deployContract.ts new file mode 100644 index 0000000000..a688dd7cfd --- /dev/null +++ b/packages/core/src/actions/deployContract.ts @@ -0,0 +1,87 @@ +import type { Abi, Account, Chain, Client, ContractConstructorArgs } from 'viem' +import { + type DeployContractErrorType as viem_DeployContractErrorType, + type DeployContractParameters as viem_DeployContractParameters, + type DeployContractReturnType as viem_DeployContractReturnType, + deployContract as viem_deployContract, +} from 'viem/actions' +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type DeployContractParameters< + abi extends Abi | readonly unknown[] = Abi, + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + allArgs = ContractConstructorArgs, + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Compute< + Omit< + viem_DeployContractParameters< + abi, + chains[key], + Account, + chains[key], + allArgs + >, + 'chain' + > & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type DeployContractReturnType = viem_DeployContractReturnType + +export type DeployContractErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_DeployContractErrorType + +/** https://wagmi.sh/core/api/actions/deployContract */ +export async function deployContract< + config extends Config, + const abi extends Abi | readonly unknown[], + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: DeployContractParameters, +): Promise { + const { account, chainId, connector, ...rest } = parameters + + let client: Client + if (typeof account === 'object' && account?.type === 'local') + client = config.getClient({ chainId }) + else + client = await getConnectorClient(config, { + account: account ?? undefined, + chainId, + connector, + }) + + const action = getAction(client, viem_deployContract, 'deployContract') + const hash = await action({ + ...(rest as any), + ...(account ? { account } : {}), + chain: chainId ? { id: chainId } : null, + }) + + return hash +} diff --git a/packages/core/src/actions/disconnect.test.ts b/packages/core/src/actions/disconnect.test.ts new file mode 100644 index 0000000000..03d63db7de --- /dev/null +++ b/packages/core/src/actions/disconnect.test.ts @@ -0,0 +1,33 @@ +import { accounts, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' + +const connector = config._internal.connectors.setup(mock({ accounts })) + +test('default', async () => { + await connect(config, { connector }) + expect(config.state.status).toEqual('connected') + await disconnect(config) + expect(config.state.status).toEqual('disconnected') +}) + +test('parameters: connector', async () => { + await connect(config, { connector }) + expect(config.state.status).toEqual('connected') + await disconnect(config, { connector }) + expect(config.state.status).toEqual('disconnected') +}) + +test('behavior: uses next connector on disconnect', async () => { + const connector_ = config._internal.connectors.setup(mock({ accounts })) + await connect(config, { connector: connector_ }) + await connect(config, { connector }) + + expect(config.state.status).toEqual('connected') + await disconnect(config, { connector }) + expect(config.state.status).toEqual('connected') + await disconnect(config, { connector: connector_ }) +}) diff --git a/packages/core/src/actions/disconnect.ts b/packages/core/src/actions/disconnect.ts new file mode 100644 index 0000000000..6efb4c790c --- /dev/null +++ b/packages/core/src/actions/disconnect.ts @@ -0,0 +1,71 @@ +import type { Config, Connection, Connector } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { + ConnectorNotConnectedErrorType, + ConnectorNotFoundErrorType, +} from '../errors/config.js' +import type { ConnectorParameter } from '../types/properties.js' + +export type DisconnectParameters = ConnectorParameter + +export type DisconnectReturnType = void + +export type DisconnectErrorType = + | ConnectorNotFoundErrorType + | ConnectorNotConnectedErrorType + // base + | BaseErrorType + | ErrorType + +/** https://wagmi.sh/core/api/actions/disconnect */ +export async function disconnect( + config: Config, + parameters: DisconnectParameters = {}, +): Promise { + let connector: Connector | undefined + if (parameters.connector) connector = parameters.connector + else { + const { connections, current } = config.state + const connection = connections.get(current!) + connector = connection?.connector + } + + const connections = config.state.connections + + if (connector) { + await connector.disconnect() + connector.emitter.off('change', config._internal.events.change) + connector.emitter.off('disconnect', config._internal.events.disconnect) + connector.emitter.on('connect', config._internal.events.connect) + + connections.delete(connector.uid) + } + + config.setState((x) => { + // if no connections exist, move to disconnected state + if (connections.size === 0) + return { + ...x, + connections: new Map(), + current: null, + status: 'disconnected', + } + + // switch over to another connection + const nextConnection = connections.values().next().value as Connection + return { + ...x, + connections: new Map(connections), + current: nextConnection.connector.uid, + } + }) + + // Set recent connector if exists + { + const current = config.state.current + if (!current) return + const connector = config.state.connections.get(current)?.connector + if (!connector) return + await config.storage?.setItem('recentConnectorId', connector.id) + } +} diff --git a/packages/core/src/actions/estimateFeesPerGas.test-d.ts b/packages/core/src/actions/estimateFeesPerGas.test-d.ts new file mode 100644 index 0000000000..dada2ea311 --- /dev/null +++ b/packages/core/src/actions/estimateFeesPerGas.test-d.ts @@ -0,0 +1,41 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { estimateFeesPerGas } from './estimateFeesPerGas.js' + +test('types', async () => { + const default_ = await estimateFeesPerGas(config) + expectTypeOf(default_).toMatchTypeOf<{ + gasPrice?: undefined + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + formatted: { + gasPrice?: undefined + maxFeePerGas: string + maxPriorityFeePerGas: string + } + }>() + + const legacy = await estimateFeesPerGas(config, { type: 'legacy' }) + expectTypeOf(legacy).toMatchTypeOf<{ + gasPrice: bigint + maxFeePerGas?: undefined + maxPriorityFeePerGas?: undefined + formatted: { + gasPrice: string + maxFeePerGas?: undefined + maxPriorityFeePerGas?: undefined + } + }>() + + const eip1559 = await estimateFeesPerGas(config, { type: 'eip1559' }) + expectTypeOf(eip1559).toMatchTypeOf<{ + gasPrice?: undefined + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + formatted: { + gasPrice?: undefined + maxFeePerGas: string + maxPriorityFeePerGas: string + } + }>() +}) diff --git a/packages/core/src/actions/estimateFeesPerGas.test.ts b/packages/core/src/actions/estimateFeesPerGas.test.ts new file mode 100644 index 0000000000..4c5d668b83 --- /dev/null +++ b/packages/core/src/actions/estimateFeesPerGas.test.ts @@ -0,0 +1,16 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { estimateFeesPerGas } from './estimateFeesPerGas.js' + +test('default', async () => { + const result = await estimateFeesPerGas(config) + expect(Object.keys(result)).toMatchInlineSnapshot(` + [ + "formatted", + "gasPrice", + "maxFeePerGas", + "maxPriorityFeePerGas", + ] + `) +}) diff --git a/packages/core/src/actions/estimateFeesPerGas.ts b/packages/core/src/actions/estimateFeesPerGas.ts new file mode 100644 index 0000000000..66915f010a --- /dev/null +++ b/packages/core/src/actions/estimateFeesPerGas.ts @@ -0,0 +1,87 @@ +import { + type Chain, + type FeeValuesEIP1559, + type FeeValuesLegacy, + type FeeValuesType, + formatUnits, +} from 'viem' +import { + type EstimateFeesPerGasErrorType as viem_EstimateFeesPerGasErrorType, + type EstimateFeesPerGasParameters as viem_EstimateFeesPerGasParameters, + type EstimateFeesPerGasReturnType as viem_EstimateFeesPerGasReturnType, + estimateFeesPerGas as viem_estimateFeesPerGas, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Unit } from '../types/unit.js' +import type { Compute } from '../types/utils.js' +import type { UnionCompute, UnionLooseOmit } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { getUnit } from '../utils/getUnit.js' + +export type EstimateFeesPerGasParameters< + type extends FeeValuesType = FeeValuesType, + config extends Config = Config, +> = UnionCompute< + UnionLooseOmit< + viem_EstimateFeesPerGasParameters, + 'chain' + > & + ChainIdParameter & { + /** @deprecated */ + formatUnits?: Unit | undefined + } +> + +export type EstimateFeesPerGasReturnType< + type extends FeeValuesType = FeeValuesType, +> = Compute< + viem_EstimateFeesPerGasReturnType & { + /** @deprecated */ + formatted: UnionCompute< + | (type extends 'legacy' ? FeeValuesLegacy : never) + | (type extends 'eip1559' ? FeeValuesEIP1559 : never) + > + } +> + +export type EstimateFeesPerGasErrorType = viem_EstimateFeesPerGasErrorType + +export async function estimateFeesPerGas< + config extends Config, + type extends FeeValuesType = 'eip1559', +>( + config: config, + parameters: EstimateFeesPerGasParameters = {}, +): Promise> { + const { chainId, formatUnits: units = 'gwei', ...rest } = parameters + + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_estimateFeesPerGas, + 'estimateFeesPerGas', + ) + + const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = await action({ + ...rest, + chain: client.chain, + }) + + const unit = getUnit(units) + const formatted = { + gasPrice: gasPrice ? formatUnits(gasPrice, unit) : undefined, + maxFeePerGas: maxFeePerGas ? formatUnits(maxFeePerGas, unit) : undefined, + maxPriorityFeePerGas: maxPriorityFeePerGas + ? formatUnits(maxPriorityFeePerGas, unit) + : undefined, + } + + return { + formatted, + gasPrice, + maxFeePerGas, + maxPriorityFeePerGas, + } as EstimateFeesPerGasReturnType +} diff --git a/packages/core/src/actions/estimateGas.test-d.ts b/packages/core/src/actions/estimateGas.test-d.ts new file mode 100644 index 0000000000..5fc66c0ec0 --- /dev/null +++ b/packages/core/src/actions/estimateGas.test-d.ts @@ -0,0 +1,47 @@ +import { http, parseEther } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { type EstimateGasParameters, estimateGas } from './estimateGas.js' + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = EstimateGasParameters + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result2 = EstimateGasParameters + expectTypeOf().toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + estimateGas(config, { + chainId: celo.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result3 = EstimateGasParameters + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + estimateGas(config, { + chainId: mainnet.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/actions/estimateGas.test.ts b/packages/core/src/actions/estimateGas.test.ts new file mode 100644 index 0000000000..f0154c55fa --- /dev/null +++ b/packages/core/src/actions/estimateGas.test.ts @@ -0,0 +1,47 @@ +import { accounts, config } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { estimateGas } from './estimateGas.js' + +const connector = config._internal.connectors.setup(mock({ accounts })) + +test('parameters: account', async () => { + await expect( + estimateGas(config, { + account: accounts[0], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ).resolves.toMatchInlineSnapshot('21000n') +}) + +test('parameters: connector', async () => { + await connect(config, { connector }) + + await expect( + estimateGas(config, { + connector, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ).resolves.toMatchInlineSnapshot('21000n') + + await disconnect(config, { connector }) +}) + +test('behavior: no account and not connected', async () => { + await expect( + estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/actions/estimateGas.ts b/packages/core/src/actions/estimateGas.ts new file mode 100644 index 0000000000..c049fa42f8 --- /dev/null +++ b/packages/core/src/actions/estimateGas.ts @@ -0,0 +1,73 @@ +import type { Account, Address, Chain } from 'viem' +import { + type EstimateGasErrorType as viem_EstimateGasErrorType, + type EstimateGasParameters as viem_EstimateGasParameters, + type EstimateGasReturnType as viem_EstimateGasReturnType, + estimateGas as viem_estimateGas, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { UnionCompute, UnionLooseOmit } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type EstimateGasParameters< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + UnionLooseOmit, 'chain'> & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type EstimateGasReturnType = viem_EstimateGasReturnType + +export type EstimateGasErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_EstimateGasErrorType + +/** https://wagmi.sh/core/api/actions/estimateGas */ +export async function estimateGas< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +>( + config: config, + parameters: EstimateGasParameters, +): Promise { + const { chainId, connector, ...rest } = parameters + + let account: Address | Account + if (parameters.account) account = parameters.account + else { + const connectorClient = await getConnectorClient(config, { + account: parameters.account, + chainId, + connector, + }) + account = connectorClient.account + } + + const client = config.getClient({ chainId }) + const action = getAction(client, viem_estimateGas, 'estimateGas') + return action({ ...(rest as viem_EstimateGasParameters), account }) +} diff --git a/packages/core/src/actions/estimateMaxPriorityFeePerGas.test.ts b/packages/core/src/actions/estimateMaxPriorityFeePerGas.test.ts new file mode 100644 index 0000000000..deb969ebec --- /dev/null +++ b/packages/core/src/actions/estimateMaxPriorityFeePerGas.test.ts @@ -0,0 +1,16 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { estimateMaxPriorityFeePerGas } from './estimateMaxPriorityFeePerGas.js' + +test('default', async () => { + await expect(estimateMaxPriorityFeePerGas(config)).resolves.toBeDefined() +}) + +test('parameters: chainId', async () => { + await expect( + estimateMaxPriorityFeePerGas(config, { + chainId: chain.mainnet2.id, + }), + ).resolves.toBeDefined() +}) diff --git a/packages/core/src/actions/estimateMaxPriorityFeePerGas.ts b/packages/core/src/actions/estimateMaxPriorityFeePerGas.ts new file mode 100644 index 0000000000..06378d84fd --- /dev/null +++ b/packages/core/src/actions/estimateMaxPriorityFeePerGas.ts @@ -0,0 +1,49 @@ +import type { Chain } from 'viem' +import { + type EstimateMaxPriorityFeePerGasErrorType as viem_EstimateMaxPriorityFeePerGasErrorType, + type EstimateMaxPriorityFeePerGasParameters as viem_EstimateMaxPriorityFeePerGasParameters, + type EstimateMaxPriorityFeePerGasReturnType as viem_EstimateMaxPriorityFeePerGasReturnType, + estimateMaxPriorityFeePerGas as viem_estimateMaxPriorityFeePerGas, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, UnionLooseOmit } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type EstimateMaxPriorityFeePerGasParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + UnionLooseOmit< + viem_EstimateMaxPriorityFeePerGasParameters & + ChainIdParameter, + 'chain' + > +> + +export type EstimateMaxPriorityFeePerGasReturnType = + viem_EstimateMaxPriorityFeePerGasReturnType + +export type EstimateMaxPriorityFeePerGasErrorType = + viem_EstimateMaxPriorityFeePerGasErrorType + +/** https://wagmi.sh/core/api/actions/estimateMaxPriorityFeePerGas */ +export async function estimateMaxPriorityFeePerGas< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: EstimateMaxPriorityFeePerGasParameters = {}, +): Promise { + const { chainId } = parameters + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_estimateMaxPriorityFeePerGas, + 'estimateMaxPriorityFeePerGas', + ) + return action({ chain: client.chain }) +} diff --git a/packages/core/src/actions/getAccount.test-d.ts b/packages/core/src/actions/getAccount.test-d.ts new file mode 100644 index 0000000000..728468039b --- /dev/null +++ b/packages/core/src/actions/getAccount.test-d.ts @@ -0,0 +1,69 @@ +import { config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import type { Connector } from '../createConfig.js' +import { getAccount } from './getAccount.js' + +test('states', () => { + const result = getAccount(config) + + switch (result.status) { + case 'reconnecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: (typeof config)['chains'][number] | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: boolean + isConnecting: false + isDisconnected: false + isReconnecting: true + status: 'reconnecting' + }>() + break + } + case 'connecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: (typeof config)['chains'][number] | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: false + isReconnecting: false + isConnecting: true + isDisconnected: false + status: 'connecting' + }>() + break + } + case 'connected': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address + chain: (typeof config)['chains'][number] | undefined + chainId: number + connector: Connector + isConnected: true + isConnecting: false + isDisconnected: false + isReconnecting: false + status: 'connected' + }>() + break + } + case 'disconnected': { + expectTypeOf(result).toMatchTypeOf<{ + address: undefined + chain: undefined + chainId: undefined + connector: undefined + isConnected: false + isReconnecting: false + isConnecting: false + isDisconnected: true + status: 'disconnected' + }>() + break + } + } +}) diff --git a/packages/core/src/actions/getAccount.test.ts b/packages/core/src/actions/getAccount.test.ts new file mode 100644 index 0000000000..a538357875 --- /dev/null +++ b/packages/core/src/actions/getAccount.test.ts @@ -0,0 +1,37 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getAccount } from './getAccount.js' + +test('default', () => { + expect(getAccount(config)).toMatchInlineSnapshot(` + { + "address": undefined, + "addresses": undefined, + "chain": undefined, + "chainId": undefined, + "connector": undefined, + "isConnected": false, + "isConnecting": false, + "isDisconnected": true, + "isReconnecting": false, + "status": "disconnected", + } + `) +}) + +test('behavior: connected', async () => { + let result = getAccount(config) + expect(result.status).toEqual('disconnected') + + await connect(config, { connector: config.connectors[0]! }) + result = getAccount(config) + expect(result.address).toBeDefined() + expect(result.status).toEqual('connected') + + await disconnect(config) + result = getAccount(config) + expect(result.status).toEqual('disconnected') +}) diff --git a/packages/core/src/actions/getAccount.ts b/packages/core/src/actions/getAccount.ts new file mode 100644 index 0000000000..af5daea028 --- /dev/null +++ b/packages/core/src/actions/getAccount.ts @@ -0,0 +1,126 @@ +import type { Address, Chain } from 'viem' + +import type { Config, Connector } from '../createConfig.js' + +export type GetAccountReturnType< + config extends Config = Config, + /// + chain = Config extends config ? Chain : config['chains'][number], +> = + | { + address: Address + addresses: readonly [Address, ...Address[]] + chain: chain | undefined + chainId: number + connector: Connector + isConnected: true + isConnecting: false + isDisconnected: false + isReconnecting: false + status: 'connected' + } + | { + address: Address | undefined + addresses: readonly Address[] | undefined + chain: chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: boolean + isConnecting: false + isDisconnected: false + isReconnecting: true + status: 'reconnecting' + } + | { + address: Address | undefined + addresses: readonly Address[] | undefined + chain: chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: false + isReconnecting: false + isConnecting: true + isDisconnected: false + status: 'connecting' + } + | { + address: undefined + addresses: undefined + chain: undefined + chainId: undefined + connector: undefined + isConnected: false + isReconnecting: false + isConnecting: false + isDisconnected: true + status: 'disconnected' + } + +/** https://wagmi.sh/core/api/actions/getAccount */ +export function getAccount( + config: config, +): GetAccountReturnType { + const uid = config.state.current! + const connection = config.state.connections.get(uid) + const addresses = connection?.accounts + const address = addresses?.[0] + const chain = config.chains.find( + (chain) => chain.id === connection?.chainId, + ) as GetAccountReturnType['chain'] + const status = config.state.status + + switch (status) { + case 'connected': + return { + address: address!, + addresses: addresses!, + chain, + chainId: connection?.chainId!, + connector: connection?.connector!, + isConnected: true, + isConnecting: false, + isDisconnected: false, + isReconnecting: false, + status, + } + case 'reconnecting': + return { + address, + addresses, + chain, + chainId: connection?.chainId, + connector: connection?.connector, + isConnected: !!address, + isConnecting: false, + isDisconnected: false, + isReconnecting: true, + status, + } + case 'connecting': + return { + address, + addresses, + chain, + chainId: connection?.chainId, + connector: connection?.connector, + isConnected: false, + isConnecting: true, + isDisconnected: false, + isReconnecting: false, + status, + } + case 'disconnected': + return { + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status, + } + } +} diff --git a/packages/core/src/actions/getBalance.test.ts b/packages/core/src/actions/getBalance.test.ts new file mode 100644 index 0000000000..954c09c2e6 --- /dev/null +++ b/packages/core/src/actions/getBalance.test.ts @@ -0,0 +1,102 @@ +import { accounts, chain, config, testClient } from '@wagmi/test' +import { parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' + +import { getBalance } from './getBalance.js' + +const address = accounts[0] + +beforeEach(async () => { + await testClient.mainnet.setBalance({ + address, + value: parseEther('10000'), + }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet2.setBalance({ + address, + value: parseEther('420'), + }) + await testClient.mainnet2.mine({ blocks: 1 }) +}) + +test('default', async () => { + await expect(getBalance(config, { address })).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "10000", + "symbol": "ETH", + "value": 10000000000000000000000n, + } + `) + + await testClient.mainnet.setBalance({ + address, + value: parseEther('6969.12222215666'), + }) + await expect(getBalance(config, { address })).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "6969.12222215666", + "symbol": "ETH", + "value": 6969122222156660000000n, + } + `) +}) + +test('parameters: chainId', async () => { + await expect( + getBalance(config, { address, chainId: chain.mainnet2.id }), + ).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "420", + "symbol": "WAG", + "value": 420000000000000000000n, + } + `) +}) + +test('parameters: token', async () => { + await expect( + getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + }), + ).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "0.559062564299199392", + "symbol": "DAI", + "value": 559062564299199392n, + } + `) +}) + +test('parameters: token (bytes32 symbol)', async () => { + await expect( + getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + }), + ).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "0", + "symbol": "MKR", + "value": 0n, + } + `) +}) + +test('parameters: unit', async () => { + await expect( + getBalance(config, { address, unit: 'wei' }), + ).resolves.toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "10000000000000000000000", + "symbol": "ETH", + "value": 10000000000000000000000n, + } + `) +}) diff --git a/packages/core/src/actions/getBalance.ts b/packages/core/src/actions/getBalance.ts new file mode 100644 index 0000000000..1aae5667b1 --- /dev/null +++ b/packages/core/src/actions/getBalance.ts @@ -0,0 +1,149 @@ +import { type Address, type Hex, formatUnits, hexToString, trim } from 'viem' +import { + type GetBalanceErrorType as viem_GetBalanceErrorType, + type GetBalanceParameters as viem_GetBalanceParameters, + getBalance as viem_getBalance, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Unit } from '../types/unit.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { getUnit } from '../utils/getUnit.js' +import { type ReadContractsErrorType, readContracts } from './readContracts.js' + +export type GetBalanceParameters = Compute< + ChainIdParameter & + viem_GetBalanceParameters & { + /** @deprecated */ + token?: Address | undefined + /** @deprecated */ + unit?: Unit | undefined + } +> + +export type GetBalanceReturnType = { + decimals: number + /** @deprecated */ + formatted: string + symbol: string + value: bigint +} + +export type GetBalanceErrorType = viem_GetBalanceErrorType + +/** https://wagmi.sh/core/api/actions/getBalance */ +export async function getBalance( + config: config, + parameters: GetBalanceParameters, +): Promise { + const { + address, + blockNumber, + blockTag, + chainId, + token: tokenAddress, + unit = 'ether', + } = parameters + + if (tokenAddress) { + try { + return await getTokenBalance(config, { + balanceAddress: address, + chainId, + symbolType: 'string', + tokenAddress, + }) + } catch (error) { + // In the chance that there is an error upon decoding the contract result, + // it could be likely that the contract data is represented as bytes32 instead + // of a string. + if ( + (error as ReadContractsErrorType).name === + 'ContractFunctionExecutionError' + ) { + const balance = await getTokenBalance(config, { + balanceAddress: address, + chainId, + symbolType: 'bytes32', + tokenAddress, + }) + const symbol = hexToString( + trim(balance.symbol as Hex, { dir: 'right' }), + ) + return { ...balance, symbol } + } + throw error + } + } + + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getBalance, 'getBalance') + const value = await action( + blockNumber ? { address, blockNumber } : { address, blockTag }, + ) + const chain = config.chains.find((x) => x.id === chainId) ?? client.chain! + return { + decimals: chain.nativeCurrency.decimals, + formatted: formatUnits(value, getUnit(unit)), + symbol: chain.nativeCurrency.symbol, + value, + } +} + +type GetTokenBalanceParameters = { + balanceAddress: Address + chainId?: number | undefined + symbolType: 'bytes32' | 'string' + tokenAddress: Address + unit?: Unit | undefined +} + +async function getTokenBalance( + config: Config, + parameters: GetTokenBalanceParameters, +) { + const { balanceAddress, chainId, symbolType, tokenAddress, unit } = parameters + const contract = { + abi: [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'decimals', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint8' }], + }, + { + type: 'function', + name: 'symbol', + stateMutability: 'view', + inputs: [], + outputs: [{ type: symbolType }], + }, + ], + address: tokenAddress, + } as const + const [value, decimals, symbol] = await readContracts(config, { + allowFailure: false, + contracts: [ + { + ...contract, + functionName: 'balanceOf', + args: [balanceAddress], + chainId, + }, + { ...contract, functionName: 'decimals', chainId }, + { ...contract, functionName: 'symbol', chainId }, + ] as const, + }) + const formatted = formatUnits(value ?? '0', getUnit(unit ?? decimals)) + return { decimals, formatted, symbol, value } +} diff --git a/packages/core/src/actions/getBlock.test-d.ts b/packages/core/src/actions/getBlock.test-d.ts new file mode 100644 index 0000000000..2344c50267 --- /dev/null +++ b/packages/core/src/actions/getBlock.test-d.ts @@ -0,0 +1,35 @@ +import { http, type Hex } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { getBlock } from './getBlock.js' + +test('chain formatters', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + const result = await getBlock(config) + if (result.chainId === celo.id) { + expectTypeOf(result.difficulty).toEqualTypeOf() + expectTypeOf(result.gasLimit).toEqualTypeOf() + expectTypeOf(result.mixHash).toEqualTypeOf() + expectTypeOf(result.nonce).toEqualTypeOf<`0x${string}`>() + expectTypeOf(result.uncles).toEqualTypeOf() + } +}) + +test('chainId', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + const result = await getBlock(config, { + chainId: celo.id, + }) + expectTypeOf(result.difficulty).toEqualTypeOf() + expectTypeOf(result.gasLimit).toEqualTypeOf() + expectTypeOf(result.mixHash).toEqualTypeOf() + expectTypeOf(result.nonce).toEqualTypeOf<`0x${string}`>() +}) diff --git a/packages/core/src/actions/getBlock.test.ts b/packages/core/src/actions/getBlock.test.ts new file mode 100644 index 0000000000..b1e92ba364 --- /dev/null +++ b/packages/core/src/actions/getBlock.test.ts @@ -0,0 +1,153 @@ +import { config, mainnet } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlock } from './getBlock.js' + +test('default', async () => { + await expect(getBlock(config)).resolves.toBeDefined() +}) + +test('args: blockNumber', async () => { + const { transactions, ...block } = await getBlock(config, { + blockNumber: mainnet.fork.blockNumber, + }) + expect(transactions).toMatchObject( + expect.arrayContaining([expect.any(String)]), + ) + expect(block).toMatchInlineSnapshot(` + { + "baseFeePerGas": 24076814055n, + "blobGasUsed": undefined, + "chainId": 1, + "difficulty": 0n, + "excessBlobGas": undefined, + "extraData": "0x546974616e2028746974616e6275696c6465722e78797a29", + "gasLimit": 30000000n, + "gasUsed": 26325393n, + "hash": "0xcfa5df46abf1521f68ae72a7f7c4661949f4fb08a3d1296fe8082f6580a414e0", + "logsBloom": "0x2df3b5a24d2d57e7d73f96dbfea3577b1d5fbaacfcb9b5fb86db74d2e4ffd1e48bba050c33edada84fe477213937158c1e95d3da9f457f6f36e3ff0afdffcb667c5ee5f9e3ddffa9db1af6bbf15fcbbca5139717d5eedab4daa63cd8bb7dfa3e976b1e7023e2dc4586cef3caa0b73d6ff2ba3afb989c9f58f6b67bb4ed596c5aeb78cef51f69ad3675df70ffbd2aa6576d7c9e3debd00cccec3b69fc617b8568bfe588f7e126ef591f34ddd0d8b68c28b7ed45b46af3a7bb75c0e2fe4bec54fb772c87ae6f7efcdfb13139b758cfda4d98dffe426fef6d1c2e55f36b5bb1f0a2aef7bcbdf83d31ea646cf6ef3fe9d8b9af2ad4197f7ea2de462bd029fdef7e6f", + "miner": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "mixHash": "0x92ac9cd6e57bacd7c7d3e9b087c3907b1c085e284eec2dce7379a847cb4c9940", + "nonce": "0x0000000000000000", + "number": 19258213n, + "parentHash": "0x40cb7885ad596d0397d664a4dc9ef5c2011c09e9a62b386f838f5f5362582ebb", + "receiptsRoot": "0x910a69ba396ab4f59c2c77aa413e941fc4da97a021b8d8bbf12c125bfc42d9d3", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": 158252n, + "stateRoot": "0x6e27207d219b0251dbc2fde71f3cde8e33703261f032056453c27275500dddbc", + "timestamp": 1708302299n, + "totalDifficulty": null, + "transactionsRoot": "0x897dba26a3a940b62f86da6e5fec5f71312ad7c871a4031db79dee67442c9d1e", + "uncles": [], + "withdrawals": [ + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x112da72", + "index": "0x21ec946", + "validatorIndex": "0x5cd8e", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1119345", + "index": "0x21ec947", + "validatorIndex": "0x5cd8f", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x110e2ca", + "index": "0x21ec948", + "validatorIndex": "0x5cd90", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1119245", + "index": "0x21ec949", + "validatorIndex": "0x5cd91", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1115a03", + "index": "0x21ec94a", + "validatorIndex": "0x5cd92", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x111cf3f", + "index": "0x21ec94b", + "validatorIndex": "0x5cd93", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1106006", + "index": "0x21ec94c", + "validatorIndex": "0x5cd94", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1115bb3", + "index": "0x21ec94d", + "validatorIndex": "0x5cd95", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x111e0d9", + "index": "0x21ec94e", + "validatorIndex": "0x5cd96", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x110829f", + "index": "0x21ec94f", + "validatorIndex": "0x5cd97", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x11029ab", + "index": "0x21ec950", + "validatorIndex": "0x5cd98", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x11140a6", + "index": "0x21ec951", + "validatorIndex": "0x5cd99", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x111396c", + "index": "0x21ec952", + "validatorIndex": "0x5cd9a", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x110de16", + "index": "0x21ec953", + "validatorIndex": "0x5cd9b", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x1121062", + "index": "0x21ec954", + "validatorIndex": "0x5cd9c", + }, + { + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "0x11188bc", + "index": "0x21ec955", + "validatorIndex": "0x5cd9d", + }, + ], + "withdrawalsRoot": "0x26638497bd55075025ac2362d92bd789ac1232fd50c4b3866565280318027950", + } + `) +}) + +test('args: includeTransactions', async () => { + const { transactions } = await getBlock(config, { + includeTransactions: true, + blockNumber: mainnet.fork.blockNumber, + }) + expect(transactions).toMatchObject( + expect.arrayContaining([expect.any(Object)]), + ) +}) diff --git a/packages/core/src/actions/getBlock.ts b/packages/core/src/actions/getBlock.ts new file mode 100644 index 0000000000..babb52cd41 --- /dev/null +++ b/packages/core/src/actions/getBlock.ts @@ -0,0 +1,74 @@ +import type { BlockTag, Chain } from 'viem' +import { + type GetBlockErrorType as viem_GetBlockErrorType, + type GetBlockParameters as viem_GetBlockParameters, + type GetBlockReturnType as viem_GetBlockReturnType, + getBlock as viem_getBlock, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetBlockParameters< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + viem_GetBlockParameters & + ChainIdParameter +> + +export type GetBlockReturnType< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = Compute< + { + [key in keyof chains]: viem_GetBlockReturnType< + IsNarrowable extends true ? chains[key] : undefined, + includeTransactions, + blockTag + > & { chainId: chains[key]['id'] } + }[number] +> + +export type GetBlockErrorType = viem_GetBlockErrorType + +/** https://wagmi.sh/core/actions/getBlock */ +export async function getBlock< + config extends Config, + chainId extends config['chains'][number]['id'], + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', +>( + config: config, + parameters: GetBlockParameters< + includeTransactions, + blockTag, + config, + chainId + > = {}, +): Promise> { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getBlock, 'getBlock') + const block = await action(rest) + return { + ...(block as unknown as GetBlockReturnType< + includeTransactions, + blockTag, + config, + chainId + >), + chainId: client.chain.id, + } +} diff --git a/packages/core/src/actions/getBlockNumber.test.ts b/packages/core/src/actions/getBlockNumber.test.ts new file mode 100644 index 0000000000..7e18744e14 --- /dev/null +++ b/packages/core/src/actions/getBlockNumber.test.ts @@ -0,0 +1,8 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlockNumber } from './getBlockNumber.js' + +test('default', async () => { + await expect(getBlockNumber(config)).resolves.toBeDefined() +}) diff --git a/packages/core/src/actions/getBlockNumber.ts b/packages/core/src/actions/getBlockNumber.ts new file mode 100644 index 0000000000..ecad7c0f67 --- /dev/null +++ b/packages/core/src/actions/getBlockNumber.ts @@ -0,0 +1,36 @@ +import { + type GetBlockNumberErrorType as viem_GetBlockNumberErrorType, + type GetBlockNumberParameters as viem_GetBlockNumberParameters, + type GetBlockNumberReturnType as viem_GetBlockNumberReturnType, + getBlockNumber as viem_getBlockNumber, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute> + +export type GetBlockNumberReturnType = viem_GetBlockNumberReturnType + +export type GetBlockNumberErrorType = viem_GetBlockNumberErrorType + +/** https://wagmi.sh/core/api/actions/getBlockNumber */ +export function getBlockNumber< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: GetBlockNumberParameters = {}, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getBlockNumber, 'getBlockNumber') + return action(rest) +} diff --git a/packages/core/src/actions/getBlockTransactionCount.test.ts b/packages/core/src/actions/getBlockTransactionCount.test.ts new file mode 100644 index 0000000000..d8a593aa00 --- /dev/null +++ b/packages/core/src/actions/getBlockTransactionCount.test.ts @@ -0,0 +1,61 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlockTransactionCount } from './getBlockTransactionCount.js' + +test('default', async () => { + await expect(getBlockTransactionCount(config)).resolves.toBeTypeOf('number') +}) + +test('parameters: chainId', async () => { + await expect( + getBlockTransactionCount(config, { chainId: chain.mainnet2.id }), + ).resolves.toBeTypeOf('number') +}) + +test('parameters: blockNumber', async () => { + await expect( + getBlockTransactionCount(config, { blockNumber: 13677382n }), + ).resolves.toBeTypeOf('number') +}) + +test('parameters: blockHash', async () => { + await expect( + getBlockTransactionCount(config, { + blockHash: + '0x6201f37a245850d1f11e4be3ac45bc51bd9d43ee4a127192cad550f351cfa575', + }), + ).resolves.toBeTypeOf('number') +}) + +test('parameters: blockTag', async () => { + await expect( + getBlockTransactionCount(config, { + blockTag: 'earliest', + }), + ).resolves.toBeTypeOf('number') + + await expect( + getBlockTransactionCount(config, { + blockTag: 'finalized', + }), + ).resolves.toBeTypeOf('number') + + await expect( + getBlockTransactionCount(config, { + blockTag: 'latest', + }), + ).resolves.toBeTypeOf('number') + + await expect( + getBlockTransactionCount(config, { + blockTag: 'pending', + }), + ).resolves.toBeTypeOf('number') + + await expect( + getBlockTransactionCount(config, { + blockTag: 'safe', + }), + ).resolves.toBeTypeOf('number') +}) diff --git a/packages/core/src/actions/getBlockTransactionCount.ts b/packages/core/src/actions/getBlockTransactionCount.ts new file mode 100644 index 0000000000..e30aca976a --- /dev/null +++ b/packages/core/src/actions/getBlockTransactionCount.ts @@ -0,0 +1,44 @@ +import { + type GetBlockTransactionCountErrorType as viem_GetBlockTransactionCountErrorType, + type GetBlockTransactionCountParameters as viem_GetBlockTransactionCountParameters, + type GetBlockTransactionCountReturnType as viem_GetBlockTransactionCountReturnType, + getBlockTransactionCount as viem_getBlockTransactionCount, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetBlockTransactionCountParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = UnionCompute< + viem_GetBlockTransactionCountParameters & ChainIdParameter +> + +export type GetBlockTransactionCountReturnType = + viem_GetBlockTransactionCountReturnType + +export type GetBlockTransactionCountErrorType = + viem_GetBlockTransactionCountErrorType + +/** https://wagmi.sh/core/api/actions/getBlockTransactionCount */ +export function getBlockTransactionCount< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: GetBlockTransactionCountParameters = {}, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_getBlockTransactionCount, + 'getBlockTransactionCount', + ) + return action(rest) +} diff --git a/packages/core/src/actions/getBytecode.test.ts b/packages/core/src/actions/getBytecode.test.ts new file mode 100644 index 0000000000..1d5b6bdff9 --- /dev/null +++ b/packages/core/src/actions/getBytecode.test.ts @@ -0,0 +1,45 @@ +import { address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBytecode } from './getBytecode.js' + +test('default', async () => { + await expect( + getBytecode(config, { + address: '0x0000000000000000000000000000000000000000', + }), + ).resolves.toBe(undefined) + + await expect( + getBytecode(config, { + address: address.wagmiMintExample, + }), + ).resolves.toMatch(/^0x.*/) +}) + +test('parameters: blockNumber', async () => { + await expect( + getBytecode(config, { + address: address.wagmiMintExample, + blockNumber: 15564163n, + }), + ).resolves.toBe(undefined) +}) + +test('parameters: blockTag', async () => { + await expect( + getBytecode(config, { + address: address.wagmiMintExample, + blockTag: 'earliest', + }), + ).resolves.toBe(undefined) +}) + +test('parameters: chainId', async () => { + await expect( + getBytecode(config, { + address: address.wagmiMintExample, + chainId: chain.optimism.id, + }), + ).resolves.toBe(undefined) +}) diff --git a/packages/core/src/actions/getBytecode.ts b/packages/core/src/actions/getBytecode.ts new file mode 100644 index 0000000000..2ece822a86 --- /dev/null +++ b/packages/core/src/actions/getBytecode.ts @@ -0,0 +1,30 @@ +import { + type GetBytecodeErrorType as viem_GetBytecodeErrorType, + type GetBytecodeParameters as viem_GetBytecodeParameters, + type GetBytecodeReturnType as viem_GetBytecodeReturnType, + getBytecode as viem_getBytecode, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetBytecodeParameters = Compute< + viem_GetBytecodeParameters & ChainIdParameter +> + +export type GetBytecodeReturnType = viem_GetBytecodeReturnType + +export type GetBytecodeErrorType = viem_GetBytecodeErrorType + +/** https://wagmi.sh/core/api/actions/getBytecode */ +export async function getBytecode( + config: config, + parameters: GetBytecodeParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getBytecode, 'getBytecode') + return action(rest) +} diff --git a/packages/core/src/actions/getCallsStatus.test.ts b/packages/core/src/actions/getCallsStatus.test.ts new file mode 100644 index 0000000000..72978a3c7f --- /dev/null +++ b/packages/core/src/actions/getCallsStatus.test.ts @@ -0,0 +1,70 @@ +import { accounts, config, testClient } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getCallsStatus } from './getCallsStatus.js' +import { sendCalls } from './sendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const { id } = await sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await testClient.mainnet.mine({ blocks: 1 }) + const { receipts, status } = await getCallsStatus(config, { + id, + }) + + expect(status).toBe('success') + expect( + receipts?.map((x) => ({ ...x, blockHash: undefined })), + ).toMatchInlineSnapshot( + ` + [ + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0x13c53b2d4d9da424835525349cd66e553330f323d6fb19458b801ae1f7989a41", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xd8397b3e82b061c26a0c2093f1ceca0c3662a512614f7d6370349e89d0eea007", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x4d26e346593d9ea265bb164b115e89aa92df43b0b8778ac75d4ad28e2a22b101", + }, + ] + `, + ) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/getCallsStatus.ts b/packages/core/src/actions/getCallsStatus.ts new file mode 100644 index 0000000000..85f7a592c5 --- /dev/null +++ b/packages/core/src/actions/getCallsStatus.ts @@ -0,0 +1,27 @@ +import { + type GetCallsStatusErrorType as viem_GetCallsStatusErrorType, + type GetCallsStatusParameters as viem_GetCallsStatusParameters, + type GetCallsStatusReturnType as viem_GetCallsStatusReturnType, + getCallsStatus as viem_getCallsStatus, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ConnectorParameter } from '../types/properties.js' +import { getConnectorClient } from './getConnectorClient.js' + +export type GetCallsStatusParameters = viem_GetCallsStatusParameters & + ConnectorParameter + +export type GetCallsStatusReturnType = viem_GetCallsStatusReturnType + +export type GetCallsStatusErrorType = viem_GetCallsStatusErrorType + +/** https://wagmi.sh/core/api/actions/getCallsStatus */ +export async function getCallsStatus( + config: config, + parameters: GetCallsStatusParameters, +): Promise { + const { connector, id } = parameters + const client = await getConnectorClient(config, { connector }) + return viem_getCallsStatus(client, { id }) +} diff --git a/packages/core/src/actions/getCapabilities.test.ts b/packages/core/src/actions/getCapabilities.test.ts new file mode 100644 index 0000000000..e7c02ec444 --- /dev/null +++ b/packages/core/src/actions/getCapabilities.test.ts @@ -0,0 +1,64 @@ +import { accounts, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getCapabilities } from './getCapabilities.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const capabilities = await getCapabilities(config) + expect(capabilities).toMatchInlineSnapshot(` + { + "8453": { + "paymasterService": { + "supported": true, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": true, + }, + }, + } + `) + await disconnect(config, { connector }) +}) + +test('args: account', async () => { + await connect(config, { connector }) + const capabilities = await getCapabilities(config, { + account: accounts[1], + }) + expect(capabilities).toMatchInlineSnapshot(` + { + "8453": { + "paymasterService": { + "supported": false, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": false, + }, + }, + } + `) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect(getCapabilities(config)).rejects.toMatchInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/actions/getCapabilities.ts b/packages/core/src/actions/getCapabilities.ts new file mode 100644 index 0000000000..ab8ea82bfb --- /dev/null +++ b/packages/core/src/actions/getCapabilities.ts @@ -0,0 +1,39 @@ +import type { Account } from 'viem' +import { + type GetCapabilitiesErrorType as viem_GetCapabilitiesErrorType, + type GetCapabilitiesParameters as viem_GetCapabilitiesParameters, + type GetCapabilitiesReturnType as viem_GetCapabilitiesReturnType, + getCapabilities as viem_getCapabilities, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ConnectorParameter } from '../types/properties.js' +import { getConnectorClient } from './getConnectorClient.js' + +export type GetCapabilitiesParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = viem_GetCapabilitiesParameters & ConnectorParameter + +export type GetCapabilitiesReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = viem_GetCapabilitiesReturnType + +export type GetCapabilitiesErrorType = viem_GetCapabilitiesErrorType + +/** https://wagmi.sh/core/api/actions/getCapabilities */ +export async function getCapabilities< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +>( + config: config, + parameters: GetCapabilitiesParameters = {}, +): Promise> { + const { account, chainId, connector } = parameters + const client = await getConnectorClient(config, { account, connector }) + return viem_getCapabilities(client as any, { + account: account as Account, + chainId, + }) +} diff --git a/packages/core/src/actions/getChainId.test.ts b/packages/core/src/actions/getChainId.test.ts new file mode 100644 index 0000000000..3dcedcf085 --- /dev/null +++ b/packages/core/src/actions/getChainId.test.ts @@ -0,0 +1,10 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getChainId } from './getChainId.js' + +test('default', async () => { + expect(getChainId(config)).toEqual(chain.mainnet.id) + config.setState((x) => ({ ...x, chainId: chain.mainnet2.id })) + expect(getChainId(config)).toEqual(chain.mainnet2.id) +}) diff --git a/packages/core/src/actions/getChainId.ts b/packages/core/src/actions/getChainId.ts new file mode 100644 index 0000000000..208602e05b --- /dev/null +++ b/packages/core/src/actions/getChainId.ts @@ -0,0 +1,11 @@ +import type { Config } from '../createConfig.js' + +export type GetChainIdReturnType = + config['chains'][number]['id'] + +/** https://wagmi.sh/core/api/actions/getChainId */ +export function getChainId( + config: config, +): GetChainIdReturnType { + return config.state.chainId +} diff --git a/packages/core/src/actions/getChains.test-d.ts b/packages/core/src/actions/getChains.test-d.ts new file mode 100644 index 0000000000..cd5f04c9c5 --- /dev/null +++ b/packages/core/src/actions/getChains.test-d.ts @@ -0,0 +1,12 @@ +import { type chain, config } from '@wagmi/test' +import type { Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { getChains } from './getChains.js' + +test('default', async () => { + const chains = getChains(config) + expectTypeOf(chains[0]).toEqualTypeOf() + expectTypeOf(chains[2]).toEqualTypeOf() + expectTypeOf(chains[3]).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/getChains.test.ts b/packages/core/src/actions/getChains.test.ts new file mode 100644 index 0000000000..fbaeae7645 --- /dev/null +++ b/packages/core/src/actions/getChains.test.ts @@ -0,0 +1,14 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getChains } from './getChains.js' + +test('default', async () => { + expect(getChains(config)).toEqual([ + chain.mainnet, + chain.mainnet2, + chain.optimism, + ]) + config._internal.chains.setState([chain.mainnet, chain.mainnet2]) + expect(getChains(config)).toEqual([chain.mainnet, chain.mainnet2]) +}) diff --git a/packages/core/src/actions/getChains.ts b/packages/core/src/actions/getChains.ts new file mode 100644 index 0000000000..a91e6e89b4 --- /dev/null +++ b/packages/core/src/actions/getChains.ts @@ -0,0 +1,21 @@ +import type { Chain } from 'viem' +import type { Config } from '../createConfig.js' +import { deepEqual } from '../utils/deepEqual.js' + +export type GetChainsReturnType = readonly [ + ...config['chains'], + ...Chain[], +] + +let previousChains: readonly Chain[] = [] + +/** https://wagmi.sh/core/api/actions/getChains */ +export function getChains( + config: config, +): GetChainsReturnType { + const chains = config.chains + if (deepEqual(previousChains, chains)) + return previousChains as GetChainsReturnType + previousChains = chains + return chains as unknown as GetChainsReturnType +} diff --git a/packages/core/src/actions/getClient.test-d.ts b/packages/core/src/actions/getClient.test-d.ts new file mode 100644 index 0000000000..f64cbae2ce --- /dev/null +++ b/packages/core/src/actions/getClient.test-d.ts @@ -0,0 +1,27 @@ +import { chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { getClient } from './getClient.js' + +test('default', () => { + const client = getClient(config) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = getClient(config, { + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + const client = getClient(config, { + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/getClient.test.ts b/packages/core/src/actions/getClient.test.ts new file mode 100644 index 0000000000..9eb0fa574b --- /dev/null +++ b/packages/core/src/actions/getClient.test.ts @@ -0,0 +1,17 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getClient } from './getClient.js' + +test('default', () => { + expect(getClient(config)).toBeDefined() +}) + +test('behavior: unconfigured chain', () => { + expect( + getClient(config, { + // @ts-expect-error + chainId: 123456, + }), + ).toBeUndefined() +}) diff --git a/packages/core/src/actions/getClient.ts b/packages/core/src/actions/getClient.ts new file mode 100644 index 0000000000..82f1c6c171 --- /dev/null +++ b/packages/core/src/actions/getClient.ts @@ -0,0 +1,52 @@ +import type { Client } from 'viem' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' + +export type GetClientParameters< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | number + | undefined = config['chains'][number]['id'], +> = ChainIdParameter + +export type GetClientReturnType< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + resolvedChainId extends + | config['chains'][number]['id'] + | undefined = IsNarrowable< + config['chains'][number]['id'], + number + > extends true + ? IsNarrowable extends true + ? chainId + : config['chains'][number]['id'] + : config['chains'][number]['id'] | undefined, +> = resolvedChainId extends config['chains'][number]['id'] + ? Compute< + Client< + config['_internal']['transports'][resolvedChainId], + Extract + > + > + : undefined + +export function getClient< + config extends Config, + chainId extends config['chains'][number]['id'] | number | undefined, +>( + config: config, + parameters: GetClientParameters = {}, +): GetClientReturnType { + let client = undefined + try { + client = config.getClient(parameters) + } catch {} + return client as GetClientReturnType +} diff --git a/packages/core/src/actions/getConnections.test.ts b/packages/core/src/actions/getConnections.test.ts new file mode 100644 index 0000000000..22e6748cf9 --- /dev/null +++ b/packages/core/src/actions/getConnections.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getConnections } from './getConnections.js' + +test('default', async () => { + const connector = config.connectors[0]! + expect(getConnections(config)).toEqual([]) + await connect(config, { connector }) + expect(getConnections(config).length).toEqual(1) + await disconnect(config, { connector }) + expect(getConnections(config)).toEqual([]) +}) diff --git a/packages/core/src/actions/getConnections.ts b/packages/core/src/actions/getConnections.ts new file mode 100644 index 0000000000..72cdbc27d7 --- /dev/null +++ b/packages/core/src/actions/getConnections.ts @@ -0,0 +1,16 @@ +import type { Config, Connection } from '../createConfig.js' +import type { Compute } from '../types/utils.js' +import { deepEqual } from '../utils/deepEqual.js' + +export type GetConnectionsReturnType = Compute[] + +let previousConnections: Connection[] = [] + +/** https://wagmi.sh/core/api/actions/getConnections */ +export function getConnections(config: Config): GetConnectionsReturnType { + const connections = [...config.state.connections.values()] + if (config.state.status === 'reconnecting') return previousConnections + if (deepEqual(previousConnections, connections)) return previousConnections + previousConnections = connections + return connections +} diff --git a/packages/core/src/actions/getConnectorClient.test-d.ts b/packages/core/src/actions/getConnectorClient.test-d.ts new file mode 100644 index 0000000000..c4d980a7d5 --- /dev/null +++ b/packages/core/src/actions/getConnectorClient.test-d.ts @@ -0,0 +1,19 @@ +import { chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { getConnectorClient } from './getConnectorClient.js' + +test('default', async () => { + const client = await getConnectorClient(config) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', async () => { + const client = await getConnectorClient(config, { + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) diff --git a/packages/core/src/actions/getConnectorClient.test.ts b/packages/core/src/actions/getConnectorClient.test.ts new file mode 100644 index 0000000000..a9d60f5142 --- /dev/null +++ b/packages/core/src/actions/getConnectorClient.test.ts @@ -0,0 +1,106 @@ +import { address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import type { Connector } from '../createConfig.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getConnectorClient } from './getConnectorClient.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect(getConnectorClient(config)).resolves.toBeDefined() + await disconnect(config, { connector }) +}) + +test('parameters: connector', async () => { + const connector2 = config.connectors[1]! + await connect(config, { connector }) + await connect(config, { connector: connector2 }) + await expect(getConnectorClient(config, { connector })).resolves.toBeDefined() + await disconnect(config, { connector }) + await disconnect(config, { connector: connector2 }) +}) + +test.todo('custom connector client') + +test('behavior: account address is checksummed', async () => { + await connect(config, { connector }) + const account = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266' + const client = await getConnectorClient(config, { account }) + expect(client.account.address).toMatchInlineSnapshot( + '"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"', + ) + expect(client.account.address).not.toBe(account) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + getConnectorClient(config), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) + +test('behavior: connector is on different chain', async () => { + await connect(config, { chainId: 1, connector }) + config.setState((state) => { + const uid = state.current! + const connection = state.connections.get(uid)! + return { + ...state, + connections: new Map(state.connections).set(uid, { + ...connection, + chainId: 456, + }), + } + }) + await expect( + getConnectorClient(config, { account: address.usdcHolder }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorChainMismatchError: The current chain of the connector (id: 1) does not match the connection's chain (id: 456). + + Current Chain ID: 1 + Expected Chain ID: 456 + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + getConnectorClient(config, { account: address.usdcHolder }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorAccountNotFoundError: Account "0x5414d89a8bF7E99d732BC52f3e6A3Ef461c0C078" not found for connector "Mock Connector". + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) + +test('behavior: reconnecting', async () => { + config.setState((state) => ({ ...state, status: 'reconnecting' })) + const { id, name, type, uid } = connector + await expect( + getConnectorClient(config, { + connector: { + id, + name, + type, + uid, + } as unknown as Connector, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorUnavailableReconnectingError: Connector "Mock Connector" unavailable while reconnecting. + + Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. + Version: @wagmi/core@x.y.z] + `) + config.setState((state) => ({ ...state, status: 'disconnected' })) +}) diff --git a/packages/core/src/actions/getConnectorClient.ts b/packages/core/src/actions/getConnectorClient.ts new file mode 100644 index 0000000000..534ba76bc6 --- /dev/null +++ b/packages/core/src/actions/getConnectorClient.ts @@ -0,0 +1,147 @@ +import { + type Account, + type Address, + type BaseErrorType, + type Client, + createClient, + custom, +} from 'viem' +import { getAddress, parseAccount } from 'viem/utils' + +import type { Config, Connection } from '../createConfig.js' +import type { ErrorType } from '../errors/base.js' +import { + ConnectorAccountNotFoundError, + type ConnectorAccountNotFoundErrorType, + ConnectorChainMismatchError, + type ConnectorChainMismatchErrorType, + ConnectorNotConnectedError, + type ConnectorNotConnectedErrorType, + ConnectorUnavailableReconnectingError, + type ConnectorUnavailableReconnectingErrorType, +} from '../errors/config.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { Compute } from '../types/utils.js' + +export type GetConnectorClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + ChainIdParameter & + ConnectorParameter & { + /** + * Account to use for the client. + * + * - `Account | Address`: An Account MUST exist on the connector. + * - `null`: Account MAY NOT exist on the connector. This is useful for + * actions that can infer the account from the connector (e.g. sending a + * call without a connected account – the user will be prompted to select + * an account within the wallet). + */ + account?: Address | Account | null | undefined + } +> + +export type GetConnectorClientReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + Client< + config['_internal']['transports'][chainId], + Extract, + Account + > +> + +export type GetConnectorClientErrorType = + | ConnectorAccountNotFoundErrorType + | ConnectorChainMismatchErrorType + | ConnectorNotConnectedErrorType + | ConnectorUnavailableReconnectingErrorType + // base + | BaseErrorType + | ErrorType + +/** https://wagmi.sh/core/api/actions/getConnectorClient */ +export async function getConnectorClient< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: GetConnectorClientParameters = {}, +): Promise> { + // Get connection + let connection: Connection | undefined + if (parameters.connector) { + const { connector } = parameters + if ( + config.state.status === 'reconnecting' && + !connector.getAccounts && + !connector.getChainId + ) + throw new ConnectorUnavailableReconnectingError({ connector }) + + const [accounts, chainId] = await Promise.all([ + connector.getAccounts().catch((e) => { + if (parameters.account === null) return [] + throw e + }), + connector.getChainId(), + ]) + connection = { + accounts: accounts as readonly [Address, ...Address[]], + chainId, + connector, + } + } else connection = config.state.connections.get(config.state.current!) + if (!connection) throw new ConnectorNotConnectedError() + + const chainId = parameters.chainId ?? connection.chainId + + // Check connector using same chainId as connection + const connectorChainId = await connection.connector.getChainId() + if (connectorChainId !== connection.chainId) + throw new ConnectorChainMismatchError({ + connectionChainId: connection.chainId, + connectorChainId, + }) + + // If connector has custom `getClient` implementation + type Return = GetConnectorClientReturnType + const connector = connection.connector + if (connector.getClient) + return connector.getClient({ chainId }) as unknown as Return + + // Default using `custom` transport + const account = parseAccount(parameters.account ?? connection.accounts[0]!) + if (account) account.address = getAddress(account.address) // TODO: Checksum address as part of `parseAccount`? + + // If account was provided, check that it exists on the connector + if ( + parameters.account && + !connection.accounts.some( + (x) => x.toLowerCase() === account.address.toLowerCase(), + ) + ) + throw new ConnectorAccountNotFoundError({ + address: account.address, + connector, + }) + + const chain = config.chains.find((chain) => chain.id === chainId) + const provider = (await connection.connector.getProvider({ chainId })) as { + request(...args: any): Promise + } + + return createClient({ + account, + chain, + name: 'Connector Client', + transport: (opts) => custom(provider)({ ...opts, retryCount: 0 }), + }) as Return +} diff --git a/packages/core/src/actions/getConnectors.test.ts b/packages/core/src/actions/getConnectors.test.ts new file mode 100644 index 0000000000..d15f5fbb08 --- /dev/null +++ b/packages/core/src/actions/getConnectors.test.ts @@ -0,0 +1,8 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getConnectors } from './getConnectors.js' + +test('default', () => { + expect(getConnectors(config)).toEqual(config.connectors) +}) diff --git a/packages/core/src/actions/getConnectors.ts b/packages/core/src/actions/getConnectors.ts new file mode 100644 index 0000000000..439362d3f4 --- /dev/null +++ b/packages/core/src/actions/getConnectors.ts @@ -0,0 +1,17 @@ +import type { Config, Connector } from '../createConfig.js' +import { deepEqual } from '../utils/deepEqual.js' + +export type GetConnectorsReturnType = + config['connectors'] + +let previousConnectors: readonly Connector[] = [] + +/** https://wagmi.sh/core/api/actions/getConnectors */ +export function getConnectors( + config: config, +): GetConnectorsReturnType { + const connectors = config.connectors + if (deepEqual(previousConnectors, connectors)) return previousConnectors + previousConnectors = connectors + return connectors +} diff --git a/packages/core/src/actions/getEnsAddress.test.ts b/packages/core/src/actions/getEnsAddress.test.ts new file mode 100644 index 0000000000..d120c82ef5 --- /dev/null +++ b/packages/core/src/actions/getEnsAddress.test.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsAddress } from './getEnsAddress.js' + +test('default', async () => { + await expect( + getEnsAddress(config, { name: 'wevm.eth' }), + ).resolves.toMatchInlineSnapshot( + '"0xd2135CfB216b74109775236E36d4b433F1DF507B"', + ) +}) diff --git a/packages/core/src/actions/getEnsAddress.ts b/packages/core/src/actions/getEnsAddress.ts new file mode 100644 index 0000000000..5f882999be --- /dev/null +++ b/packages/core/src/actions/getEnsAddress.ts @@ -0,0 +1,30 @@ +import { + type GetEnsAddressErrorType as viem_GetEnsAddressErrorType, + type GetEnsAddressParameters as viem_GetEnsAddressParameters, + type GetEnsAddressReturnType as viem_GetEnsAddressReturnType, + getEnsAddress as viem_getEnsAddress, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetEnsAddressParameters = Compute< + viem_GetEnsAddressParameters & ChainIdParameter +> + +export type GetEnsAddressReturnType = viem_GetEnsAddressReturnType + +export type GetEnsAddressErrorType = viem_GetEnsAddressErrorType + +/** https://wagmi.sh/core/api/actions/getEnsAddress */ +export function getEnsAddress( + config: config, + parameters: GetEnsAddressParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getEnsAddress, 'getEnsAddress') + return action(rest) +} diff --git a/packages/core/src/actions/getEnsAvatar.test.ts b/packages/core/src/actions/getEnsAvatar.test.ts new file mode 100644 index 0000000000..ed1e830481 --- /dev/null +++ b/packages/core/src/actions/getEnsAvatar.test.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsAvatar } from './getEnsAvatar.js' + +test('default', async () => { + await expect( + getEnsAvatar(config, { + name: 'wevm.eth', + }), + ).resolves.toMatchInlineSnapshot('"https://euc.li/wevm.eth"') +}) diff --git a/packages/core/src/actions/getEnsAvatar.ts b/packages/core/src/actions/getEnsAvatar.ts new file mode 100644 index 0000000000..e6c3855d13 --- /dev/null +++ b/packages/core/src/actions/getEnsAvatar.ts @@ -0,0 +1,30 @@ +import { + type GetEnsAvatarErrorType as viem_GetEnsAvatarErrorType, + type GetEnsAvatarParameters as viem_GetEnsAvatarParameters, + type GetEnsAvatarReturnType as viem_GetEnsAvatarReturnType, + getEnsAvatar as viem_getEnsAvatar, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetEnsAvatarParameters = Compute< + viem_GetEnsAvatarParameters & ChainIdParameter +> + +export type GetEnsAvatarReturnType = viem_GetEnsAvatarReturnType + +export type GetEnsAvatarErrorType = viem_GetEnsAvatarErrorType + +/** https://wagmi.sh/core/api/actions/getEnsAvatar */ +export function getEnsAvatar( + config: config, + parameters: GetEnsAvatarParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getEnsAvatar, 'getEnsAvatar') + return action(rest) +} diff --git a/packages/core/src/actions/getEnsName.test.ts b/packages/core/src/actions/getEnsName.test.ts new file mode 100644 index 0000000000..38d1bae97b --- /dev/null +++ b/packages/core/src/actions/getEnsName.test.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsName } from './getEnsName.js' + +test('default', async () => { + await expect( + getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }), + ).resolves.toMatchInlineSnapshot('"wevm.eth"') +}) diff --git a/packages/core/src/actions/getEnsName.ts b/packages/core/src/actions/getEnsName.ts new file mode 100644 index 0000000000..e6ab338db4 --- /dev/null +++ b/packages/core/src/actions/getEnsName.ts @@ -0,0 +1,30 @@ +import { + type GetEnsNameErrorType as viem_GetEnsNameErrorType, + type GetEnsNameParameters as viem_GetEnsNameParameters, + type GetEnsNameReturnType as viem_GetEnsNameReturnType, + getEnsName as viem_getEnsName, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetEnsNameParameters = Compute< + viem_GetEnsNameParameters & ChainIdParameter +> + +export type GetEnsNameReturnType = viem_GetEnsNameReturnType + +export type GetEnsNameErrorType = viem_GetEnsNameErrorType + +/** https://wagmi.sh/core/api/actions/getEnsName */ +export function getEnsName( + config: config, + parameters: GetEnsNameParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getEnsName, 'getEnsName') + return action(rest) +} diff --git a/packages/core/src/actions/getEnsResolver.test.ts b/packages/core/src/actions/getEnsResolver.test.ts new file mode 100644 index 0000000000..4bc30be55b --- /dev/null +++ b/packages/core/src/actions/getEnsResolver.test.ts @@ -0,0 +1,14 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsResolver } from './getEnsResolver.js' + +test('default', async () => { + await expect( + getEnsResolver(config, { + name: 'wevm.eth', + }), + ).resolves.toMatchInlineSnapshot( + '"0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"', + ) +}) diff --git a/packages/core/src/actions/getEnsResolver.ts b/packages/core/src/actions/getEnsResolver.ts new file mode 100644 index 0000000000..ab59b7d76c --- /dev/null +++ b/packages/core/src/actions/getEnsResolver.ts @@ -0,0 +1,30 @@ +import { + type GetEnsResolverErrorType as viem_GetEnsResolverErrorType, + type GetEnsResolverParameters as viem_GetEnsResolverParameters, + type GetEnsResolverReturnType as viem_GetEnsResolverReturnType, + getEnsResolver as viem_getEnsResolver, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetEnsResolverParameters = Compute< + viem_GetEnsResolverParameters & ChainIdParameter +> + +export type GetEnsResolverReturnType = viem_GetEnsResolverReturnType + +export type GetEnsResolverErrorType = viem_GetEnsResolverErrorType + +/** https://wagmi.sh/core/api/actions/getEnsResolver */ +export function getEnsResolver( + config: config, + parameters: GetEnsResolverParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getEnsResolver, 'getEnsResolver') + return action(rest) +} diff --git a/packages/core/src/actions/getEnsText.test.ts b/packages/core/src/actions/getEnsText.test.ts new file mode 100644 index 0000000000..63801747d5 --- /dev/null +++ b/packages/core/src/actions/getEnsText.test.ts @@ -0,0 +1,13 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsText } from './getEnsText.js' + +test('default', async () => { + await expect( + getEnsText(config, { + name: 'wevm.eth', + key: 'com.twitter', + }), + ).resolves.toMatchInlineSnapshot('"wevm_dev"') +}) diff --git a/packages/core/src/actions/getEnsText.ts b/packages/core/src/actions/getEnsText.ts new file mode 100644 index 0000000000..d786f72b78 --- /dev/null +++ b/packages/core/src/actions/getEnsText.ts @@ -0,0 +1,30 @@ +import { + type GetEnsTextErrorType as viem_GetEnsTextErrorType, + type GetEnsTextParameters as viem_GetEnsTextParameters, + type GetEnsTextReturnType as viem_GetEnsTextReturnType, + getEnsText as viem_getEnsText, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetEnsTextParameters = Compute< + viem_GetEnsTextParameters & ChainIdParameter +> + +export type GetEnsTextReturnType = viem_GetEnsTextReturnType + +export type GetEnsTextErrorType = viem_GetEnsTextErrorType + +/** https://wagmi.sh/core/api/actions/getEnsText */ +export function getEnsText( + config: config, + parameters: GetEnsTextParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getEnsText, 'getEnsText') + return action(rest) +} diff --git a/packages/core/src/actions/getFeeHistory.test.ts b/packages/core/src/actions/getFeeHistory.test.ts new file mode 100644 index 0000000000..2630381556 --- /dev/null +++ b/packages/core/src/actions/getFeeHistory.test.ts @@ -0,0 +1,63 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getFeeHistory } from './getFeeHistory.js' + +test('default', async () => { + await expect( + getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + }), + ).resolves.toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) +}) + +test('parameters: chainId', async () => { + await expect( + getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + chainId: chain.mainnet2.id, + }), + ).resolves.toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) +}) + +test('parameters: blockNumber', async () => { + await expect( + getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + blockNumber: 18677379n, + }), + ).resolves.toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) +}) + +test('parameters: blockTag', async () => { + await expect( + getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + blockTag: 'safe', + }), + ).resolves.toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) +}) diff --git a/packages/core/src/actions/getFeeHistory.ts b/packages/core/src/actions/getFeeHistory.ts new file mode 100644 index 0000000000..9588214139 --- /dev/null +++ b/packages/core/src/actions/getFeeHistory.ts @@ -0,0 +1,36 @@ +import { + type GetFeeHistoryErrorType as viem_GetFeeHistoryErrorType, + type GetFeeHistoryParameters as viem_GetFeeHistoryParameters, + type GetFeeHistoryReturnType as viem_GetFeeHistoryReturnType, + getFeeHistory as viem_getFeeHistory, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetFeeHistoryParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute> + +export type GetFeeHistoryReturnType = viem_GetFeeHistoryReturnType + +export type GetFeeHistoryErrorType = viem_GetFeeHistoryErrorType + +/** https://wagmi.sh/core/api/actions/getFeeHistory */ +export function getFeeHistory< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: GetFeeHistoryParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getFeeHistory, 'getFeeHistory') + return action(rest) +} diff --git a/packages/core/src/actions/getGasPrice.test.ts b/packages/core/src/actions/getGasPrice.test.ts new file mode 100644 index 0000000000..64b7ba6fd5 --- /dev/null +++ b/packages/core/src/actions/getGasPrice.test.ts @@ -0,0 +1,21 @@ +import { chain, config, testClient } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getGasPrice } from './getGasPrice.js' + +test('default', async () => { + await testClient.mainnet.setNextBlockBaseFeePerGas({ + baseFeePerGas: 2_000_000_000n, + }) + await expect(getGasPrice(config)).resolves.toBe(3000000000n) +}) + +test('parameters: chainId', async () => { + await testClient.mainnet2.setNextBlockBaseFeePerGas({ + baseFeePerGas: 1_000_000_000n, + }) + await testClient.mainnet2.mine({ blocks: 1 }) + await expect( + getGasPrice(config, { chainId: chain.mainnet2.id }), + ).resolves.toBe(1875000000n) +}) diff --git a/packages/core/src/actions/getGasPrice.ts b/packages/core/src/actions/getGasPrice.ts new file mode 100644 index 0000000000..c6482c44a2 --- /dev/null +++ b/packages/core/src/actions/getGasPrice.ts @@ -0,0 +1,35 @@ +import { + type GetGasPriceErrorType as viem_GetGasPriceErrorType, + type GetGasPriceReturnType as viem_GetGasPriceReturnType, + getGasPrice as viem_getGasPrice, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetGasPriceParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute> + +export type GetGasPriceReturnType = viem_GetGasPriceReturnType + +export type GetGasPriceErrorType = viem_GetGasPriceErrorType + +/** https://wagmi.sh/core/api/actions/getGasPrice */ +export function getGasPrice< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: GetGasPriceParameters = {}, +): Promise { + const { chainId } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getGasPrice, 'getGasPrice') + return action({}) +} diff --git a/packages/core/src/actions/getProof.test.ts b/packages/core/src/actions/getProof.test.ts new file mode 100644 index 0000000000..5ff0af2828 --- /dev/null +++ b/packages/core/src/actions/getProof.test.ts @@ -0,0 +1,16 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getProof } from './getProof.js' + +test('default', async () => { + await expect( + getProof(config, { + chainId: chain.optimism.id, + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ).resolves.toBeDefined() +}) diff --git a/packages/core/src/actions/getProof.ts b/packages/core/src/actions/getProof.ts new file mode 100644 index 0000000000..ee9ec218d3 --- /dev/null +++ b/packages/core/src/actions/getProof.ts @@ -0,0 +1,30 @@ +import { + type GetProofErrorType as viem_GetProofErrorType, + type GetProofParameters as viem_GetProofParameters, + type GetProofReturnType as viem_GetProofReturnType, + getProof as viem_getProof, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetProofParameters = Compute< + viem_GetProofParameters & ChainIdParameter +> + +export type GetProofReturnType = viem_GetProofReturnType + +export type GetProofErrorType = viem_GetProofErrorType + +/** https://wagmi.sh/core/api/actions/getProof */ +export async function getProof( + config: config, + parameters: GetProofParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getProof, 'getProof') + return action(rest) +} diff --git a/packages/core/src/actions/getPublicClient.test-d.ts b/packages/core/src/actions/getPublicClient.test-d.ts new file mode 100644 index 0000000000..711f11a29f --- /dev/null +++ b/packages/core/src/actions/getPublicClient.test-d.ts @@ -0,0 +1,27 @@ +import { chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { getPublicClient } from './getPublicClient.js' + +test('default', () => { + const client = getPublicClient(config) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = getPublicClient(config, { + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + const client = getPublicClient(config, { + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/getPublicClient.test.ts b/packages/core/src/actions/getPublicClient.test.ts new file mode 100644 index 0000000000..c77d0bfb94 --- /dev/null +++ b/packages/core/src/actions/getPublicClient.test.ts @@ -0,0 +1,17 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getPublicClient } from './getPublicClient.js' + +test('default', () => { + expect(getPublicClient(config)).toBeDefined() +}) + +test('behavior: unconfigured chain', () => { + expect( + getPublicClient(config, { + // @ts-expect-error + chainId: 123456, + }), + ).toBeUndefined() +}) diff --git a/packages/core/src/actions/getPublicClient.ts b/packages/core/src/actions/getPublicClient.ts new file mode 100644 index 0000000000..1fbd53ed30 --- /dev/null +++ b/packages/core/src/actions/getPublicClient.ts @@ -0,0 +1,52 @@ +import { type Client, type PublicClient, publicActions } from 'viem' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' +import { getClient } from './getClient.js' + +export type GetPublicClientParameters< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +> = ChainIdParameter + +export type GetPublicClientReturnType< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + resolvedChainId extends + | config['chains'][number]['id'] + | undefined = IsNarrowable< + config['chains'][number]['id'], + number + > extends true + ? IsNarrowable extends true + ? chainId + : config['chains'][number]['id'] + : config['chains'][number]['id'] | undefined, +> = resolvedChainId extends config['chains'][number]['id'] + ? Compute< + PublicClient< + config['_internal']['transports'][resolvedChainId], + Extract + > + > + : undefined + +export function getPublicClient< + config extends Config, + chainId extends config['chains'][number]['id'] | number | undefined, +>( + config: config, + parameters: GetPublicClientParameters = {}, +): GetPublicClientReturnType { + const client = getClient(config, parameters) + return (client as Client)?.extend(publicActions) as GetPublicClientReturnType< + config, + chainId + > +} diff --git a/packages/core/src/actions/getStorageAt.test.ts b/packages/core/src/actions/getStorageAt.test.ts new file mode 100644 index 0000000000..bc612fe91b --- /dev/null +++ b/packages/core/src/actions/getStorageAt.test.ts @@ -0,0 +1,59 @@ +import { address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getStorageAt } from './getStorageAt.js' + +test('default', async () => { + await expect( + getStorageAt(config, { + address: address.wagmiMintExample, + slot: '0x0', + }), + ).resolves.toBe( + '0x7761676d6900000000000000000000000000000000000000000000000000000a', + ) + await expect( + getStorageAt(config, { + address: address.wagmiMintExample, + slot: '0x1', + }), + ).resolves.toBe( + '0x5741474d4900000000000000000000000000000000000000000000000000000a', + ) +}) + +test('parameters: blockNumber', async () => { + await expect( + getStorageAt(config, { + address: address.wagmiMintExample, + blockNumber: 16280770n, + slot: '0x0', + }), + ).resolves.toBe( + '0x7761676d6900000000000000000000000000000000000000000000000000000a', + ) +}) + +test('parameters: blockTag', async () => { + await expect( + getStorageAt(config, { + address: address.wagmiMintExample, + blockTag: 'safe', + slot: '0x0', + }), + ).resolves.toBe( + '0x7761676d6900000000000000000000000000000000000000000000000000000a', + ) +}) + +test('parameters: chainId', async () => { + await expect( + getStorageAt(config, { + address: address.wagmiMintExample, + chainId: chain.optimism.id, + slot: '0x0', + }), + ).resolves.toBe( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ) +}) diff --git a/packages/core/src/actions/getStorageAt.ts b/packages/core/src/actions/getStorageAt.ts new file mode 100644 index 0000000000..a07ec081b7 --- /dev/null +++ b/packages/core/src/actions/getStorageAt.ts @@ -0,0 +1,30 @@ +import { + type GetStorageAtErrorType as viem_GetStorageAtErrorType, + type GetStorageAtParameters as viem_GetStorageAtParameters, + type GetStorageAtReturnType as viem_GetStorageAtReturnType, + getStorageAt as viem_getStorageAt, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetStorageAtParameters = Compute< + viem_GetStorageAtParameters & ChainIdParameter +> + +export type GetStorageAtReturnType = viem_GetStorageAtReturnType + +export type GetStorageAtErrorType = viem_GetStorageAtErrorType + +/** https://wagmi.sh/core/api/actions/getStorageAt */ +export async function getStorageAt( + config: config, + parameters: GetStorageAtParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getStorageAt, 'getStorageAt') + return action(rest) +} diff --git a/packages/core/src/actions/getToken.test.ts b/packages/core/src/actions/getToken.test.ts new file mode 100644 index 0000000000..ed8903f3dd --- /dev/null +++ b/packages/core/src/actions/getToken.test.ts @@ -0,0 +1,84 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getToken } from './getToken.js' + +test('default', async () => { + await expect( + getToken(config, { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + }), + ).resolves.toMatchInlineSnapshot(` + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "decimals": 18, + "name": "Uniswap", + "symbol": "UNI", + "totalSupply": { + "formatted": "1000000000", + "value": 1000000000000000000000000000n, + }, + } + `) +}) + +test('parameters: formatUnits', async () => { + await expect( + getToken(config, { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + formatUnits: 'gwei', + }), + ).resolves.toMatchInlineSnapshot(` + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "decimals": 18, + "name": "Uniswap", + "symbol": "UNI", + "totalSupply": { + "formatted": "1000000000000000000", + "value": 1000000000000000000000000000n, + }, + } + `) +}) + +test('behavior: bytes32 token', async () => { + await expect( + getToken(config, { + address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + }), + ).resolves.toMatchInlineSnapshot(` + { + "address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "decimals": 18, + "name": "Maker", + "symbol": "MKR", + "totalSupply": { + "formatted": "977631.036950888222010062", + "value": 977631036950888222010062n, + }, + } + `) +}) + +test('behavior: bogus token', async () => { + await expect( + getToken(config, { + address: '0xa0cf798816d4b9b9866b5330eea46a18382f251e', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "decimals" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "decimals", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa0cf798816d4b9b9866b5330eea46a18382f251e + function: decimals() + + Docs: https://viem.sh/docs/contract/multicall + Version: viem@2.29.2] + `) +}) diff --git a/packages/core/src/actions/getToken.ts b/packages/core/src/actions/getToken.ts new file mode 100644 index 0000000000..480a742820 --- /dev/null +++ b/packages/core/src/actions/getToken.ts @@ -0,0 +1,141 @@ +import type { Address, Hex } from 'viem' +import { + ContractFunctionExecutionError, + formatUnits, + hexToString, + trim, +} from 'viem' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Unit } from '../types/unit.js' +import type { Compute } from '../types/utils.js' +import { getUnit } from '../utils/getUnit.js' +import { type ReadContractsErrorType, readContracts } from './readContracts.js' + +export type GetTokenParameters = Compute< + ChainIdParameter & { + address: Address + formatUnits?: Unit | undefined + } +> + +export type GetTokenReturnType = { + address: Address + decimals: number + name: string | undefined + symbol: string | undefined + totalSupply: { + formatted: string + value: bigint + } +} + +export type GetTokenErrorType = ReadContractsErrorType + +/** @deprecated */ +export async function getToken( + config: config, + parameters: GetTokenParameters, +): Promise { + const { address, chainId, formatUnits: unit = 18 } = parameters + + function getAbi(type: type) { + return [ + { + type: 'function', + name: 'decimals', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint8' }], + }, + { + type: 'function', + name: 'name', + stateMutability: 'view', + inputs: [], + outputs: [{ type }], + }, + { + type: 'function', + name: 'symbol', + stateMutability: 'view', + inputs: [], + outputs: [{ type }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ type: 'uint256' }], + }, + ] as const + } + + try { + const abi = getAbi('string') + const contractConfig = { address, abi, chainId } as const + const [decimals, name, symbol, totalSupply] = await readContracts(config, { + allowFailure: true, + contracts: [ + { ...contractConfig, functionName: 'decimals' }, + { ...contractConfig, functionName: 'name' }, + { ...contractConfig, functionName: 'symbol' }, + { ...contractConfig, functionName: 'totalSupply' }, + ] as const, + }) + + // throw if `name` or `symbol` failed + if (name.error instanceof ContractFunctionExecutionError) throw name.error + if (symbol.error instanceof ContractFunctionExecutionError) + throw symbol.error + + // `decimals` and `totalSupply` are required + if (decimals.error) throw decimals.error + if (totalSupply.error) throw totalSupply.error + + return { + address, + decimals: decimals.result, + name: name.result, + symbol: symbol.result, + totalSupply: { + formatted: formatUnits(totalSupply.result!, getUnit(unit)), + value: totalSupply.result, + }, + } + } catch (error) { + // In the chance that there is an error upon decoding the contract result, + // it could be likely that the contract data is represented as bytes32 instead + // of a string. + if (error instanceof ContractFunctionExecutionError) { + const abi = getAbi('bytes32') + const contractConfig = { address, abi, chainId } as const + const [decimals, name, symbol, totalSupply] = await readContracts( + config, + { + allowFailure: false, + contracts: [ + { ...contractConfig, functionName: 'decimals' }, + { ...contractConfig, functionName: 'name' }, + { ...contractConfig, functionName: 'symbol' }, + { ...contractConfig, functionName: 'totalSupply' }, + ] as const, + }, + ) + return { + address, + decimals, + name: hexToString(trim(name as Hex, { dir: 'right' })), + symbol: hexToString(trim(symbol as Hex, { dir: 'right' })), + totalSupply: { + formatted: formatUnits(totalSupply, getUnit(unit)), + value: totalSupply, + }, + } + } + + throw error + } +} diff --git a/packages/core/src/actions/getTransaction.test-d.ts b/packages/core/src/actions/getTransaction.test-d.ts new file mode 100644 index 0000000000..9476b781c1 --- /dev/null +++ b/packages/core/src/actions/getTransaction.test-d.ts @@ -0,0 +1,29 @@ +import { http } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { getTransaction } from './getTransaction.js' + +test('chain formatters', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + const result = await getTransaction(config, { hash: '0x123' }) + if (result.chainId === celo.id) { + expectTypeOf(result.feeCurrency).toEqualTypeOf<`0x${string}` | null>() + } +}) + +test('chainId', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + const result = await getTransaction(config, { + hash: '0x123', + chainId: celo.id, + }) + expectTypeOf(result.feeCurrency).toEqualTypeOf<`0x${string}` | null>() +}) diff --git a/packages/core/src/actions/getTransaction.test.ts b/packages/core/src/actions/getTransaction.test.ts new file mode 100644 index 0000000000..3615e6a0d9 --- /dev/null +++ b/packages/core/src/actions/getTransaction.test.ts @@ -0,0 +1,36 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransaction } from './getTransaction.js' + +test('default', async () => { + await expect( + getTransaction(config, { + hash: '0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd', + }), + ).resolves.toMatchInlineSnapshot(` + { + "accessList": [], + "blockHash": "0x61c4e868008b465addd7c0a5da03db28bb9911597c58e239a85dd14dd43fd56a", + "blockNumber": 17488642n, + "chainId": 1, + "from": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "gas": 53671n, + "gasPrice": 15806335296n, + "hash": "0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd", + "input": "0xa9059cbb0000000000000000000000006acbe090725d8b1cd59fe5f3e0c9c3685ebb77af00000000000000000000000000000000000000000000000000000002540be400", + "maxFeePerGas": 19000000000n, + "maxPriorityFeePerGas": 1000000000n, + "nonce": 29, + "r": "0x60a19c4a708571d2a7c661dc5494542fa2c6ddd8e7dc218e4c4795b6ba7969f5", + "s": "0x7ef2778cc21f5c12861208d0c030e77193a234273e32a1dd5066d7d677aa1ef2", + "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "transactionIndex": 58, + "type": "eip1559", + "typeHex": "0x2", + "v": 1n, + "value": 0n, + "yParity": 1, + } + `) +}) diff --git a/packages/core/src/actions/getTransaction.ts b/packages/core/src/actions/getTransaction.ts new file mode 100644 index 0000000000..0148282811 --- /dev/null +++ b/packages/core/src/actions/getTransaction.ts @@ -0,0 +1,51 @@ +import type { Chain } from 'viem' +import { + type GetTransactionErrorType as viem_GetTransactionErrorType, + type GetTransactionParameters as viem_GetTransactionParameters, + type GetTransactionReturnType as viem_GetTransactionReturnType, + getTransaction as viem_getTransaction, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetTransactionParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute> + +export type GetTransactionReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = Compute< + { + [key in keyof chains]: viem_GetTransactionReturnType< + IsNarrowable extends true ? chains[key] : undefined + > & { chainId: chains[key]['id'] } + }[number] +> + +export type GetTransactionErrorType = viem_GetTransactionErrorType + +/** https://wagmi.sh/core/api/actions/getTransaction */ +export function getTransaction< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: GetTransactionParameters, +): Promise> { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_getTransaction, 'getTransaction') + return action(rest) as unknown as Promise< + GetTransactionReturnType + > +} diff --git a/packages/core/src/actions/getTransactionConfirmations.test-d.ts b/packages/core/src/actions/getTransactionConfirmations.test-d.ts new file mode 100644 index 0000000000..fd9168dfa2 --- /dev/null +++ b/packages/core/src/actions/getTransactionConfirmations.test-d.ts @@ -0,0 +1,85 @@ +import { config } from '@wagmi/test' +import { mainnet, zkSync } from 'viem/chains' +import { test } from 'vitest' + +import { http } from 'viem' +import { createConfig } from '../createConfig.js' +import { getTransactionConfirmations } from './getTransactionConfirmations.js' + +test('default', async () => { + getTransactionConfirmations(config, { + transactionReceipt: { + blockHash: '0x', + blockNumber: 1n, + contractAddress: '0x', + cumulativeGasUsed: 1n, + effectiveGasPrice: 1n, + from: '0x', + gasUsed: 1n, + l1Fee: 1n, + logs: [], + logsBloom: '0x', + status: 'success', + to: '0x', + transactionHash: '0x', + transactionIndex: 1, + type: 'eip1559', + }, + }) +}) + +test('chain formatters', async () => { + const config = createConfig({ + chains: [mainnet, zkSync], + transports: { [mainnet.id]: http(), [zkSync.id]: http() }, + }) + + const transactionReceipt = { + blockHash: '0x', + blockNumber: 1n, + contractAddress: '0x', + cumulativeGasUsed: 1n, + effectiveGasPrice: 1n, + from: '0x', + gasUsed: 1n, + logsBloom: '0x', + status: 'success', + to: '0x', + transactionHash: '0x', + transactionIndex: 1, + type: 'eip1559', + } as const + + getTransactionConfirmations(config, { + transactionReceipt: { + ...transactionReceipt, + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) + + getTransactionConfirmations(config, { + chainId: zkSync.id, + transactionReceipt: { + ...transactionReceipt, + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) + + getTransactionConfirmations(config, { + chainId: mainnet.id, + transactionReceipt: { + ...transactionReceipt, + // @ts-expect-error + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) +}) diff --git a/packages/core/src/actions/getTransactionConfirmations.test.ts b/packages/core/src/actions/getTransactionConfirmations.test.ts new file mode 100644 index 0000000000..a2f47d6a42 --- /dev/null +++ b/packages/core/src/actions/getTransactionConfirmations.test.ts @@ -0,0 +1,25 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransactionConfirmations } from './getTransactionConfirmations.js' +import { getTransactionReceipt } from './getTransactionReceipt.js' + +test('default', async () => { + await expect( + getTransactionConfirmations(config, { + hash: '0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd', + }), + ).resolves.toBeTypeOf('bigint') +}) + +test('parameters: transactionReceipt', async () => { + const transactionReceipt = await getTransactionReceipt(config, { + hash: '0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd', + }) + + await expect( + getTransactionConfirmations(config, { + transactionReceipt, + }), + ).resolves.toBeTypeOf('bigint') +}) diff --git a/packages/core/src/actions/getTransactionConfirmations.ts b/packages/core/src/actions/getTransactionConfirmations.ts new file mode 100644 index 0000000000..8baa88cf16 --- /dev/null +++ b/packages/core/src/actions/getTransactionConfirmations.ts @@ -0,0 +1,52 @@ +import type { Chain } from 'viem' +import { + type GetTransactionConfirmationsErrorType as viem_GetTransactionConfirmationsErrorType, + type GetTransactionConfirmationsParameters as viem_GetTransactionConfirmationsParameters, + type GetTransactionConfirmationsReturnType as viem_GetTransactionConfirmationsReturnType, + getTransactionConfirmations as viem_getTransactionConfirmations, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import { getAction } from '../utils/getAction.js' + +export type GetTransactionConfirmationsParameters< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: viem_GetTransactionConfirmationsParameters< + chains[key] + > & + ChainIdParameter +}[number] + +export type GetTransactionConfirmationsReturnType = + viem_GetTransactionConfirmationsReturnType + +export type GetTransactionConfirmationsErrorType = + viem_GetTransactionConfirmationsErrorType + +/** https://wagmi.sh/core/api/actions/getTransactionConfirmations */ +export function getTransactionConfirmations< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +>( + config: config, + parameters: GetTransactionConfirmationsParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_getTransactionConfirmations, + 'getTransactionConfirmations', + ) + return action(rest as viem_GetTransactionConfirmationsParameters) +} diff --git a/packages/core/src/actions/getTransactionCount.test.ts b/packages/core/src/actions/getTransactionCount.test.ts new file mode 100644 index 0000000000..95f0e6ddee --- /dev/null +++ b/packages/core/src/actions/getTransactionCount.test.ts @@ -0,0 +1,50 @@ +import { accounts, chain, config, testClient } from '@wagmi/test' +import { expect, test } from 'vitest' + +import type { BlockTag } from 'viem' +import { getTransactionCount } from './getTransactionCount.js' + +const address = accounts[0] + +test('default', async () => { + await expect(getTransactionCount(config, { address })).resolves.toBeTypeOf( + 'number', + ) +}) + +test('parameters: chainId', async () => { + await testClient.mainnet2.setNonce({ + address, + nonce: 6969, + }) + await testClient.mainnet2.mine({ blocks: 1 }) + await expect( + getTransactionCount(config, { address, chainId: chain.mainnet2.id }), + ).resolves.toBeTypeOf('number') +}) + +test('parameters: blockNumber', async () => { + await expect( + getTransactionCount(config, { address, blockNumber: 13677382n }), + ).resolves.toBeTypeOf('number') +}) + +test.each([ + { blockTag: 'earliest' }, + { blockTag: 'finalized' }, + { blockTag: 'latest' }, + { blockTag: 'pending' }, + { blockTag: 'safe' }, +] as { blockTag: BlockTag; expected: number }[])( + 'parameters: blockTag $blockTag', + async ({ blockTag }) => { + await testClient.mainnet.restart() + + await expect( + getTransactionCount(config, { + address, + blockTag, + }), + ).resolves.toBeTypeOf('number') + }, +) diff --git a/packages/core/src/actions/getTransactionCount.ts b/packages/core/src/actions/getTransactionCount.ts new file mode 100644 index 0000000000..6872e6ede6 --- /dev/null +++ b/packages/core/src/actions/getTransactionCount.ts @@ -0,0 +1,34 @@ +import { + type GetTransactionCountErrorType as viem_GetTransactionCountErrorType, + type GetTransactionCountParameters as viem_GetTransactionCountParameters, + type GetTransactionCountReturnType as viem_GetTransactionCountReturnType, + getTransactionCount as viem_getTransactionCount, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetTransactionCountParameters = + Compute & viem_GetTransactionCountParameters> + +export type GetTransactionCountReturnType = viem_GetTransactionCountReturnType + +export type GetTransactionCountErrorType = viem_GetTransactionCountErrorType + +/** https://wagmi.sh/core/api/actions/getTransactionCount */ +export async function getTransactionCount( + config: config, + parameters: GetTransactionCountParameters, +): Promise { + const { address, blockNumber, blockTag, chainId } = parameters + + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_getTransactionCount, + 'getTransactionCount', + ) + return action(blockNumber ? { address, blockNumber } : { address, blockTag }) +} diff --git a/packages/core/src/actions/getTransactionReceipt.test-d.ts b/packages/core/src/actions/getTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..e9850eacc7 --- /dev/null +++ b/packages/core/src/actions/getTransactionReceipt.test-d.ts @@ -0,0 +1,36 @@ +import { http } from 'viem' +import { mainnet, zkSync } from 'viem/chains' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { getTransactionReceipt } from './getTransactionReceipt.js' + +test('chain formatters', async () => { + const config = createConfig({ + chains: [mainnet, zkSync], + transports: { [mainnet.id]: http(), [zkSync.id]: http() }, + }) + const result = await getTransactionReceipt(config, { hash: '0x123' }) + if (result.chainId === zkSync.id) { + expectTypeOf(result.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.logs).toEqualTypeOf() + expectTypeOf(result.l2ToL1Logs).toEqualTypeOf() + } +}) + +test('chainId', async () => { + const config = createConfig({ + chains: [zkSync], + transports: { [zkSync.id]: http() }, + }) + const result = await getTransactionReceipt(config, { + hash: '0x123', + chainId: zkSync.id, + }) + expectTypeOf(result.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.logs).toEqualTypeOf() + expectTypeOf(result.l2ToL1Logs).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/getTransactionReceipt.test.ts b/packages/core/src/actions/getTransactionReceipt.test.ts new file mode 100644 index 0000000000..82fee0b11f --- /dev/null +++ b/packages/core/src/actions/getTransactionReceipt.test.ts @@ -0,0 +1,35 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransaction } from './getTransaction.js' +import { getTransactionReceipt } from './getTransactionReceipt.js' + +test('default', async () => { + const transaction = await getTransaction(config, { + blockNumber: 16280769n, + index: 0, + }) + + await expect( + getTransactionReceipt(config, { + hash: transaction.hash, + }), + ).resolves.toMatchInlineSnapshot(` + { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + } + `) +}) diff --git a/packages/core/src/actions/getTransactionReceipt.ts b/packages/core/src/actions/getTransactionReceipt.ts new file mode 100644 index 0000000000..8c06e36ba6 --- /dev/null +++ b/packages/core/src/actions/getTransactionReceipt.ts @@ -0,0 +1,57 @@ +import type { Chain } from 'viem' +import { + type GetTransactionReceiptErrorType as viem_GetTransactionReceiptErrorType, + type GetTransactionReceiptParameters as viem_GetTransactionReceiptParameters, + type GetTransactionReceiptReturnType as viem_GetTransactionReceiptReturnType, + getTransactionReceipt as viem_getTransactionReceipt, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type GetTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + viem_GetTransactionReceiptParameters & ChainIdParameter +> + +export type GetTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = Compute< + { + [key in keyof chains]: viem_GetTransactionReceiptReturnType< + IsNarrowable extends true ? chains[key] : undefined + > & { chainId: chains[key]['id'] } + }[number] +> + +export type GetTransactionReceiptErrorType = viem_GetTransactionReceiptErrorType + +/** https://wagmi.sh/core/api/actions/getTransactionReceipt */ +export async function getTransactionReceipt< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: GetTransactionReceiptParameters, +): Promise> { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_getTransactionReceipt, + 'getTransactionReceipt', + ) + return action(rest) as unknown as Promise< + GetTransactionReceiptReturnType + > +} diff --git a/packages/core/src/actions/getWalletClient.test-d.ts b/packages/core/src/actions/getWalletClient.test-d.ts new file mode 100644 index 0000000000..d1d87f5e97 --- /dev/null +++ b/packages/core/src/actions/getWalletClient.test-d.ts @@ -0,0 +1,22 @@ +import { chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Account } from 'viem' +import { getWalletClient } from './getWalletClient.js' + +test('default', async () => { + const client = await getWalletClient(config) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() + expectTypeOf(client.account).toEqualTypeOf() +}) + +test('parameters: chainId', async () => { + const client = await getWalletClient(config, { + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() + expectTypeOf(client.account).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/getWalletClient.test.ts b/packages/core/src/actions/getWalletClient.test.ts new file mode 100644 index 0000000000..2350f81b3e --- /dev/null +++ b/packages/core/src/actions/getWalletClient.test.ts @@ -0,0 +1,24 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getWalletClient } from './getWalletClient.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect(getWalletClient(config)).resolves.toBeDefined() + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + getWalletClient(config), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/actions/getWalletClient.ts b/packages/core/src/actions/getWalletClient.ts new file mode 100644 index 0000000000..bf49668701 --- /dev/null +++ b/packages/core/src/actions/getWalletClient.ts @@ -0,0 +1,50 @@ +import { type Account, type WalletClient, walletActions } from 'viem' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { Compute } from '../types/utils.js' +import { + type GetConnectorClientErrorType, + type GetConnectorClientParameters, + getConnectorClient, +} from './getConnectorClient.js' + +export type GetWalletClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = GetConnectorClientParameters + +export type GetWalletClientReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + WalletClient< + config['_internal']['transports'][chainId], + Extract, + Account + > +> + +export type GetWalletClientErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + +export async function getWalletClient< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: GetWalletClientParameters = {}, +): Promise> { + const client = await getConnectorClient(config, parameters) + // @ts-ignore + return client.extend(walletActions) as unknown as GetWalletClientReturnType< + config, + chainId + > +} diff --git a/packages/core/src/actions/multicall.test-d.ts b/packages/core/src/actions/multicall.test-d.ts new file mode 100644 index 0000000000..bb70db9989 --- /dev/null +++ b/packages/core/src/actions/multicall.test-d.ts @@ -0,0 +1,106 @@ +import { abi, config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { multicall } from './multicall.js' + +test('default', async () => { + const result = await multicall(config, { + chainId: 1, + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + expectTypeOf(result).toEqualTypeOf< + [ + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: bigint; status: 'success' } + ), + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: string; status: 'success' } + ), + ] + >() +}) + +test('allowFailure', async () => { + const result = await multicall(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + expectTypeOf(result).toEqualTypeOf<[bigint, string]>() +}) + +test('MulticallParameters', async () => { + type Result = Parameters< + typeof multicall< + typeof config, + [ + { + address: '0x' + abi: typeof abi.viewOverloads + functionName: 'foo' + }, + ] + > + >[1]['contracts'][0] + expectTypeOf().toEqualTypeOf<'foo' | 'bar'>() + expectTypeOf().toEqualTypeOf< + readonly [] | readonly [Address] | readonly [Address, Address] | undefined + >() +}) + +test('overloads', async () => { + const res = await multicall(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }, + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }, + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }, + ], + }) + + expectTypeOf(res).toEqualTypeOf< + [number, string, { foo: Address; bar: Address }] + >() +}) diff --git a/packages/core/src/actions/multicall.test.ts b/packages/core/src/actions/multicall.test.ts new file mode 100644 index 0000000000..cf2d7c0ddf --- /dev/null +++ b/packages/core/src/actions/multicall.test.ts @@ -0,0 +1,46 @@ +import { abi, address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { multicall } from './multicall.js' + +test('default', async () => { + await expect( + multicall(config, { + contracts: [ + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + }), + ).resolves.toMatchInlineSnapshot(` + [ + { + "result": 4n, + "status": "success", + }, + ] + `) +}) + +test('allowFailure', async () => { + await expect( + multicall(config, { + allowFailure: false, + contracts: [ + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + }), + ).resolves.toMatchInlineSnapshot(` + [ + 4n, + ] + `) +}) diff --git a/packages/core/src/actions/multicall.ts b/packages/core/src/actions/multicall.ts new file mode 100644 index 0000000000..528bb0118e --- /dev/null +++ b/packages/core/src/actions/multicall.ts @@ -0,0 +1,42 @@ +import type { + ContractFunctionParameters, + MulticallErrorType as viem_MulticallErrorType, + MulticallParameters as viem_MulticallParameters, + MulticallReturnType as viem_MulticallReturnType, +} from 'viem' +import { multicall as viem_multicall } from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import { getAction } from '../utils/getAction.js' + +export type MulticallParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + config extends Config = Config, +> = viem_MulticallParameters & ChainIdParameter + +export type MulticallReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, +> = viem_MulticallReturnType + +export type MulticallErrorType = viem_MulticallErrorType + +export async function multicall< + config extends Config, + const contracts extends readonly ContractFunctionParameters[], + allowFailure extends boolean = true, +>( + config: config, + parameters: MulticallParameters, +): Promise> { + const { allowFailure = true, chainId, contracts, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_multicall, 'multicall') + return action({ + allowFailure, + contracts, + ...rest, + }) as Promise> +} diff --git a/packages/core/src/actions/prepareTransactionRequest.test-d.ts b/packages/core/src/actions/prepareTransactionRequest.test-d.ts new file mode 100644 index 0000000000..a8a0091157 --- /dev/null +++ b/packages/core/src/actions/prepareTransactionRequest.test-d.ts @@ -0,0 +1,80 @@ +import { accounts, config } from '@wagmi/test' +import { http, parseEther } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type PrepareTransactionRequestParameters, + prepareTransactionRequest, +} from './prepareTransactionRequest.js' + +const targetAccount = accounts[1] + +test('default', async () => { + const response = await prepareTransactionRequest(config, { + chainId: 1, + to: '0x', + value: parseEther('1'), + }) + const { nonce: _nonce, ...request } = response + request.to + request.chainId + + expectTypeOf(response).toMatchTypeOf<{ + chainId: 1 + }>() +}) + +test('chain formatters', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = PrepareTransactionRequestParameters + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + const request = await prepareTransactionRequest(config, { + to: targetAccount, + value: parseEther('0.01'), + feeCurrency: '0x', + }) + if (request.chainId === celo.id) { + expectTypeOf(request.chainId).toEqualTypeOf(celo.id) + expectTypeOf(request.feeCurrency).toEqualTypeOf<`0x${string}` | undefined>() + } + + type Result2 = PrepareTransactionRequestParameters< + typeof config, + typeof celo.id + > + expectTypeOf().toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + const request2 = await prepareTransactionRequest(config, { + chainId: celo.id, + to: targetAccount, + value: parseEther('0.01'), + feeCurrency: '0x', + }) + expectTypeOf(request2.chainId).toEqualTypeOf(celo.id) + expectTypeOf(request2.feeCurrency).toEqualTypeOf<`0x${string}` | undefined>() + + type Result3 = PrepareTransactionRequestParameters< + typeof config, + typeof mainnet.id + > + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + prepareTransactionRequest(config, { + chainId: mainnet.id, + to: targetAccount, + value: parseEther('0.01'), + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/actions/prepareTransactionRequest.test.ts b/packages/core/src/actions/prepareTransactionRequest.test.ts new file mode 100644 index 0000000000..271037af8f --- /dev/null +++ b/packages/core/src/actions/prepareTransactionRequest.test.ts @@ -0,0 +1,108 @@ +import { accounts, config, privateKey } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { parseEther } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { prepareTransactionRequest } from './prepareTransactionRequest.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const request = await prepareTransactionRequest(config, { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) + + const { + gas: _gas, + maxFeePerGas: _mfpg, + maxPriorityFeePerGas: _mpfpg, + nonce: _nonce, + ...rest + } = request + expect(rest).toMatchInlineSnapshot(` + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "type": "eip1559", + "value": 1000000000000000000n, + } + `) + + await disconnect(config, { connector }) +}) + +test('parameters: account', async () => { + await connect(config, { connector }) + + const request = await prepareTransactionRequest(config, { + account: accounts[0], + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) + + const { + gas: _gas, + maxFeePerGas: _mfpg, + maxPriorityFeePerGas: _mpfpg, + nonce: _nonce, + ...rest + } = request + expect(rest).toMatchInlineSnapshot(` + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "type": "eip1559", + "value": 1000000000000000000n, + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const account = privateKeyToAccount(privateKey) + + const request = await prepareTransactionRequest(config, { + account, + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) + + const { + gas: _gas, + maxFeePerGas: _mfpg, + maxPriorityFeePerGas: _mpfpg, + nonce: _nonce, + ...rest + } = request + expect(rest).toMatchInlineSnapshot(` + { + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "nonceManager": undefined, + "publicKey": "0x048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5", + "sign": [Function], + "signAuthorization": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "privateKey", + "type": "local", + }, + "chainId": 1, + "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "type": "eip1559", + "value": 1000000000000000000n, + } + `) +}) diff --git a/packages/core/src/actions/prepareTransactionRequest.ts b/packages/core/src/actions/prepareTransactionRequest.ts new file mode 100644 index 0000000000..36ed81f774 --- /dev/null +++ b/packages/core/src/actions/prepareTransactionRequest.ts @@ -0,0 +1,125 @@ +import type { + Account, + Address, + Chain, + PrepareTransactionRequestErrorType as viem_PrepareTransactionRequestErrorType, + PrepareTransactionRequestParameters as viem_PrepareTransactionRequestParameters, + PrepareTransactionRequestRequest as viem_PrepareTransactionRequestRequest, + PrepareTransactionRequestReturnType as viem_PrepareTransactionRequestReturnType, +} from 'viem' +import { prepareTransactionRequest as viem_prepareTransactionRequest } from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { + Compute, + IsNarrowable, + UnionCompute, + UnionStrictOmit, +} from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { getAccount } from './getAccount.js' + +export type PrepareTransactionRequestParameters< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + > = viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + UnionStrictOmit< + viem_PrepareTransactionRequestParameters< + chains[key], + Account, + chains[key], + Account | Address, + request extends viem_PrepareTransactionRequestRequest< + chains[key], + chains[key] + > + ? request + : never + >, + 'chain' + > & + ChainIdParameter & { + to: Address + } + > +}[number] + +export type PrepareTransactionRequestReturnType< + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + > = viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Compute< + viem_PrepareTransactionRequestReturnType< + IsNarrowable extends true ? chains[key] : undefined, + Account, + chains[key], + Account, + request extends viem_PrepareTransactionRequestRequest< + IsNarrowable extends true ? chains[key] : undefined, + chains[key] + > + ? request + : never + > + > & { + chainId: chains[key]['id'] + } +}[number] + +export type PrepareTransactionRequestErrorType = + viem_PrepareTransactionRequestErrorType + +/** https://wagmi.sh/core/api/actions/prepareTransactionRequest */ +export async function prepareTransactionRequest< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + const request extends viem_PrepareTransactionRequestRequest< + SelectChains['0'], + SelectChains['0'] + >, +>( + config: config, + parameters: PrepareTransactionRequestParameters, +): Promise> { + const { account: account_, chainId, ...rest } = parameters + + const account = account_ ?? getAccount(config).address + const client = config.getClient({ chainId }) + + const action = getAction( + client, + viem_prepareTransactionRequest, + 'prepareTransactionRequest', + ) + return action({ + ...rest, + ...(account ? { account } : {}), + } as unknown as viem_PrepareTransactionRequestParameters) as unknown as Promise< + PrepareTransactionRequestReturnType + > +} diff --git a/packages/core/src/actions/readContract.test-d.ts b/packages/core/src/actions/readContract.test-d.ts new file mode 100644 index 0000000000..a667ec03e8 --- /dev/null +++ b/packages/core/src/actions/readContract.test-d.ts @@ -0,0 +1,74 @@ +import { abi, config } from '@wagmi/test' +import { assertType, expectTypeOf, test } from 'vitest' + +import { readContract } from './readContract.js' + +test('default', async () => { + const result = await readContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }) + expectTypeOf(result).toEqualTypeOf() +}) + +test('overloads', async () => { + const result1 = await readContract(config, { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }) + assertType(result1) + + const result2 = await readContract(config, { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: [], + }) + assertType(result2) + + const result3 = await readContract(config, { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3) + + const result4 = await readContract(config, { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType<{ + foo: `0x${string}` + bar: `0x${string}` + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + }>(result4) +}) + +test('deployless read (bytecode)', async () => { + const result = await readContract(config, { + code: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }) + expectTypeOf(result).toEqualTypeOf() +}) + +test('deployless read (factory)', async () => { + const result = await readContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + factory: '0x', + factoryData: '0x', + }) + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/readContract.test.ts b/packages/core/src/actions/readContract.test.ts new file mode 100644 index 0000000000..37f0db7e0c --- /dev/null +++ b/packages/core/src/actions/readContract.test.ts @@ -0,0 +1,37 @@ +import { abi, address, bytecode, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { readContract } from './readContract.js' + +test('default', async () => { + await expect( + readContract(config, { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ).resolves.toMatchInlineSnapshot('4n') +}) + +test('parameters: chainId', async () => { + await expect( + readContract(config, { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + chainId: chain.mainnet2.id, + }), + ).resolves.toMatchInlineSnapshot('4n') +}) + +test('parameters: deployless read (bytecode)', async () => { + await expect( + readContract(config, { + abi: abi.wagmiMintExample, + functionName: 'name', + code: bytecode.wagmiMintExample, + }), + ).resolves.toMatchInlineSnapshot(`"wagmi"`) +}) diff --git a/packages/core/src/actions/readContract.ts b/packages/core/src/actions/readContract.ts new file mode 100644 index 0000000000..e01e74e9f2 --- /dev/null +++ b/packages/core/src/actions/readContract.ts @@ -0,0 +1,58 @@ +import type { Abi } from 'viem' +import type { ContractFunctionArgs, ContractFunctionName } from 'viem' +import { + type ReadContractErrorType as viem_ReadContractErrorType, + type ReadContractParameters as viem_ReadContractParameters, + type ReadContractReturnType as viem_ReadContractReturnType, + readContract as viem_readContract, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import { getAction } from '../utils/getAction.js' + +export type ReadContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + config extends Config = Config, +> = viem_ReadContractParameters & + ChainIdParameter + +export type ReadContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, +> = viem_ReadContractReturnType + +export type ReadContractErrorType = viem_ReadContractErrorType + +/** https://wagmi.sh/core/api/actions/readContract */ +export function readContract< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, +>( + config: config, + parameters: ReadContractParameters, +): Promise> { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_readContract, 'readContract') + return action(rest as any) +} diff --git a/packages/core/src/actions/readContracts.test-d.ts b/packages/core/src/actions/readContracts.test-d.ts new file mode 100644 index 0000000000..a68b2acab6 --- /dev/null +++ b/packages/core/src/actions/readContracts.test-d.ts @@ -0,0 +1,118 @@ +import { abi, config } from '@wagmi/test' +import { assertType, expectTypeOf, test } from 'vitest' + +import { readContracts } from './readContracts.js' + +test('default', async () => { + const result = await readContracts(config, { + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + chainId: 1, + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + expectTypeOf(result).toEqualTypeOf< + [ + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: bigint; status: 'success' } + ), + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: string; status: 'success' } + ), + ] + >() +}) + +test('allowFailure', async () => { + const result = await readContracts(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + expectTypeOf(result).toEqualTypeOf<[bigint, string]>() +}) + +test('overloads', async () => { + const result1 = await readContracts(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }, + ], + }) + assertType<[number] | undefined>(result1) + + const result2 = await readContracts(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: [], + }, + ], + }) + assertType<[number] | undefined>(result2) + + const result3 = await readContracts(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }, + ], + }) + assertType<[string] | undefined>(result3) + + const result4 = await readContracts(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }, + ], + }) + assertType< + | [ + { + foo: `0x${string}` + bar: `0x${string}` + }, + ] + | undefined + >(result4) +}) diff --git a/packages/core/src/actions/readContracts.test.ts b/packages/core/src/actions/readContracts.test.ts new file mode 100644 index 0000000000..4bc33f60da --- /dev/null +++ b/packages/core/src/actions/readContracts.test.ts @@ -0,0 +1,678 @@ +import { abi, address, chain } from '@wagmi/test' +import { http, type MulticallResponse } from 'viem' +import { expect, expectTypeOf, test, vi } from 'vitest' + +import { createConfig } from '../createConfig.js' +import * as multicall from './multicall.js' +import * as readContract from './readContract.js' +import { readContracts } from './readContracts.js' + +const contracts = [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + functionName: 'getAlive', + }, + { + abi: abi.mloot, + address: address.mloot, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 0n], + }, +] + +const { mainnet, mainnet2, optimism } = chain + +const config = createConfig({ + chains: [ + { ...mainnet, contracts: { multicall3: undefined } }, + { ...mainnet2, contracts: { multicall3: undefined } }, + ], + transports: { + [mainnet.id]: http(), + [mainnet2.id]: http(), + }, +}) + +test('default', async () => { + const spy = vi.spyOn(multicall, 'multicall') + const config = createConfig({ + chains: [mainnet, mainnet2], + transports: { + [mainnet.id]: http(), + [mainnet2.id]: http(), + }, + }) + const results = await readContracts(config, { contracts }) + + expect(spy).toHaveBeenCalledWith(config, { + allowFailure: true, + contracts, + chainId: mainnet.id, + }) + expect(results).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + ] + `) +}) + +test.skip('falls back to readContract if multicall is not available', async () => { + const spy = vi.spyOn(readContract, 'readContract') + const config = createConfig({ + chains: [mainnet, { ...mainnet2, contracts: { multicall3: undefined } }], + transports: { + [mainnet.id]: http(), + [mainnet2.id]: http(), + }, + }) + const chainId = mainnet2.id + const contracts = [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'getAlive', + }, + { + abi: abi.mloot, + address: address.mloot, + chainId: mainnet2.id, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 0n], + }, + ] as const + const results = await readContracts(config, { contracts }) + expectTypeOf(results).toEqualTypeOf< + [ + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + ] + >() + + for (const contract of contracts) { + expect(spy).toBeCalledWith(config, { ...contract, chainId }) + } + expect(results).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + ] + `) +}) + +test.skip('multichain', async () => { + const config = createConfig({ + chains: [mainnet, mainnet2, optimism], + transports: { + [mainnet.id]: http(), + [mainnet2.id]: http(), + [optimism.id]: http(), + }, + }) + + const spy = vi.spyOn(multicall, 'multicall') + const mainnetContracts = [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], + }, + ] as const + const mainnet2Contracts = [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'getAlive', + }, + { + abi: abi.mloot, + address: address.mloot, + chainId: mainnet2.id, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 0n], + }, + ] as const + const optimismContracts = [ + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'symbol', + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ] as const + const results = await readContracts(config, { + contracts: [ + mainnetContracts[0]!, + optimismContracts[0]!, + mainnetContracts[1]!, + mainnet2Contracts[0]!, + optimismContracts[1]!, + mainnet2Contracts[1]!, + mainnetContracts[2]!, + ], + }) + expectTypeOf(results).toEqualTypeOf< + [ + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + ] + >() + + expect(spy).toHaveBeenCalledWith(config, { + allowFailure: true, + contracts: mainnetContracts, + chainId: mainnet.id, + overrides: undefined, + }) + expect(spy).toHaveBeenCalledWith(config, { + allowFailure: true, + contracts: mainnet2Contracts, + chainId: mainnet2.id, + overrides: undefined, + }) + expect(results).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": "USDC", + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 10959340n, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "result": 0n, + "status": "success", + }, + ] + `) +}) + +test('multi-chain: falls back to readContract if multicall is not available', async () => { + const config = createConfig({ + chains: [mainnet, { ...optimism, contracts: { multicall3: undefined } }], + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + }) + + const spy = vi.spyOn(readContract, 'readContract') + const mainnetContracts = [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ] as const + const optimismContracts = [ + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'symbol', + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ] as const + const results = await readContracts(config, { + contracts: [...mainnetContracts, ...optimismContracts], + }) + expectTypeOf(results).toEqualTypeOf< + [ + MulticallResponse, + MulticallResponse, + MulticallResponse, + MulticallResponse, + ] + >() + + for (const contract of mainnetContracts) { + expect(spy).toBeCalledWith(config, { ...contract, chainId: mainnet.id }) + } + for (const contract of optimismContracts) { + expect(spy).toBeCalledWith(config, { ...contract, chainId: optimism.id }) + } + expect(results).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": "USDC", + "status": "success", + }, + { + "result": 10959340n, + "status": "success", + }, + ] + `) +}) + +test('throws if allowFailure=false & a contract method fails', async () => { + await expect( + readContracts(config, { + allowFailure: false, + contracts: [ + ...contracts, + { + abi: abi.mloot, + address: address.mloot, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 69420n], + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + ` + [ContractFunctionExecutionError: The contract function "tokenOfOwnerByIndex" reverted with the following reason: + ERC721Enumerable: owner index out of bounds + + Contract Call: + address: 0x1dfe7ca09e99d10835bf73044a23b73fc20623df + function: tokenOfOwnerByIndex(address owner, uint256 index) + args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69420) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2] + `, + ) +}) + +test('allowFailure=true & a contract method fails', async () => { + expect( + await readContracts(config, { + allowFailure: true, + contracts: [ + ...contracts, + { + abi: abi.mloot, + address: address.mloot, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 69420n], + }, + { + abi: abi.mloot, + address: address.mloot, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 69421n], + }, + ], + }), + ).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "error": [ContractFunctionExecutionError: The contract function "tokenOfOwnerByIndex" reverted with the following reason: + ERC721Enumerable: owner index out of bounds + + Contract Call: + address: 0x1dfe7ca09e99d10835bf73044a23b73fc20623df + function: tokenOfOwnerByIndex(address owner, uint256 index) + args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69420) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2], + "result": undefined, + "status": "failure", + }, + { + "error": [ContractFunctionExecutionError: The contract function "tokenOfOwnerByIndex" reverted with the following reason: + ERC721Enumerable: owner index out of bounds + + Contract Call: + address: 0x1dfe7ca09e99d10835bf73044a23b73fc20623df + function: tokenOfOwnerByIndex(address owner, uint256 index) + args: (0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 69421) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2], + "result": undefined, + "status": "failure", + }, + ] + `) +}) + +test('throws if allowFailure=false & encoding contract function data fails', async () => { + await expect( + readContracts(config, { + allowFailure: false, + contracts: [ + ...contracts, + { + abi: abi.mloot, + functionName: 'ownerOf', + // address is not the mloot contract + address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + args: [10e30], + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + ` + [ContractFunctionExecutionError: The contract function "ownerOf" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "ownerOf", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC + function: ownerOf(uint256 tokenId) + args: (1e+31) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2] + `, + ) +}) + +test('allowFailure=true & encoding contract function data fails', async () => { + expect( + await readContracts(config, { + allowFailure: true, + contracts: [ + ...contracts, + { + abi: abi.mloot, + functionName: 'ownerOf', + // address is not the mloot contract + address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + args: [10e30], + }, + { + abi: abi.mloot, + functionName: 'ownerOf', + // address is not the mloot contract + address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + args: [10e30], + }, + ], + }), + ).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "error": [ContractFunctionExecutionError: The contract function "ownerOf" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "ownerOf", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC + function: ownerOf(uint256 tokenId) + args: (1e+31) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2], + "result": undefined, + "status": "failure", + }, + { + "error": [ContractFunctionExecutionError: The contract function "ownerOf" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "ownerOf", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC + function: ownerOf(uint256 tokenId) + args: (1e+31) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2], + "result": undefined, + "status": "failure", + }, + ] + `) +}) + +test('should throw if allowFailure=false & a contract has no response', async () => { + await expect( + readContracts(config, { + allowFailure: false, + contracts: [ + ...contracts, + { + abi: abi.wagmigotchi, + functionName: 'love', + // address is not the wagmigotchi contract + address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + ` + [ContractFunctionExecutionError: The contract function "love" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "love", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC + function: love(address) + args: (0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2] + `, + ) +}) + +test('allowFailure=true & a contract has no response', async () => { + expect( + await readContracts(config, { + allowFailure: true, + contracts: [ + ...contracts, + { + abi: abi.wagmigotchi, + functionName: 'love', + // address is not the wagmigotchi contract + address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + }), + ).toMatchInlineSnapshot(` + [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "error": [ContractFunctionExecutionError: The contract function "love" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "love", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC + function: love(address) + args: (0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC) + + Docs: https://viem.sh/docs/contract/readContract + Version: viem@2.29.2], + "result": undefined, + "status": "failure", + }, + ] + `) +}) diff --git a/packages/core/src/actions/readContracts.ts b/packages/core/src/actions/readContracts.ts new file mode 100644 index 0000000000..45f2a0cef4 --- /dev/null +++ b/packages/core/src/actions/readContracts.ts @@ -0,0 +1,96 @@ +import type { + ContractFunctionParameters, + MulticallParameters as viem_MulticallParameters, + MulticallReturnType as viem_MulticallReturnType, +} from 'viem' +import { ContractFunctionExecutionError } from 'viem' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import { type MulticallErrorType, multicall } from './multicall.js' +import { type ReadContractErrorType, readContract } from './readContract.js' + +export type ReadContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + config extends Config = Config, +> = viem_MulticallParameters< + contracts, + allowFailure, + { properties: ChainIdParameter } +> + +export type ReadContractsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, +> = viem_MulticallReturnType + +export type ReadContractsErrorType = MulticallErrorType | ReadContractErrorType + +export async function readContracts< + config extends Config, + const contracts extends readonly ContractFunctionParameters[], + allowFailure extends boolean = true, +>( + config: config, + parameters: ReadContractsParameters, +): Promise> { + const { allowFailure = true, blockNumber, blockTag, ...rest } = parameters + const contracts = parameters.contracts as (ContractFunctionParameters & { + chainId?: number | undefined + })[] + + try { + const contractsByChainId: { + [chainId: number]: { + contract: ContractFunctionParameters + index: number + }[] + } = {} + for (const [index, contract] of contracts.entries()) { + const chainId = contract.chainId ?? config.state.chainId + if (!contractsByChainId[chainId]) contractsByChainId[chainId] = [] + contractsByChainId[chainId]?.push({ contract, index }) + } + const promises = () => + Object.entries(contractsByChainId).map(([chainId, contracts]) => + multicall(config, { + ...rest, + allowFailure, + blockNumber, + blockTag, + chainId: Number.parseInt(chainId), + contracts: contracts.map(({ contract }) => contract), + }), + ) + + const multicallResults = (await Promise.all(promises())).flat() + // Reorder the contract results back to the order they were + // provided in. + const resultIndexes = Object.values(contractsByChainId).flatMap( + (contracts) => contracts.map(({ index }) => index), + ) + return multicallResults.reduce((results, result, index) => { + if (results) (results as unknown[])[resultIndexes[index]!] = result + return results + }, [] as unknown[]) as ReadContractsReturnType + } catch (error) { + if (error instanceof ContractFunctionExecutionError) throw error + + const promises = () => + contracts.map((contract) => + readContract(config, { ...contract, blockNumber, blockTag }), + ) + if (allowFailure) + return (await Promise.allSettled(promises())).map((result) => { + if (result.status === 'fulfilled') + return { result: result.value, status: 'success' } + return { error: result.reason, result: undefined, status: 'failure' } + }) as ReadContractsReturnType + + return (await Promise.all(promises())) as ReadContractsReturnType< + contracts, + allowFailure + > + } +} diff --git a/packages/core/src/actions/reconnect.test.ts b/packages/core/src/actions/reconnect.test.ts new file mode 100644 index 0000000000..910d16e304 --- /dev/null +++ b/packages/core/src/actions/reconnect.test.ts @@ -0,0 +1,119 @@ +import { accounts, config, mainnet } from '@wagmi/test' +import { http } from 'viem' +import { afterEach, expect, test, vi } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { createConfig } from '../createConfig.js' +import { createStorage } from '../createStorage.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { reconnect } from './reconnect.js' + +const connector = config._internal.connectors.setup( + mock({ + accounts, + features: { reconnect: true }, + }), +) + +afterEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) + else if (config.state.current) await disconnect(config) +}) + +test('default', async () => { + await expect( + reconnect(config, { connectors: [connector] }), + ).resolves.toStrictEqual([]) + expect(config.state.status).toEqual('disconnected') +}) + +test('parameters: connectors (Connector)', async () => { + await connect(config, { connector }) + await expect( + reconnect(config, { connectors: [connector] }), + ).resolves.toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) + expect(config.state.status).toEqual('connected') +}) + +test('parameters: connectors (CreateConnectorFn)', async () => { + const connector = mock({ + accounts, + features: { reconnect: true }, + }) + await connect(config, { connector }) + await expect( + reconnect(config, { connectors: [connector] }), + ).resolves.toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) + expect(config.state.status).toEqual('connected') +}) + +test("behavior: doesn't reconnect if already reconnecting", async () => { + const previousStatus = config.state.status + config.setState((x) => ({ ...x, status: 'reconnecting' })) + await expect( + reconnect(config, { connectors: [connector] }), + ).resolves.toStrictEqual([]) + config.setState((x) => ({ ...x, status: previousStatus })) +}) + +test('behavior: recovers from invalid state', async () => { + const state = { + 'wagmi.store': JSON.stringify({ + state: { + status: 'connected', // <-- invalid - `status` should not be kept in storage + chainId: 1, + current: '983b8aca245', + }, + version: Number.NaN, // mocked version is `'x.y.z'`, which will get interpreted as `NaN` + }), + } as Record + Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn((key) => state[key] ?? null), + removeItem: vi.fn((key) => state.delete?.[key]), + setItem: vi.fn((key, value) => { + state[key] = value + }), + }, + writable: true, + }) + + const storage = createStorage<{ store: object }>({ + storage: window.localStorage, + }) + + const config = createConfig({ + chains: [mainnet], + storage, + transports: { + [mainnet.id]: http(), + }, + }) + + await reconnect(config, { connectors: [connector] }) + + expect(config.state).toMatchInlineSnapshot(` + { + "chainId": 1, + "connections": Map {}, + "current": null, + "status": "disconnected", + } + `) +}) diff --git a/packages/core/src/actions/reconnect.ts b/packages/core/src/actions/reconnect.ts new file mode 100644 index 0000000000..2234c934d4 --- /dev/null +++ b/packages/core/src/actions/reconnect.ts @@ -0,0 +1,127 @@ +import type { Address } from 'viem' + +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import type { Config, Connection, Connector } from '../createConfig.js' +import type { ErrorType } from '../errors/base.js' +import type { Compute } from '../types/utils.js' + +export type ReconnectParameters = { + /** Connectors to attempt reconnect with */ + connectors?: readonly (CreateConnectorFn | Connector)[] | undefined +} + +export type ReconnectReturnType = Compute[] + +export type ReconnectErrorType = ErrorType + +let isReconnecting = false + +/** https://wagmi.sh/core/api/actions/reconnect */ +export async function reconnect( + config: Config, + parameters: ReconnectParameters = {}, +): Promise { + // If already reconnecting, do nothing + if (isReconnecting) return [] + isReconnecting = true + + config.setState((x) => ({ + ...x, + status: x.current ? 'reconnecting' : 'connecting', + })) + + const connectors: Connector[] = [] + if (parameters.connectors?.length) { + for (const connector_ of parameters.connectors) { + let connector: Connector + // "Register" connector if not already created + if (typeof connector_ === 'function') + connector = config._internal.connectors.setup(connector_) + else connector = connector_ + connectors.push(connector) + } + } else connectors.push(...config.connectors) + + // Try recently-used connectors first + let recentConnectorId: string | null | undefined + try { + recentConnectorId = await config.storage?.getItem('recentConnectorId') + } catch {} + const scores: Record = {} + for (const [, connection] of config.state.connections) { + scores[connection.connector.id] = 1 + } + if (recentConnectorId) scores[recentConnectorId] = 0 + const sorted = + Object.keys(scores).length > 0 + ? // .toSorted() + [...connectors].sort( + (a, b) => (scores[a.id] ?? 10) - (scores[b.id] ?? 10), + ) + : connectors + + // Iterate through each connector and try to connect + let connected = false + const connections: Connection[] = [] + const providers: unknown[] = [] + for (const connector of sorted) { + const provider = await connector.getProvider().catch(() => undefined) + if (!provider) continue + + // If we already have an instance of this connector's provider, + // then we have already checked it (ie. injected connectors can + // share the same `window.ethereum` instance, so we don't want to + // connect to it again). + if (providers.some((x) => x === provider)) continue + + const isAuthorized = await connector.isAuthorized() + if (!isAuthorized) continue + + const data = await connector + .connect({ isReconnecting: true }) + .catch(() => null) + if (!data) continue + + connector.emitter.off('connect', config._internal.events.connect) + connector.emitter.on('change', config._internal.events.change) + connector.emitter.on('disconnect', config._internal.events.disconnect) + + config.setState((x) => { + const connections = new Map(connected ? x.connections : new Map()).set( + connector.uid, + { accounts: data.accounts, chainId: data.chainId, connector }, + ) + return { + ...x, + current: connected ? x.current : connector.uid, + connections, + } + }) + connections.push({ + accounts: data.accounts as readonly [Address, ...Address[]], + chainId: data.chainId, + connector, + }) + providers.push(provider) + connected = true + } + + // Prevent overwriting connected status from race condition + if ( + config.state.status === 'reconnecting' || + config.state.status === 'connecting' + ) { + // If connecting didn't succeed, set to disconnected + if (!connected) + config.setState((x) => ({ + ...x, + connections: new Map(), + current: null, + status: 'disconnected', + })) + else config.setState((x) => ({ ...x, status: 'connected' })) + } + + isReconnecting = false + return connections +} diff --git a/packages/core/src/actions/sendCalls.test.ts b/packages/core/src/actions/sendCalls.test.ts new file mode 100644 index 0000000000..cb25e2b0ba --- /dev/null +++ b/packages/core/src/actions/sendCalls.test.ts @@ -0,0 +1,121 @@ +import { accounts, config } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendCalls } from './sendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).resolves.toMatchInlineSnapshot( + ` + { + "id": "0x5dedb5a4ff8968db37459b52b83cbdc92b01c9c709c9cff26e345ef5cf27f92e", + } + `, + ) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + sendCalls(config, { + calls: [ + { + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) + +test('behavior: nullish account (account filled by wallet)', async () => { + await expect( + sendCalls(config, { + account: null, + connector, + calls: [ + { + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).resolves.toMatchInlineSnapshot( + ` + { + "id": "0x035b56a56a5b2fea10e194bae4c846b415de48a8288c7eb704ba7880edcc29a0", + } + `, + ) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + sendCalls(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + calls: [ + { + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorAccountNotFoundError: Account "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e" not found for connector "Mock Connector". + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/sendCalls.ts b/packages/core/src/actions/sendCalls.ts new file mode 100644 index 0000000000..6139455d60 --- /dev/null +++ b/packages/core/src/actions/sendCalls.ts @@ -0,0 +1,74 @@ +import type { Account, Chain } from 'viem' +import { + type SendCallsErrorType as viem_SendCallsErrorType, + type SendCallsParameters as viem_SendCallsParameters, + type SendCallsReturnType as viem_SendCallsReturnType, + sendCalls as viem_sendCalls, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type SendCallsParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + calls extends readonly unknown[] = readonly unknown[], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Compute< + Omit< + viem_SendCallsParameters, + 'chain' + > & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type SendCallsReturnType = viem_SendCallsReturnType + +export type SendCallsErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SendCallsErrorType + +/** https://wagmi.sh/core/api/actions/sendCalls */ +export async function sendCalls< + const calls extends readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: SendCallsParameters, +): Promise { + const { account, chainId, connector, calls, ...rest } = parameters + + const client = await getConnectorClient(config, { + account, + chainId, + connector, + }) + + return viem_sendCalls(client, { + ...(rest as any), + ...(typeof account !== 'undefined' ? { account } : {}), + calls, + chain: chainId ? { id: chainId } : undefined, + }) +} diff --git a/packages/core/src/actions/sendTransaction.test-d.ts b/packages/core/src/actions/sendTransaction.test-d.ts new file mode 100644 index 0000000000..54ce62a947 --- /dev/null +++ b/packages/core/src/actions/sendTransaction.test-d.ts @@ -0,0 +1,50 @@ +import { http, parseEther } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type SendTransactionParameters, + sendTransaction, +} from './sendTransaction.js' + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = SendTransactionParameters + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result2 = SendTransactionParameters + expectTypeOf().toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + sendTransaction(config, { + chainId: celo.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result3 = SendTransactionParameters + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + sendTransaction(config, { + chainId: mainnet.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/actions/sendTransaction.test.ts b/packages/core/src/actions/sendTransaction.test.ts new file mode 100644 index 0000000000..e263d96670 --- /dev/null +++ b/packages/core/src/actions/sendTransaction.test.ts @@ -0,0 +1,105 @@ +import { config, privateKey, transactionHashRegex } from '@wagmi/test' +import { parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendTransaction } from './sendTransaction.js' + +const connector = config.connectors[0]! + +beforeEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test('default', async () => { + const result = await connect(config, { connector }) + config.state.connections.set(connector.uid, { + ...result, + // Switch up the current account because the default one is running out of funds somewhere + accounts: result.accounts.slice(1) as unknown as typeof result.accounts, + connector, + }) + await expect( + sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.0001'), + }), + ).resolves.toMatch(transactionHashRegex) + await disconnect(config, { connector }) +}) + +test('behavior: connector not connected', async () => { + await connect(config, { connector }) + await expect( + sendTransaction(config, { + connector: config.connectors[1], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.0001'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + sendTransaction(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.0001'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorAccountNotFoundError: Account "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e" not found for connector "Mock Connector". + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) + +test('behavior: value exceeds balance', async () => { + await connect(config, { connector }) + await expect( + sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('99999'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [TransactionExecutionError: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account. + + This error could arise when the account does not have enough funds to: + - pay for the total gas fee, + - pay for the value to send. + + The cost of the transaction is calculated as \`gas * gas fee + value\`, where: + - \`gas\` is the amount of gas needed for transaction to execute, + - \`gas fee\` is the gas fee, + - \`value\` is the amount of ether to send to the recipient. + + Request Arguments: + from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + to: 0xd2135CfB216b74109775236E36d4b433F1DF507B + value: 99999 ETH + + Details: Insufficient funds for gas * price + value + Version: viem@2.29.2] + `) + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const account = privateKeyToAccount(privateKey) + await expect( + sendTransaction(config, { + account, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.000001'), + }), + ).resolves.toMatch(transactionHashRegex) +}) diff --git a/packages/core/src/actions/sendTransaction.ts b/packages/core/src/actions/sendTransaction.ts new file mode 100644 index 0000000000..76bc3a420a --- /dev/null +++ b/packages/core/src/actions/sendTransaction.ts @@ -0,0 +1,86 @@ +import type { + Account, + Chain, + Client, + TransactionRequest, + SendTransactionErrorType as viem_SendTransactionErrorType, + SendTransactionParameters as viem_SendTransactionParameters, + SendTransactionReturnType as viem_SendTransactionReturnType, +} from 'viem' +import { sendTransaction as viem_sendTransaction } from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type SendTransactionParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Compute< + Omit< + viem_SendTransactionParameters, + 'chain' | 'gas' + > & + ChainIdParameter & + ConnectorParameter + > +}[number] & { + /** Gas provided for transaction execution. */ + gas?: TransactionRequest['gas'] | null +} + +export type SendTransactionReturnType = viem_SendTransactionReturnType + +export type SendTransactionErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SendTransactionErrorType + +/** https://wagmi.sh/core/api/actions/sendTransaction */ +export async function sendTransaction< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: SendTransactionParameters, +): Promise { + const { account, chainId, connector, ...rest } = parameters + + let client: Client + if (typeof account === 'object' && account?.type === 'local') + client = config.getClient({ chainId }) + else + client = await getConnectorClient(config, { + account: account ?? undefined, + chainId, + connector, + }) + + const action = getAction(client, viem_sendTransaction, 'sendTransaction') + const hash = await action({ + ...(rest as any), + ...(account ? { account } : {}), + chain: chainId ? { id: chainId } : null, + gas: rest.gas ?? undefined, + }) + + return hash +} diff --git a/packages/core/src/actions/showCallsStatus.test.ts b/packages/core/src/actions/showCallsStatus.test.ts new file mode 100644 index 0000000000..5822242382 --- /dev/null +++ b/packages/core/src/actions/showCallsStatus.test.ts @@ -0,0 +1,36 @@ +import { accounts, config, testClient } from '@wagmi/test' +import { parseEther } from 'viem' +import { test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendCalls } from './sendCalls.js' +import { showCallsStatus } from './showCallsStatus.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const { id } = await sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await testClient.mainnet.mine({ blocks: 1 }) + await showCallsStatus(config, { + id, + }) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/showCallsStatus.ts b/packages/core/src/actions/showCallsStatus.ts new file mode 100644 index 0000000000..e3c6ae067d --- /dev/null +++ b/packages/core/src/actions/showCallsStatus.ts @@ -0,0 +1,27 @@ +import { + type ShowCallsStatusErrorType as viem_ShowCallsStatusErrorType, + type ShowCallsStatusParameters as viem_ShowCallsStatusParameters, + type ShowCallsStatusReturnType as viem_ShowCallsStatusReturnType, + showCallsStatus as viem_showCallsStatus, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ConnectorParameter } from '../types/properties.js' +import { getConnectorClient } from './getConnectorClient.js' + +export type ShowCallsStatusParameters = viem_ShowCallsStatusParameters & + ConnectorParameter + +export type ShowCallsStatusReturnType = viem_ShowCallsStatusReturnType + +export type ShowCallsStatusErrorType = viem_ShowCallsStatusErrorType + +/** https://wagmi.sh/core/api/actions/showCallsStatus */ +export async function showCallsStatus( + config: config, + parameters: ShowCallsStatusParameters, +): Promise { + const { connector, id } = parameters + const client = await getConnectorClient(config, { connector }) + return viem_showCallsStatus(client, { id }) +} diff --git a/packages/core/src/actions/signMessage.test.ts b/packages/core/src/actions/signMessage.test.ts new file mode 100644 index 0000000000..5b87c7d652 --- /dev/null +++ b/packages/core/src/actions/signMessage.test.ts @@ -0,0 +1,67 @@ +import { accounts, config, privateKey } from '@wagmi/test' +import { recoverMessageAddress } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getAccount } from './getAccount.js' +import { signMessage } from './signMessage.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const signature = await signMessage(config, { message: 'foo bar baz' }) + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature, + }), + ).resolves.toEqual(getAccount(config).address) + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const account = privateKeyToAccount(privateKey) + const signature = await signMessage(config, { + account, + message: 'foo bar baz', + }) + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature, + }), + ).resolves.toEqual(account.address) +}) + +test('behavior: user rejected request', async () => { + const connector_ = config._internal.connectors.setup( + mock({ + accounts, + features: { signMessageError: true }, + }), + ) + await connect(config, { connector: connector_ }) + await expect( + signMessage(config, { message: 'foo bar baz' }), + ).rejects.toMatchInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to sign message. + Version: viem@2.29.2] + `) + await disconnect(config, { connector: connector_ }) +}) + +test('behavior: not connected', async () => { + await expect( + signMessage(config, { message: 'foo bar baz' }), + ).rejects.toMatchInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/actions/signMessage.ts b/packages/core/src/actions/signMessage.ts new file mode 100644 index 0000000000..67f0c293a6 --- /dev/null +++ b/packages/core/src/actions/signMessage.ts @@ -0,0 +1,51 @@ +import type { Account, Client } from 'viem' +import { + type SignMessageErrorType as viem_SignMessageErrorType, + type SignMessageParameters as viem_SignMessageParameters, + type SignMessageReturnType as viem_SignMessageReturnType, + signMessage as viem_signMessage, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { ConnectorParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type SignMessageParameters = Compute< + viem_SignMessageParameters & ConnectorParameter +> + +export type SignMessageReturnType = viem_SignMessageReturnType + +export type SignMessageErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SignMessageErrorType + +/** https://wagmi.sh/core/api/actions/signMessage */ +export async function signMessage( + config: Config, + parameters: SignMessageParameters, +): Promise { + const { account, connector, ...rest } = parameters + + let client: Client + if (typeof account === 'object' && account.type === 'local') + client = config.getClient() + else client = await getConnectorClient(config, { account, connector }) + + const action = getAction(client, viem_signMessage, 'signMessage') + return action({ + ...rest, + ...(account ? { account } : {}), + } as viem_SignMessageParameters) +} diff --git a/packages/core/src/actions/signTypedData.test-d.ts b/packages/core/src/actions/signTypedData.test-d.ts new file mode 100644 index 0000000000..a502d26e91 --- /dev/null +++ b/packages/core/src/actions/signTypedData.test-d.ts @@ -0,0 +1,31 @@ +import { config, typedData } from '@wagmi/test' +import { test } from 'vitest' + +import { signTypedData } from './signTypedData.js' + +test('default', async () => { + signTypedData(config, { + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) +}) + +test('domain', async () => { + signTypedData(config, { + primaryType: 'EIP712Domain', + domain: {}, + }) +}) + +test('custom domain', async () => { + signTypedData(config, { + types: { + EIP712Domain: [{ type: 'uint256', name: 'chainId' }], + }, + primaryType: 'EIP712Domain', + domain: { + chainId: 123n, + }, + }) +}) diff --git a/packages/core/src/actions/signTypedData.test.ts b/packages/core/src/actions/signTypedData.test.ts new file mode 100644 index 0000000000..b72ecab8cb --- /dev/null +++ b/packages/core/src/actions/signTypedData.test.ts @@ -0,0 +1,85 @@ +import { accounts, config, privateKey, typedData } from '@wagmi/test' +import { recoverTypedDataAddress } from 'viem' +import { expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getAccount } from './getAccount.js' +import { signTypedData } from './signTypedData.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const signature = await signTypedData(config, { + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature, + }), + ).resolves.toBe(getAccount(config).address) + await disconnect(config, { connector }) +}) + +test('behavior: user rejected request', async () => { + const connector_ = config._internal.connectors.setup( + mock({ + accounts, + features: { signTypedDataError: true }, + }), + ) + await connect(config, { connector: connector_ }) + await expect( + signTypedData(config, { + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }), + ).rejects.toMatchInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to sign typed data. + Version: viem@2.29.2] + `) + await disconnect(config, { connector: connector_ }) +}) + +test('behavior: not connected', async () => { + await expect( + signTypedData(config, { + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }), + ).rejects.toMatchInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) + +test('behavior: local account', async () => { + const account = privateKeyToAccount(privateKey) + const signature = await signTypedData(config, { + account, + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature, + }), + ).resolves.toBe(account.address) +}) diff --git a/packages/core/src/actions/signTypedData.ts b/packages/core/src/actions/signTypedData.ts new file mode 100644 index 0000000000..22cea12662 --- /dev/null +++ b/packages/core/src/actions/signTypedData.ts @@ -0,0 +1,60 @@ +import type { Account, Client, TypedData } from 'viem' +import { + type SignMessageErrorType as viem_SignMessageErrorType, + type SignTypedDataParameters as viem_SignTypedDataParameters, + type SignTypedDataReturnType as viem_SignTypedDataReturnType, + signTypedData as viem_signTypedData, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { ConnectorParameter } from '../types/properties.js' +import type { UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type SignTypedDataParameters< + typedData extends TypedData | Record = TypedData, + primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData, + /// + primaryTypes = typedData extends TypedData ? keyof typedData : string, +> = UnionCompute< + viem_SignTypedDataParameters & + ConnectorParameter +> + +export type SignTypedDataReturnType = viem_SignTypedDataReturnType + +export type SignTypedDataErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SignMessageErrorType + +/** https://wagmi.sh/core/api/actions/signTypedData */ +export async function signTypedData< + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + config: Config, + parameters: SignTypedDataParameters, +): Promise { + const { account, connector, ...rest } = parameters + + let client: Client + if (typeof account === 'object' && account.type === 'local') + client = config.getClient() + else client = await getConnectorClient(config, { account, connector }) + + const action = getAction(client, viem_signTypedData, 'signTypedData') + return action({ + ...rest, + ...(account ? { account } : {}), + } as unknown as viem_SignTypedDataParameters) +} diff --git a/packages/core/src/actions/simulateContract.test-d.ts b/packages/core/src/actions/simulateContract.test-d.ts new file mode 100644 index 0000000000..3f893ff8f8 --- /dev/null +++ b/packages/core/src/actions/simulateContract.test-d.ts @@ -0,0 +1,160 @@ +import { abi, config } from '@wagmi/test' +import { http, type Address } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type SimulateContractParameters, + type SimulateContractReturnType, + simulateContract, +} from './simulateContract.js' + +test('default', async () => { + const response = await simulateContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + + expectTypeOf(response).toMatchTypeOf<{ + result: boolean + request: { + chainId: 1 + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + }>() +}) + +test('chain formatters', async () => { + const config = createConfig({ + chains: [celo, mainnet], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config + > + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + const response = await simulateContract(config, { + account: '0x', + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + + if (response.chainId === celo.id) { + expectTypeOf(response.chainId).toEqualTypeOf(celo.id) + expectTypeOf(response.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + type Result2 = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toMatchTypeOf<{ + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + feeCurrency?: `0x${string}` | undefined + }>() + const response2 = await simulateContract(config, { + chainId: celo.id, + account: '0x', + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + expectTypeOf(response2.chainId).toEqualTypeOf(celo.id) + expectTypeOf(response2.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + + type Result3 = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof mainnet.id + > + expectTypeOf().toMatchTypeOf<{ + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + simulateContract(config, { + chainId: mainnet.id, + account: '0x', + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('SimulateContractParameters', () => { + type Result = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + (typeof config)['chains'][number]['id'] + > + expectTypeOf().toMatchTypeOf<{ + chainId?: (typeof config)['chains'][number]['id'] | undefined + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + }>() +}) + +test('SimulateContractReturnType', () => { + type Result = SimulateContractReturnType< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + (typeof config)['chains'][number]['id'] + > + expectTypeOf().toMatchTypeOf<{ + result: boolean + request: { + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + chainId: (typeof config)['chains'][number]['id'] + } + }>() +}) diff --git a/packages/core/src/actions/simulateContract.test.ts b/packages/core/src/actions/simulateContract.test.ts new file mode 100644 index 0000000000..a52cbd5568 --- /dev/null +++ b/packages/core/src/actions/simulateContract.test.ts @@ -0,0 +1,84 @@ +import { abi, accounts, address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { simulateContract } from './simulateContract.js' + +const connector = config.connectors[0]! + +test('parameters: account', async () => { + await expect( + simulateContract(config, { + account: accounts[0], + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + }), + ).resolves.toMatchInlineSnapshot(` + { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": undefined, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) +}) + +test('parameters: connector', async () => { + await connect(config, { connector }) + + await expect( + simulateContract(config, { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + connector, + }), + ).resolves.toMatchInlineSnapshot(` + { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": undefined, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) + + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/simulateContract.ts b/packages/core/src/actions/simulateContract.ts new file mode 100644 index 0000000000..e5fe5655ff --- /dev/null +++ b/packages/core/src/actions/simulateContract.ts @@ -0,0 +1,166 @@ +import type { + Abi, + Account, + Address, + Chain, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' +import { + type SimulateContractErrorType as viem_SimulateContractErrorType, + type SimulateContractParameters as viem_SimulateContractParameters, + type SimulateContractReturnType as viem_SimulateContractReturnType, + simulateContract as viem_simulateContract, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { + Compute, + PartialBy, + UnionCompute, + UnionStrictOmit, +} from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type SimulateContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + UnionStrictOmit< + viem_SimulateContractParameters< + abi, + functionName, + args, + chains[key], + chains[key], + Account | Address + >, + 'chain' + > + > & + ChainIdParameter & + ConnectorParameter +}[number] + +export type SimulateContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: viem_SimulateContractReturnType< + abi, + functionName, + args, + chains[key], + Account, + chains[key] + > & { + chainId: chains[key]['id'] + request: Compute< + PartialBy< + { chainId: chainId; chain: chains[key] }, + chainId extends config['chains'][number]['id'] ? never : 'chainId' + > + > + } +}[number] + +export type SimulateContractErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SimulateContractErrorType + +/** https://wagmi.sh/core/api/actions/simulateContract */ +export async function simulateContract< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'] | undefined = undefined, +>( + config: config, + parameters: SimulateContractParameters< + abi, + functionName, + args, + config, + chainId + >, +): Promise< + SimulateContractReturnType +> { + const { abi, chainId, connector, ...rest } = + parameters as SimulateContractParameters + + let account: Address | Account + if (parameters.account) account = parameters.account + else { + const connectorClient = await getConnectorClient(config, { + chainId, + connector, + }) + account = connectorClient.account + } + + const client = config.getClient({ chainId }) + const action = getAction(client, viem_simulateContract, 'simulateContract') + const { result, request } = await action({ ...rest, abi, account }) + + return { + chainId: client.chain.id, + result, + request: { ...request, chainId }, + } as unknown as SimulateContractReturnType< + abi, + functionName, + args, + config, + chainId + > +} diff --git a/packages/core/src/actions/switchAccount.test.ts b/packages/core/src/actions/switchAccount.test.ts new file mode 100644 index 0000000000..97d0e84b37 --- /dev/null +++ b/packages/core/src/actions/switchAccount.test.ts @@ -0,0 +1,32 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getAccount } from './getAccount.js' +import { switchAccount } from './switchAccount.js' + +const connector1 = config.connectors[0]! +const connector2 = config.connectors[1]! + +test('default', async () => { + await connect(config, { connector: connector2 }) + await connect(config, { connector: connector1 }) + + const address1 = getAccount(config).address + + await switchAccount(config, { connector: connector2 }) + + const address2 = getAccount(config).address + expect(address2).toBeDefined() + expect(address1).not.toBe(address2) + + await switchAccount(config, { connector: connector1 }) + + const address3 = getAccount(config).address + expect(address3).toBeDefined() + expect(address1).toBe(address3) + + await disconnect(config, { connector: connector1 }) + await disconnect(config, { connector: connector2 }) +}) diff --git a/packages/core/src/actions/switchAccount.ts b/packages/core/src/actions/switchAccount.ts new file mode 100644 index 0000000000..ec577a57c7 --- /dev/null +++ b/packages/core/src/actions/switchAccount.ts @@ -0,0 +1,45 @@ +import type { Address } from 'viem' + +import type { Config, Connector } from '../createConfig.js' +import type { BaseError, ErrorType } from '../errors/base.js' +import { + ConnectorNotConnectedError, + type ConnectorNotConnectedErrorType, +} from '../errors/config.js' + +export type SwitchAccountParameters = { + connector: Connector +} + +export type SwitchAccountReturnType = { + accounts: readonly [Address, ...Address[]] + chainId: + | config['chains'][number]['id'] + | (number extends config['chains'][number]['id'] ? number : number & {}) +} + +export type SwitchAccountErrorType = + | ConnectorNotConnectedErrorType + | BaseError + | ErrorType + +/** https://wagmi.sh/core/api/actions/switchAccount */ +export async function switchAccount( + config: config, + parameters: SwitchAccountParameters, +): Promise> { + const { connector } = parameters + + const connection = config.state.connections.get(connector.uid) + if (!connection) throw new ConnectorNotConnectedError() + + await config.storage?.setItem('recentConnectorId', connector.id) + config.setState((x) => ({ + ...x, + current: connector.uid, + })) + return { + accounts: connection.accounts, + chainId: connection.chainId, + } +} diff --git a/packages/core/src/actions/switchChain.test.ts b/packages/core/src/actions/switchChain.test.ts new file mode 100644 index 0000000000..466d77824f --- /dev/null +++ b/packages/core/src/actions/switchChain.test.ts @@ -0,0 +1,73 @@ +import { accounts, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getAccount } from './getAccount.js' +import { switchChain } from './switchChain.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const chainId1 = getAccount(config).chainId + + await switchChain(config, { chainId: chain.mainnet2.id }) + + const chainId2 = getAccount(config).chainId + expect(chainId2).toBeDefined() + expect(chainId1).not.toBe(chainId2) + + await switchChain(config, { chainId: chain.mainnet.id }) + + const chainId3 = getAccount(config).chainId + expect(chainId3).toBeDefined() + expect(chainId1).toBe(chainId3) + + await disconnect(config, { connector }) +}) + +test('behavior: user rejected request', async () => { + const connector_ = config._internal.connectors.setup( + mock({ + accounts, + features: { switchChainError: true }, + }), + ) + await connect(config, { connector: connector_ }) + await expect( + switchChain(config, { chainId: chain.mainnet.id }), + ).rejects.toMatchInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to switch chain. + Version: viem@2.29.2] + `) + await disconnect(config, { connector: connector_ }) +}) + +test('behavior: not supported', async () => { + const { switchChain: _, ...connector_ } = config._internal.connectors.setup( + mock({ accounts }), + ) + await connect(config, { connector: connector_ }) + await expect( + switchChain(config, { chainId: chain.mainnet.id }), + ).rejects.toMatchInlineSnapshot(` + [SwitchChainNotSupportedError: "Mock Connector" does not support programmatic chain switching. + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector: connector_ }) +}) + +test('behavior: not connected', async () => { + const chainId = config.state.chainId + expect(config.state.chainId).toMatchInlineSnapshot('1') + await switchChain(config, { chainId: chain.mainnet2.id }) + expect(config.state.chainId).toMatchInlineSnapshot('456') + await switchChain(config, { chainId }) + expect(config.state.chainId).toMatchInlineSnapshot('1') +}) diff --git a/packages/core/src/actions/switchChain.ts b/packages/core/src/actions/switchChain.ts new file mode 100644 index 0000000000..59f337d141 --- /dev/null +++ b/packages/core/src/actions/switchChain.ts @@ -0,0 +1,83 @@ +import type { + AddEthereumChainParameter, + UserRejectedRequestErrorType, + SwitchChainErrorType as viem_SwitchChainErrorType, +} from 'viem' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import { + ChainNotConfiguredError, + type ChainNotConfiguredErrorType, +} from '../errors/config.js' +import { + type ProviderNotFoundErrorType, + SwitchChainNotSupportedError, + type SwitchChainNotSupportedErrorType, +} from '../errors/connector.js' +import type { ConnectorParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' + +export type SwitchChainParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + ConnectorParameter & { + chainId: chainId | config['chains'][number]['id'] + addEthereumChainParameter?: + | Compute>> + | undefined + } +> + +export type SwitchChainReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Extract< + config['chains'][number], + { id: Config extends config ? number : chainId } +> + +export type SwitchChainErrorType = + | SwitchChainNotSupportedErrorType + | ChainNotConfiguredErrorType + // connector.switchChain() + | ProviderNotFoundErrorType + | UserRejectedRequestErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_SwitchChainErrorType + +/** https://wagmi.sh/core/api/actions/switchChain */ +export async function switchChain< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: SwitchChainParameters, +): Promise> { + const { addEthereumChainParameter, chainId } = parameters + + const connection = config.state.connections.get( + parameters.connector?.uid ?? config.state.current!, + ) + if (connection) { + const connector = connection.connector + if (!connector.switchChain) + throw new SwitchChainNotSupportedError({ connector }) + const chain = await connector.switchChain({ + addEthereumChainParameter, + chainId, + }) + return chain as SwitchChainReturnType + } + + const chain = config.chains.find((x) => x.id === chainId) + if (!chain) throw new ChainNotConfiguredError() + config.setState((x) => ({ ...x, chainId })) + return chain as SwitchChainReturnType +} diff --git a/packages/core/src/actions/verifyMessage.test.ts b/packages/core/src/actions/verifyMessage.test.ts new file mode 100644 index 0000000000..f2766cbea4 --- /dev/null +++ b/packages/core/src/actions/verifyMessage.test.ts @@ -0,0 +1,72 @@ +import { accounts, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { verifyMessage } from './verifyMessage.js' + +const eoaAddress = accounts[0] +const smartAccountAddress = '0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145' + +test('smart account: valid signature', async () => { + expect( + await verifyMessage(config, { + address: smartAccountAddress, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toBe(true) +}) + +test('smart account: invalid signature', async () => { + expect( + await verifyMessage(config, { + address: smartAccountAddress, + message: 'This is a test message for viem!', + signature: '0xdead', + }), + ).toBe(false) +}) + +test('smart account: account not deployed', async () => { + expect( + await verifyMessage(config, { + blockNumber: 1234567890n, + address: smartAccountAddress, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toBe(false) +}) + +test('eoa: valid signature', async () => { + expect( + await verifyMessage(config, { + address: eoaAddress, + message: 'This is a test message for viem!', + signature: + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b', + }), + ).toBe(true) +}) + +test('eoa: invalid signature', async () => { + expect( + await verifyMessage(config, { + address: eoaAddress, + message: 'This is a test message for viem!', + signature: '0xdead', + }), + ).toBe(false) +}) + +test('eoa: raw message', async () => { + expect( + await verifyMessage(config, { + address: eoaAddress, + message: { raw: '0x68656c6c6f20776f726c64' }, + signature: + '0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b', + }), + ).toBe(true) +}) diff --git a/packages/core/src/actions/verifyMessage.ts b/packages/core/src/actions/verifyMessage.ts new file mode 100644 index 0000000000..851f8589fa --- /dev/null +++ b/packages/core/src/actions/verifyMessage.ts @@ -0,0 +1,30 @@ +import { + type VerifyMessageErrorType as viem_VerifyMessageErrorType, + type VerifyMessageParameters as viem_VerifyMessageParameters, + type VerifyMessageReturnType as viem_VerifyMessageReturnType, + verifyMessage as viem_verifyMessage, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type VerifyMessageParameters = Compute< + viem_VerifyMessageParameters & ChainIdParameter +> + +export type VerifyMessageReturnType = viem_VerifyMessageReturnType + +export type VerifyMessageErrorType = viem_VerifyMessageErrorType + +/** https://wagmi.sh/core/api/actions/verifyMessage */ +export async function verifyMessage( + config: config, + parameters: VerifyMessageParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_verifyMessage, 'verifyMessage') + return action(rest) +} diff --git a/packages/core/src/actions/verifyTypedData.test.ts b/packages/core/src/actions/verifyTypedData.test.ts new file mode 100644 index 0000000000..c4b98c68dc --- /dev/null +++ b/packages/core/src/actions/verifyTypedData.test.ts @@ -0,0 +1,42 @@ +import { config, typedData } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { verifyTypedData } from './verifyTypedData.js' + +const smartAccountAddress = '0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145' +const notDeployedAddress = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' + +test('valid signature', async () => { + expect( + await verifyTypedData(config, { + ...typedData.basic, + primaryType: 'Mail', + address: smartAccountAddress, + signature: + '0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c', + }), + ).toBe(true) +}) + +test('invalid signature', async () => { + expect( + await verifyTypedData(config, { + ...typedData.basic, + primaryType: 'Mail', + address: smartAccountAddress, + signature: '0xdead', + }), + ).toBe(false) +}) + +test('account not deployed', async () => { + expect( + await verifyTypedData(config, { + ...typedData.basic, + primaryType: 'Mail', + address: notDeployedAddress, + signature: + '0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c', + }), + ).toBe(false) +}) diff --git a/packages/core/src/actions/verifyTypedData.ts b/packages/core/src/actions/verifyTypedData.ts new file mode 100644 index 0000000000..4fac5463f5 --- /dev/null +++ b/packages/core/src/actions/verifyTypedData.ts @@ -0,0 +1,40 @@ +import type { TypedData } from 'viem' +import { + type VerifyTypedDataErrorType as viem_VerifyTypedDataErrorType, + type VerifyTypedDataParameters as viem_VerifyTypedDataParameters, + type VerifyTypedDataReturnType as viem_VerifyTypedDataReturnType, + verifyTypedData as viem_verifyTypedData, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type VerifyTypedDataParameters< + typedData extends TypedData | Record = TypedData, + primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData, + config extends Config = Config, +> = Compute< + viem_VerifyTypedDataParameters & + ChainIdParameter +> + +export type VerifyTypedDataReturnType = viem_VerifyTypedDataReturnType + +export type VerifyTypedDataErrorType = viem_VerifyTypedDataErrorType + +/** https://wagmi.sh/core/api/actions/verifyTypedData */ +export async function verifyTypedData< + config extends Config, + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + config: config, + parameters: VerifyTypedDataParameters, +): Promise { + const { chainId, ...rest } = parameters + const client = config.getClient({ chainId }) + const action = getAction(client, viem_verifyTypedData, 'verifyTypedData') + return action(rest as viem_VerifyTypedDataParameters) +} diff --git a/packages/core/src/actions/waitForCallsStatus.test.ts b/packages/core/src/actions/waitForCallsStatus.test.ts new file mode 100644 index 0000000000..338fe02616 --- /dev/null +++ b/packages/core/src/actions/waitForCallsStatus.test.ts @@ -0,0 +1,77 @@ +import { accounts, config, testClient, wait } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendCalls } from './sendCalls.js' +import { waitForCallsStatus } from './waitForCallsStatus.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { id } = await sendCalls(config, { + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + + const [{ receipts, status }] = await Promise.all([ + waitForCallsStatus(config, { + id, + }), + (async () => { + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + })(), + ]) + + expect(status).toBe('success') + expect( + receipts?.map((x) => ({ ...x, blockHash: undefined })), + ).toMatchInlineSnapshot( + ` + [ + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0x13c53b2d4d9da424835525349cd66e553330f323d6fb19458b801ae1f7989a41", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xd8397b3e82b061c26a0c2093f1ceca0c3662a512614f7d6370349e89d0eea007", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x4d26e346593d9ea265bb164b115e89aa92df43b0b8778ac75d4ad28e2a22b101", + }, + ] + `, + ) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/waitForCallsStatus.ts b/packages/core/src/actions/waitForCallsStatus.ts new file mode 100644 index 0000000000..a1c5764d32 --- /dev/null +++ b/packages/core/src/actions/waitForCallsStatus.ts @@ -0,0 +1,27 @@ +import { + type WaitForCallsStatusErrorType as viem_WaitForCallsStatusErrorType, + type WaitForCallsStatusParameters as viem_WaitForCallsStatusParameters, + type WaitForCallsStatusReturnType as viem_WaitForCallsStatusReturnType, + waitForCallsStatus as viem_waitForCallsStatus, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { ConnectorParameter } from '../types/properties.js' +import { getConnectorClient } from './getConnectorClient.js' + +export type WaitForCallsStatusParameters = viem_WaitForCallsStatusParameters & + ConnectorParameter + +export type WaitForCallsStatusReturnType = viem_WaitForCallsStatusReturnType + +export type WaitForCallsStatusErrorType = viem_WaitForCallsStatusErrorType + +/** https://wagmi.sh/core/api/actions/waitForCallsStatus */ +export async function waitForCallsStatus( + config: config, + parameters: WaitForCallsStatusParameters, +): Promise { + const { connector } = parameters + const client = await getConnectorClient(config, { connector }) + return viem_waitForCallsStatus(client, parameters) +} diff --git a/packages/core/src/actions/waitForTransactionReceipt.test-d.ts b/packages/core/src/actions/waitForTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..0cce58d1e4 --- /dev/null +++ b/packages/core/src/actions/waitForTransactionReceipt.test-d.ts @@ -0,0 +1,36 @@ +import { http } from 'viem' +import { mainnet, zkSync } from 'viem/chains' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' + +test('chain formatters', async () => { + const config = createConfig({ + chains: [mainnet, zkSync], + transports: { [mainnet.id]: http(), [zkSync.id]: http() }, + }) + const result = await waitForTransactionReceipt(config, { hash: '0x123' }) + if (result.chainId === zkSync.id) { + expectTypeOf(result.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.logs).toEqualTypeOf() + expectTypeOf(result.l2ToL1Logs).toEqualTypeOf() + } +}) + +test('chainId', async () => { + const config = createConfig({ + chains: [zkSync], + transports: { [zkSync.id]: http() }, + }) + const result = await waitForTransactionReceipt(config, { + hash: '0x123', + chainId: zkSync.id, + }) + expectTypeOf(result.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.logs).toEqualTypeOf() + expectTypeOf(result.l2ToL1Logs).toEqualTypeOf() +}) diff --git a/packages/core/src/actions/waitForTransactionReceipt.test.ts b/packages/core/src/actions/waitForTransactionReceipt.test.ts new file mode 100644 index 0000000000..c060e82665 --- /dev/null +++ b/packages/core/src/actions/waitForTransactionReceipt.test.ts @@ -0,0 +1,58 @@ +import { config, testClient, wait } from '@wagmi/test' +import { parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendTransaction } from './sendTransaction.js' +import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' + +const connector = config.connectors[0]! + +beforeEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test('default', async () => { + await connect(config, { connector }) + + const hash = await sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + await expect( + waitForTransactionReceipt(config, { hash }), + ).resolves.toMatchObject({ + chainId: 1, + transactionHash: hash, + }) + + await disconnect(config, { connector }) +}) + +test('behavior: transaction reverted', async () => { + await expect( + waitForTransactionReceipt(config, { + hash: '0x745367f76807d411b7fa4c3a552a62e3e45303ef40145fff04d84b867c2575d3', + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: Execution reverted with reason: PartyBid::claim: contribution already claimed. + + Raw Call Arguments: + to: 0xf1332f21487e74612ed3a0fb36da729b73f1ae19 + value: 0 ETH + data: 0x1e83409a000000000000000000000000a0cf798816d4b9b9866b5330eea46a18382f251e + gas: 128730 + maxFeePerGas: 43.307900987 gwei + maxPriorityFeePerGas: 1.5 gwei + nonce: 43 + + Details: execution reverted: PartyBid::claim: contribution already claimed + Version: viem@2.29.2] + `) +}) diff --git a/packages/core/src/actions/waitForTransactionReceipt.ts b/packages/core/src/actions/waitForTransactionReceipt.ts new file mode 100644 index 0000000000..5ac8fcdb20 --- /dev/null +++ b/packages/core/src/actions/waitForTransactionReceipt.ts @@ -0,0 +1,86 @@ +import type { Chain } from 'viem' +import { hexToString } from 'viem' +import { + call, + getTransaction, + type WaitForTransactionReceiptErrorType as viem_WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptParameters as viem_WaitForTransactionReceiptParameters, + type WaitForTransactionReceiptReturnType as viem_WaitForTransactionReceiptReturnType, + waitForTransactionReceipt as viem_waitForTransactionReceipt, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { Compute, IsNarrowable } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type WaitForTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + viem_WaitForTransactionReceiptParameters & ChainIdParameter +> + +export type WaitForTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = Compute< + { + [key in keyof chains]: viem_WaitForTransactionReceiptReturnType< + IsNarrowable extends true ? chains[key] : undefined + > & { chainId: chains[key]['id'] } + }[number] +> + +export type WaitForTransactionReceiptErrorType = + viem_WaitForTransactionReceiptErrorType + +export async function waitForTransactionReceipt< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WaitForTransactionReceiptParameters, +): Promise> { + const { chainId, timeout = 0, ...rest } = parameters + + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_waitForTransactionReceipt, + 'waitForTransactionReceipt', + ) + const receipt = await action({ ...rest, timeout }) + + if (receipt.status === 'reverted') { + const action_getTransaction = getAction( + client, + getTransaction, + 'getTransaction', + ) + const txn = await action_getTransaction({ hash: receipt.transactionHash }) + const action_call = getAction(client, call, 'call') + const code = await action_call({ + ...(txn as any), + data: txn.input, + gasPrice: txn.type !== 'eip1559' ? txn.gasPrice : undefined, + maxFeePerGas: txn.type === 'eip1559' ? txn.maxFeePerGas : undefined, + maxPriorityFeePerGas: + txn.type === 'eip1559' ? txn.maxPriorityFeePerGas : undefined, + }) + const reason = code?.data + ? hexToString(`0x${code.data.substring(138)}`) + : 'unknown reason' + throw new Error(reason) + } + + return { + ...receipt, + chainId: client.chain.id, + } as WaitForTransactionReceiptReturnType +} diff --git a/packages/core/src/actions/watchAccount.test.ts b/packages/core/src/actions/watchAccount.test.ts new file mode 100644 index 0000000000..c803165888 --- /dev/null +++ b/packages/core/src/actions/watchAccount.test.ts @@ -0,0 +1,38 @@ +import { config } from '@wagmi/test' +import type { Address } from 'viem' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { watchAccount } from './watchAccount.js' + +test('default', async () => { + const accounts: { address: Address | undefined; status: string }[] = [] + const unwatch = watchAccount(config, { + onChange(data) { + accounts.push({ address: data.address, status: data.status }) + }, + }) + + await connect(config, { connector: config.connectors[0]! }) + await disconnect(config) + + expect(accounts).toMatchInlineSnapshot(` + [ + { + "address": undefined, + "status": "connecting", + }, + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "status": "connected", + }, + { + "address": undefined, + "status": "disconnected", + }, + ] + `) + + unwatch() +}) diff --git a/packages/core/src/actions/watchAccount.ts b/packages/core/src/actions/watchAccount.ts new file mode 100644 index 0000000000..dfa8ae4908 --- /dev/null +++ b/packages/core/src/actions/watchAccount.ts @@ -0,0 +1,33 @@ +import type { Config } from '../createConfig.js' +import { deepEqual } from '../utils/deepEqual.js' +import { type GetAccountReturnType, getAccount } from './getAccount.js' + +export type WatchAccountParameters = { + onChange( + account: GetAccountReturnType, + prevAccount: GetAccountReturnType, + ): void +} + +export type WatchAccountReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchAccount */ +export function watchAccount( + config: config, + parameters: WatchAccountParameters, +): WatchAccountReturnType { + const { onChange } = parameters + + return config.subscribe(() => getAccount(config), onChange, { + equalityFn(a, b) { + const { connector: aConnector, ...aRest } = a + const { connector: bConnector, ...bRest } = b + return ( + deepEqual(aRest, bRest) && + // check connector separately + aConnector?.id === bConnector?.id && + aConnector?.uid === bConnector?.uid + ) + }, + }) +} diff --git a/packages/core/src/actions/watchAsset.test.ts b/packages/core/src/actions/watchAsset.test.ts new file mode 100644 index 0000000000..6d4cd86b58 --- /dev/null +++ b/packages/core/src/actions/watchAsset.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { watchAsset } from './watchAsset.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + watchAsset(config, { + type: 'ERC20', + options: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'NULL', + decimals: 18, + }, + }), + ).resolves.toMatchInlineSnapshot('true') + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/watchAsset.ts b/packages/core/src/actions/watchAsset.ts new file mode 100644 index 0000000000..cd5690bf57 --- /dev/null +++ b/packages/core/src/actions/watchAsset.ts @@ -0,0 +1,44 @@ +import { + type WatchAssetErrorType as viem_WatchAssetErrorType, + type WatchAssetParameters as viem_WatchAssetParameters, + type WatchAssetReturnType as viem_WatchAssetReturnType, + watchAsset as viem_watchAsset, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { ConnectorParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type WatchAssetParameters = Compute< + viem_WatchAssetParameters & ConnectorParameter +> + +export type WatchAssetReturnType = viem_WatchAssetReturnType + +export type WatchAssetErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_WatchAssetErrorType + +/** https://wagmi.sh/core/api/actions/watchAsset */ +export async function watchAsset( + config: Config, + parameters: WatchAssetParameters, +): Promise { + const { connector, ...rest } = parameters + + const client = await getConnectorClient(config, { connector }) + + const action = getAction(client, viem_watchAsset, 'watchAsset') + return action(rest as viem_WatchAssetParameters) +} diff --git a/packages/core/src/actions/watchBlockNumber.test-d.ts b/packages/core/src/actions/watchBlockNumber.test-d.ts new file mode 100644 index 0000000000..ae63ed8af6 --- /dev/null +++ b/packages/core/src/actions/watchBlockNumber.test-d.ts @@ -0,0 +1,56 @@ +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type WatchBlockNumberParameters, + watchBlockNumber, +} from './watchBlockNumber.js' + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = WatchBlockNumberParameters< + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchBlockNumber(config, { + poll: false, + onBlockNumber() {}, + }) + + type Result2 = WatchBlockNumberParameters + expectTypeOf().toEqualTypeOf() + watchBlockNumber(config, { + chainId: mainnet.id, + poll: true, + onBlockNumber() {}, + }) + watchBlockNumber(config, { + chainId: mainnet.id, + // @ts-expect-error + poll: false, + onBlockNumber() {}, + }) + + type Result3 = WatchBlockNumberParameters + expectTypeOf().toEqualTypeOf() + watchBlockNumber(config, { + chainId: optimism.id, + poll: true, + onBlockNumber() {}, + }) + watchBlockNumber(config, { + chainId: optimism.id, + poll: false, + onBlockNumber() {}, + }) +}) diff --git a/packages/core/src/actions/watchBlockNumber.test.ts b/packages/core/src/actions/watchBlockNumber.test.ts new file mode 100644 index 0000000000..0a4299db58 --- /dev/null +++ b/packages/core/src/actions/watchBlockNumber.test.ts @@ -0,0 +1,27 @@ +import { config, testClient, wait } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { watchBlockNumber } from './watchBlockNumber.js' + +test('default', async () => { + const blockNumbers: bigint[] = [] + const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + blockNumbers.push(blockNumber) + }, + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blockNumbers.length).toBe(3) + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) + + unwatch() +}) diff --git a/packages/core/src/actions/watchBlockNumber.ts b/packages/core/src/actions/watchBlockNumber.ts new file mode 100644 index 0000000000..712849080a --- /dev/null +++ b/packages/core/src/actions/watchBlockNumber.ts @@ -0,0 +1,78 @@ +import { + type WatchBlockNumberParameters as viem_WatchBlockNumberParameters, + type WatchBlockNumberReturnType as viem_WatchBlockNumberReturnType, + watchBlockNumber as viem_watchBlockNumber, +} from 'viem/actions' + +import type { Chain, Transport, WebSocketTransport } from 'viem' +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + SyncConnectedChainParameter, +} from '../types/properties.js' +import type { UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type WatchBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + viem_WatchBlockNumberParameters< + config['_internal']['transports'][chains[key]['id']] extends infer transport extends + Transport + ? Transport extends transport + ? WebSocketTransport + : transport + : WebSocketTransport + > & + ChainIdParameter & + SyncConnectedChainParameter + > +}[number] + +export type WatchBlockNumberReturnType = viem_WatchBlockNumberReturnType + +// TODO: wrap in viem's `observe` to avoid duplicate invocations. +/** https://wagmi.sh/core/api/actions/watchBlockNumber */ +export function watchBlockNumber< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + config: config, + parameters: WatchBlockNumberParameters, +): WatchBlockNumberReturnType { + const { syncConnectedChain = config._internal.syncConnectedChain, ...rest } = + parameters as WatchBlockNumberParameters + + let unwatch: WatchBlockNumberReturnType | undefined + const listener = (chainId: number | undefined) => { + if (unwatch) unwatch() + + const client = config.getClient({ chainId }) + const action = getAction(client, viem_watchBlockNumber, 'watchBlockNumber') + unwatch = action(rest as viem_WatchBlockNumberParameters) + return unwatch + } + + // set up listener for block number changes + const unlisten = listener(parameters.chainId) + + // set up subscriber for connected chain changes + let unsubscribe: (() => void) | undefined + if (syncConnectedChain && !parameters.chainId) + unsubscribe = config.subscribe( + ({ chainId }) => chainId, + async (chainId) => listener(chainId), + ) + + return () => { + unlisten?.() + unsubscribe?.() + } +} diff --git a/packages/core/src/actions/watchBlocks.test-d.ts b/packages/core/src/actions/watchBlocks.test-d.ts new file mode 100644 index 0000000000..1899dcb1ae --- /dev/null +++ b/packages/core/src/actions/watchBlocks.test-d.ts @@ -0,0 +1,59 @@ +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { type WatchBlocksParameters, watchBlocks } from './watchBlocks.js' + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = WatchBlocksParameters< + false, + 'latest', + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchBlocks(config, { + poll: false, + onBlock() {}, + }) + + type Result2 = WatchBlocksParameters< + false, + 'latest', + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + watchBlocks(config, { + chainId: mainnet.id, + poll: true, + onBlock() {}, + }) + + type Result3 = WatchBlocksParameters< + false, + 'latest', + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchBlocks(config, { + chainId: optimism.id, + poll: true, + onBlock() {}, + }) + watchBlocks(config, { + chainId: optimism.id, + poll: false, + onBlock() {}, + }) +}) diff --git a/packages/core/src/actions/watchBlocks.test.ts b/packages/core/src/actions/watchBlocks.test.ts new file mode 100644 index 0000000000..caf4c0dc61 --- /dev/null +++ b/packages/core/src/actions/watchBlocks.test.ts @@ -0,0 +1,30 @@ +import { config, testClient, wait } from '@wagmi/test' +import { expect, test } from 'vitest' + +import type { Block } from 'viem' +import { watchBlocks } from './watchBlocks.js' + +test('default', async () => { + const blocks: Block[] = [] + const unwatch = watchBlocks(config, { + onBlock(block) { + blocks.push(block) + }, + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blocks.length).toBe(3) + expect(blocks.map((block) => block.number! - blocks[0]?.number!)).toEqual([ + 0n, + 1n, + 2n, + ]) + + unwatch() +}) diff --git a/packages/core/src/actions/watchBlocks.ts b/packages/core/src/actions/watchBlocks.ts new file mode 100644 index 0000000000..c6f3225dc9 --- /dev/null +++ b/packages/core/src/actions/watchBlocks.ts @@ -0,0 +1,90 @@ +import { + type WatchBlocksParameters as viem_WatchBlocksParameters, + type WatchBlocksReturnType as viem_WatchBlocksReturnType, + watchBlocks as viem_watchBlocks, +} from 'viem/actions' + +import type { BlockTag, Chain, Transport, WebSocketTransport } from 'viem' +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + SyncConnectedChainParameter, +} from '../types/properties.js' +import type { IsNarrowable, UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type WatchBlocksParameters< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + viem_WatchBlocksParameters< + config['_internal']['transports'][chains[key]['id']] extends infer transport extends + Transport + ? Transport extends transport + ? WebSocketTransport + : transport + : WebSocketTransport, + IsNarrowable extends true ? chains[key] : undefined, + includeTransactions, + blockTag + > & + ChainIdParameter & + SyncConnectedChainParameter + > +}[number] + +export type WatchBlocksReturnType = viem_WatchBlocksReturnType + +// TODO: wrap in viem's `observe` to avoid duplicate invocations. +/** https://wagmi.sh/core/actions/watchBlocks */ +export function watchBlocks< + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', +>( + config: config, + parameters: WatchBlocksParameters< + includeTransactions, + blockTag, + config, + chainId + >, +): WatchBlocksReturnType { + const { syncConnectedChain = config._internal.syncConnectedChain, ...rest } = + parameters as WatchBlocksParameters + + let unwatch: WatchBlocksReturnType | undefined + const listener = (chainId: number | undefined) => { + if (unwatch) unwatch() + + const client = config.getClient({ chainId }) + const action = getAction(client, viem_watchBlocks, 'watchBlocks') + unwatch = action(rest as viem_WatchBlocksParameters) + return unwatch + } + + // set up listener for block number changes + const unlisten = listener(parameters.chainId) + + // set up subscriber for connected chain changes + let unsubscribe: (() => void) | undefined + if (syncConnectedChain && !parameters.chainId) + unsubscribe = config.subscribe( + ({ chainId }) => chainId, + async (chainId) => listener(chainId), + ) + + return () => { + unlisten?.() + unsubscribe?.() + } +} diff --git a/packages/core/src/actions/watchChainId.test.ts b/packages/core/src/actions/watchChainId.test.ts new file mode 100644 index 0000000000..9e27ba7edd --- /dev/null +++ b/packages/core/src/actions/watchChainId.test.ts @@ -0,0 +1,26 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { watchChainId } from './watchChainId.js' + +test('default', async () => { + const chainIds: number[] = [] + const unwatch = watchChainId(config, { + onChange(chainId) { + chainIds.push(chainId) + }, + }) + config.setState((x) => ({ ...x, chainId: chain.mainnet2.id })) + config.setState((x) => ({ ...x, chainId: chain.mainnet.id })) + config.setState((x) => ({ ...x, chainId: chain.mainnet2.id })) + + expect(chainIds).toMatchInlineSnapshot(` + [ + 456, + 1, + 456, + ] + `) + + unwatch() +}) diff --git a/packages/core/src/actions/watchChainId.ts b/packages/core/src/actions/watchChainId.ts new file mode 100644 index 0000000000..e3d4f010a4 --- /dev/null +++ b/packages/core/src/actions/watchChainId.ts @@ -0,0 +1,20 @@ +import type { Config } from '../createConfig.js' +import type { GetChainIdReturnType } from './getChainId.js' + +export type WatchChainIdParameters = { + onChange( + chainId: GetChainIdReturnType, + prevChainId: GetChainIdReturnType, + ): void +} + +export type WatchChainIdReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchChainId */ +export function watchChainId( + config: config, + parameters: WatchChainIdParameters, +): WatchChainIdReturnType { + const { onChange } = parameters + return config.subscribe((state) => state.chainId, onChange) +} diff --git a/packages/core/src/actions/watchChains.test.ts b/packages/core/src/actions/watchChains.test.ts new file mode 100644 index 0000000000..d7d586a833 --- /dev/null +++ b/packages/core/src/actions/watchChains.test.ts @@ -0,0 +1,37 @@ +import { chain, config } from '@wagmi/test' +import type { Chain } from 'viem' +import { expect, test } from 'vitest' + +import { watchChains } from './watchChains.js' + +test('default', async () => { + let chains: readonly [Chain, ...Chain[]] = config.chains + const unwatch = watchChains(config, { + onChange(nextChains) { + chains = nextChains + }, + }) + + config._internal.chains.setState([chain.mainnet, chain.mainnet2]) + expect(chains.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + ] + `) + + config._internal.chains.setState([ + chain.mainnet, + chain.mainnet2, + chain.optimism, + ]) + expect(chains.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + 10, + ] + `) + + unwatch() +}) diff --git a/packages/core/src/actions/watchChains.ts b/packages/core/src/actions/watchChains.ts new file mode 100644 index 0000000000..db5c496d5a --- /dev/null +++ b/packages/core/src/actions/watchChains.ts @@ -0,0 +1,29 @@ +import type { Config } from '../createConfig.js' +import type { GetChainsReturnType } from './getChains.js' + +export type WatchChainsParameters = { + onChange( + chains: GetChainsReturnType, + prevChains: GetChainsReturnType, + ): void +} + +export type WatchChainsReturnType = () => void + +/** + * @internal + * We don't expose this because as far as consumers know, you can't chainge (lol) `config.chains` at runtime. + * Setting `config.chains` via `config._internal.chains.setState(...)` is an extremely advanced use case that's not worth documenting or supporting in the public API at this time. + */ +export function watchChains( + config: config, + parameters: WatchChainsParameters, +): WatchChainsReturnType { + const { onChange } = parameters + return config._internal.chains.subscribe((chains, prevChains) => { + onChange( + chains as unknown as GetChainsReturnType, + prevChains as unknown as GetChainsReturnType, + ) + }) +} diff --git a/packages/core/src/actions/watchClient.test-d.ts b/packages/core/src/actions/watchClient.test-d.ts new file mode 100644 index 0000000000..42830f02c3 --- /dev/null +++ b/packages/core/src/actions/watchClient.test-d.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { watchClient } from './watchClient.js' + +test('default', () => { + watchClient(config, { + onChange(client) { + expectTypeOf(client.chain).toEqualTypeOf< + (typeof config)['chains'][number] + >() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() + }, + }) +}) diff --git a/packages/core/src/actions/watchClient.test.ts b/packages/core/src/actions/watchClient.test.ts new file mode 100644 index 0000000000..9cb5f447d2 --- /dev/null +++ b/packages/core/src/actions/watchClient.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import type { Client } from 'viem' +import { expect, test } from 'vitest' + +import { switchChain } from './switchChain.js' +import { watchClient } from './watchClient.js' + +test('default', async () => { + const clients: Client[] = [] + const unwatch = watchClient(config, { + onChange(client) { + clients.push(client) + }, + }) + + switchChain(config, { chainId: 456 }) + switchChain(config, { chainId: 10 }) + switchChain(config, { chainId: 1 }) + + expect(clients.length).toBe(3) + + unwatch() +}) diff --git a/packages/core/src/actions/watchClient.ts b/packages/core/src/actions/watchClient.ts new file mode 100644 index 0000000000..4247353537 --- /dev/null +++ b/packages/core/src/actions/watchClient.ts @@ -0,0 +1,35 @@ +import type { Config } from '../createConfig.js' +import { type GetClientReturnType, getClient } from './getClient.js' + +export type WatchClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = { + onChange( + publicClient: GetClientReturnType, + prevClient: GetClientReturnType, + ): void +} + +export type WatchClientReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchClient */ +export function watchClient< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WatchClientParameters, +): WatchClientReturnType { + const { onChange } = parameters + return config.subscribe( + () => getClient(config) as GetClientReturnType, + onChange, + { + equalityFn(a, b) { + return a?.uid === b?.uid + }, + }, + ) +} diff --git a/packages/core/src/actions/watchConnections.test.ts b/packages/core/src/actions/watchConnections.test.ts new file mode 100644 index 0000000000..9ddf4444ab --- /dev/null +++ b/packages/core/src/actions/watchConnections.test.ts @@ -0,0 +1,25 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import type { Connection } from '../createConfig.js' +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { watchConnections } from './watchConnections.js' + +test('default', async () => { + const connections: Connection[][] = [] + const unwatch = watchConnections(config, { + onChange(connection) { + connections.push(connection) + }, + }) + + const connector = config.connectors[0]! + expect(connections).toEqual([]) + await connect(config, { connector }) + expect(connections[0]?.length).toEqual(1) + await disconnect(config, { connector }) + expect(connections[1]).toEqual([]) + + unwatch() +}) diff --git a/packages/core/src/actions/watchConnections.ts b/packages/core/src/actions/watchConnections.ts new file mode 100644 index 0000000000..56b94fd455 --- /dev/null +++ b/packages/core/src/actions/watchConnections.ts @@ -0,0 +1,26 @@ +import type { Config } from '../createConfig.js' +import { deepEqual } from '../utils/deepEqual.js' +import { + type GetConnectionsReturnType, + getConnections, +} from './getConnections.js' + +export type WatchConnectionsParameters = { + onChange( + connections: GetConnectionsReturnType, + prevConnections: GetConnectionsReturnType, + ): void +} + +export type WatchConnectionsReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchConnections */ +export function watchConnections( + config: Config, + parameters: WatchConnectionsParameters, +): WatchConnectionsReturnType { + const { onChange } = parameters + return config.subscribe(() => getConnections(config), onChange, { + equalityFn: deepEqual, + }) +} diff --git a/packages/core/src/actions/watchConnectors.test.ts b/packages/core/src/actions/watchConnectors.test.ts new file mode 100644 index 0000000000..6e66f75f93 --- /dev/null +++ b/packages/core/src/actions/watchConnectors.test.ts @@ -0,0 +1,27 @@ +import { accounts, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { mock } from '../connectors/mock.js' +import type { Connector } from '../createConfig.js' +import { watchConnectors } from './watchConnectors.js' + +test('default', async () => { + const connectors: (readonly Connector[])[] = [] + const unwatch = watchConnectors(config, { + onChange(connector) { + connectors.push(connector) + }, + }) + + const count = config.connectors.length + expect(config.connectors).toEqual(config.connectors) + + config._internal.connectors.setState(() => [ + ...config.connectors, + config._internal.connectors.setup(mock({ accounts })), + ]) + + expect(config.connectors.length).toBe(count + 1) + + unwatch() +}) diff --git a/packages/core/src/actions/watchConnectors.ts b/packages/core/src/actions/watchConnectors.ts new file mode 100644 index 0000000000..e463ab52fa --- /dev/null +++ b/packages/core/src/actions/watchConnectors.ts @@ -0,0 +1,22 @@ +import type { Config } from '../createConfig.js' +import type { GetConnectorsReturnType } from './getConnectors.js' + +export type WatchConnectorsParameters = { + onChange( + connections: GetConnectorsReturnType, + prevConnectors: GetConnectorsReturnType, + ): void +} + +export type WatchConnectorsReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchConnectors */ +export function watchConnectors( + config: config, + parameters: WatchConnectorsParameters, +): WatchConnectorsReturnType { + const { onChange } = parameters + return config._internal.connectors.subscribe((connectors, prevConnectors) => { + onChange(Object.values(connectors), prevConnectors) + }) +} diff --git a/packages/core/src/actions/watchContractEvent.test-d.ts b/packages/core/src/actions/watchContractEvent.test-d.ts new file mode 100644 index 0000000000..e6624fff57 --- /dev/null +++ b/packages/core/src/actions/watchContractEvent.test-d.ts @@ -0,0 +1,142 @@ +import { abi, config } from '@wagmi/test' +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type WatchContractEventParameters, + watchContractEvent, +} from './watchContractEvent.js' + +test('default', () => { + watchContractEvent(config, { + address: '0x', + abi: abi.erc20, + eventName: 'Transfer', + args: { + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) + +test('behavior: no eventName', () => { + type Result = WatchContractEventParameters< + typeof abi.erc20, + undefined, + true, + typeof config + > + expectTypeOf().toEqualTypeOf< + | { + from?: `0x${string}` | `0x${string}`[] | null | undefined + to?: `0x${string}` | `0x${string}`[] | null | undefined + } + | { + owner?: `0x${string}` | `0x${string}`[] | null | undefined + spender?: `0x${string}` | `0x${string}`[] | null | undefined + } + | undefined + >() + + watchContractEvent(config, { + address: '0x', + abi: abi.erc20, + args: { + // TODO: Figure out why this is not working + // @ts-ignore + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer' | 'Approval'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf< + | Record + | readonly unknown[] + | { + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + } + | { + owner?: `0x${string}` | undefined + spender?: `0x${string}` | undefined + value?: bigint | undefined + } + >() + }, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = WatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchContractEvent(config, { + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + type Result2 = WatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + watchContractEvent(config, { + chainId: mainnet.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + type Result3 = WatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchContractEvent(config, { + chainId: optimism.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + watchContractEvent(config, { + chainId: optimism.id, + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) +}) diff --git a/packages/core/src/actions/watchContractEvent.test.ts b/packages/core/src/actions/watchContractEvent.test.ts new file mode 100644 index 0000000000..759a21dd19 --- /dev/null +++ b/packages/core/src/actions/watchContractEvent.test.ts @@ -0,0 +1,96 @@ +import { + abi, + accounts, + address, + config, + testClient, + transactionHashRegex, + wait, +} from '@wagmi/test' +import { http, createWalletClient, parseEther } from 'viem' +import type { WatchEventOnLogsParameter } from 'viem/actions' +import { beforeEach, expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { getBalance } from './getBalance.js' +import { watchContractEvent } from './watchContractEvent.js' +import { writeContract } from './writeContract.js' + +const connector = config.connectors[0]! + +// TODO: Some test does not call disconnect after finishing. Remove once fixing it. +beforeEach(async () => { + if (config.state.current) { + const connection = config.state.connections.get(config.state.current)! + const connector = connection.connector + await disconnect(config, { connector }) + } +}) + +test('default', async () => { + const data = await connect(config, { connector }) + const connectedAddress = data.accounts[0] + + // impersonate usdc holder account and transfer usdc to connected account + await testClient.mainnet.impersonateAccount({ address: address.usdcHolder }) + await testClient.mainnet.setBalance({ + address: address.usdcHolder, + value: 10000000000000000000000n, + }) + await createWalletClient({ + account: address.usdcHolder, + chain: testClient.mainnet.chain, + transport: http(), + }).writeContract({ + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [connectedAddress, parseEther('10', 'gwei')], + }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet.stopImpersonatingAccount({ + address: address.usdcHolder, + }) + + const balance = await getBalance(config, { + address: connectedAddress, + token: address.usdc, + }) + expect(balance.value).toBeGreaterThan(0n) + + // start watching transfer events + let logs: WatchEventOnLogsParameter = [] + const unwatch = watchContractEvent(config, { + address: address.usdc, + abi: abi.erc20, + eventName: 'Transfer', + onLogs(next) { + logs = logs.concat(next) + }, + }) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[1], parseEther('1', 'gwei')], + }) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[3], parseEther('1', 'gwei')], + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(1000) // wait for events to be emitted + + unwatch() + expect(logs.length).toBe(2) + expect(logs[0]?.transactionHash).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/watchContractEvent.ts b/packages/core/src/actions/watchContractEvent.ts new file mode 100644 index 0000000000..ddc15741da --- /dev/null +++ b/packages/core/src/actions/watchContractEvent.ts @@ -0,0 +1,102 @@ +import type { + Abi, + Chain, + ContractEventName, + Transport, + WebSocketTransport, +} from 'viem' +import { + type WatchContractEventParameters as viem_WatchContractEventParameters, + type WatchContractEventReturnType as viem_WatchContractEventReturnType, + watchContractEvent as viem_watchContractEvent, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + SyncConnectedChainParameter, +} from '../types/properties.js' +import type { UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type WatchContractEventParameters< + abi extends Abi | readonly unknown[] = Abi, + eventName extends ContractEventName | undefined = ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + viem_WatchContractEventParameters< + abi, + eventName, + strict, + config['_internal']['transports'][chains[key]['id']] extends infer transport extends + Transport + ? Transport extends transport + ? WebSocketTransport + : transport + : WebSocketTransport + > & + ChainIdParameter & + SyncConnectedChainParameter + > +}[number] + +export type WatchContractEventReturnType = viem_WatchContractEventReturnType + +// TODO: wrap in viem's `observe` to avoid duplicate invocations. +/** https://wagmi.sh/core/api/actions/watchContractEvent */ +export function watchContractEvent< + config extends Config, + chainId extends config['chains'][number]['id'], + const abi extends Abi | readonly unknown[], + eventName extends ContractEventName | undefined, + strict extends boolean | undefined = undefined, +>( + config: config, + parameters: WatchContractEventParameters< + abi, + eventName, + strict, + config, + chainId + >, +) { + const { syncConnectedChain = config._internal.syncConnectedChain, ...rest } = + parameters + + let unwatch: WatchContractEventReturnType | undefined + const listener = (chainId: number | undefined) => { + if (unwatch) unwatch() + + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_watchContractEvent, + 'watchContractEvent', + ) + unwatch = action(rest as unknown as viem_WatchContractEventParameters) + return unwatch + } + + // set up listener for transaction changes + const unlisten = listener(parameters.chainId) + + // set up subscriber for connected chain changes + let unsubscribe: (() => void) | undefined + if (syncConnectedChain && !parameters.chainId) + unsubscribe = config.subscribe( + ({ chainId }) => chainId, + async (chainId) => listener(chainId), + ) + + return () => { + unlisten?.() + unsubscribe?.() + } +} diff --git a/packages/core/src/actions/watchPendingTransactions.test-d.ts b/packages/core/src/actions/watchPendingTransactions.test-d.ts new file mode 100644 index 0000000000..0af40a24c5 --- /dev/null +++ b/packages/core/src/actions/watchPendingTransactions.test-d.ts @@ -0,0 +1,56 @@ +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type WatchPendingTransactionsParameters, + watchPendingTransactions, +} from './watchPendingTransactions.js' + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = WatchPendingTransactionsParameters< + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchPendingTransactions(config, { + poll: false, + onTransactions() {}, + }) + + type Result2 = WatchPendingTransactionsParameters< + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + watchPendingTransactions(config, { + chainId: mainnet.id, + poll: true, + onTransactions() {}, + }) + + type Result3 = WatchPendingTransactionsParameters< + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + watchPendingTransactions(config, { + chainId: optimism.id, + poll: true, + onTransactions() {}, + }) + watchPendingTransactions(config, { + chainId: optimism.id, + poll: false, + onTransactions() {}, + }) +}) diff --git a/packages/core/src/actions/watchPendingTransactions.test.ts b/packages/core/src/actions/watchPendingTransactions.test.ts new file mode 100644 index 0000000000..510b9acce3 --- /dev/null +++ b/packages/core/src/actions/watchPendingTransactions.test.ts @@ -0,0 +1,49 @@ +import { + accounts, + config, + testClient, + transactionHashRegex, + wait, +} from '@wagmi/test' +import { parseEther } from 'viem' +import type { OnTransactionsParameter } from 'viem/actions' +import { expect, test } from 'vitest' + +import { connect } from './connect.js' +import { disconnect } from './disconnect.js' +import { sendTransaction } from './sendTransaction.js' +import { watchPendingTransactions } from './watchPendingTransactions.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + let transactions: OnTransactionsParameter = [] + const unwatch = watchPendingTransactions(config, { + onTransactions(next) { + transactions = [...transactions, ...next] + }, + }) + await wait(500) + + await sendTransaction(config, { + to: accounts[1], + value: parseEther('1'), + }) + await wait(100) + + await sendTransaction(config, { + to: accounts[3], + value: parseEther('1'), + }) + await wait(100) + + await testClient.mainnet.mine({ blocks: 1 }) + + unwatch() + expect(transactions.length).toBe(2) + expect(transactions[0]).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/actions/watchPendingTransactions.ts b/packages/core/src/actions/watchPendingTransactions.ts new file mode 100644 index 0000000000..29f0354501 --- /dev/null +++ b/packages/core/src/actions/watchPendingTransactions.ts @@ -0,0 +1,82 @@ +import type { Chain, Transport, WebSocketTransport } from 'viem' +import { + type WatchPendingTransactionsParameters as viem_WatchPendingTransactionsParameters, + type WatchPendingTransactionsReturnType as viem_WatchPendingTransactionsReturnType, + watchPendingTransactions as viem_watchPendingTransactions, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + SyncConnectedChainParameter, +} from '../types/properties.js' +import type { UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' + +export type WatchPendingTransactionsParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: UnionCompute< + viem_WatchPendingTransactionsParameters< + config['_internal']['transports'][chains[key]['id']] extends infer transport extends + Transport + ? Transport extends transport + ? WebSocketTransport + : transport + : WebSocketTransport + > & + ChainIdParameter & + SyncConnectedChainParameter + > +}[number] + +export type WatchPendingTransactionsReturnType = + viem_WatchPendingTransactionsReturnType + +// TODO: wrap in viem's `observe` to avoid duplicate invocations. +/** https://wagmi.sh/core/api/actions/watchPendingTransactions */ +export function watchPendingTransactions< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WatchPendingTransactionsParameters, +) { + const { syncConnectedChain = config._internal.syncConnectedChain, ...rest } = + parameters + + let unwatch: WatchPendingTransactionsReturnType | undefined + const listener = (chainId: number | undefined) => { + if (unwatch) unwatch() + + const client = config.getClient({ chainId }) + const action = getAction( + client, + viem_watchPendingTransactions, + 'watchPendingTransactions', + ) + unwatch = action(rest as viem_WatchPendingTransactionsParameters) + return unwatch + } + + // set up listener for transaction changes + const unlisten = listener(parameters.chainId) + + // set up subscriber for connected chain changes + let unsubscribe: (() => void) | undefined + if (syncConnectedChain && !parameters.chainId) + unsubscribe = config.subscribe( + ({ chainId }) => chainId, + async (chainId) => listener(chainId), + ) + + return () => { + unlisten?.() + unsubscribe?.() + } +} diff --git a/packages/core/src/actions/watchPublicClient.test-d.ts b/packages/core/src/actions/watchPublicClient.test-d.ts new file mode 100644 index 0000000000..a7353636cb --- /dev/null +++ b/packages/core/src/actions/watchPublicClient.test-d.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { watchPublicClient } from './watchPublicClient.js' + +test('default', () => { + watchPublicClient(config, { + onChange(client) { + expectTypeOf(client.chain).toEqualTypeOf< + (typeof config)['chains'][number] + >() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() + }, + }) +}) diff --git a/packages/core/src/actions/watchPublicClient.test.ts b/packages/core/src/actions/watchPublicClient.test.ts new file mode 100644 index 0000000000..3d9594002c --- /dev/null +++ b/packages/core/src/actions/watchPublicClient.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import type { Client } from 'viem' +import { expect, test } from 'vitest' + +import { switchChain } from './switchChain.js' +import { watchPublicClient } from './watchPublicClient.js' + +test('default', async () => { + const clients: Client[] = [] + const unwatch = watchPublicClient(config, { + onChange(client) { + clients.push(client) + }, + }) + + switchChain(config, { chainId: 456 }) + switchChain(config, { chainId: 10 }) + switchChain(config, { chainId: 1 }) + + expect(clients.length).toBe(3) + + unwatch() +}) diff --git a/packages/core/src/actions/watchPublicClient.ts b/packages/core/src/actions/watchPublicClient.ts new file mode 100644 index 0000000000..b7d854c2ad --- /dev/null +++ b/packages/core/src/actions/watchPublicClient.ts @@ -0,0 +1,38 @@ +import type { Config } from '../createConfig.js' +import { + type GetPublicClientReturnType, + getPublicClient, +} from './getPublicClient.js' + +export type WatchPublicClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = { + onChange( + publicClient: GetPublicClientReturnType, + prevPublicClient: GetPublicClientReturnType, + ): void +} + +export type WatchPublicClientReturnType = () => void + +/** https://wagmi.sh/core/api/actions/watchPublicClient */ +export function watchPublicClient< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WatchPublicClientParameters, +): WatchPublicClientReturnType { + const { onChange } = parameters + return config.subscribe( + () => getPublicClient(config) as GetPublicClientReturnType, + onChange, + { + equalityFn(a, b) { + return a?.uid === b?.uid + }, + }, + ) +} diff --git a/packages/core/src/actions/writeContract.test-d.ts b/packages/core/src/actions/writeContract.test-d.ts new file mode 100644 index 0000000000..68009a3773 --- /dev/null +++ b/packages/core/src/actions/writeContract.test-d.ts @@ -0,0 +1,152 @@ +import { abi, config } from '@wagmi/test' +import { http, type Address, parseAbi } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { simulateContract } from './simulateContract.js' +import { type WriteContractParameters, writeContract } from './writeContract.js' + +test('default', async () => { + await writeContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) +}) + +test('simulateContract', async () => { + const { request } = await simulateContract(config, { + account: '0x', + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + await writeContract(config, request) + await writeContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + }) +}) + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = WriteContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config + > + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + writeContract(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + + type Result2 = WriteContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toMatchTypeOf<{ + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + feeCurrency?: `0x${string}` | undefined + }>() + writeContract(config, { + chainId: celo.id, + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + + type Result3 = WriteContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof mainnet.id + > + expectTypeOf().toMatchTypeOf<{ + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + writeContract(config, { + chainId: mainnet.id, + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('overloads', async () => { + const abi = parseAbi([ + 'function foo() returns (int8)', + 'function foo(address) returns (string)', + 'function foo(address, address) returns ((address foo, address bar))', + 'function bar(uint256) returns (int8)', + ]) + + type Result = WriteContractParameters + expectTypeOf().toEqualTypeOf<'foo' | 'bar'>() + expectTypeOf().toEqualTypeOf< + | readonly [] + | readonly [`0x${string}`] + | readonly [`0x${string}`, `0x${string}`] + | undefined + >() + writeContract(config, { + address: '0x', + abi, + functionName: 'foo', + }) + writeContract(config, { + address: '0x', + abi, + functionName: 'foo', + args: ['0x'], + }) + writeContract(config, { + address: '0x', + abi, + functionName: 'foo', + args: ['0x', '0x'], + }) + writeContract(config, { + address: '0x', + abi, + functionName: 'foo', + // @ts-expect-error + args: ['0x', 123n], + }) + + type Result2 = WriteContractParameters + expectTypeOf().toEqualTypeOf<'foo' | 'bar'>() + expectTypeOf().toEqualTypeOf() +}) diff --git a/packages/core/src/actions/writeContract.ts b/packages/core/src/actions/writeContract.ts new file mode 100644 index 0000000000..5fb6d87104 --- /dev/null +++ b/packages/core/src/actions/writeContract.ts @@ -0,0 +1,114 @@ +import type { + Abi, + Account, + Chain, + Client, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' +import { + type WriteContractErrorType as viem_WriteContractErrorType, + type WriteContractParameters as viem_WriteContractParameters, + type WriteContractReturnType as viem_WriteContractReturnType, + writeContract as viem_writeContract, +} from 'viem/actions' + +import type { Config } from '../createConfig.js' +import type { BaseErrorType, ErrorType } from '../errors/base.js' +import type { SelectChains } from '../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../types/properties.js' +import type { Compute, UnionCompute } from '../types/utils.js' +import { getAction } from '../utils/getAction.js' +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from './getConnectorClient.js' + +export type WriteContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + allFunctionNames = ContractFunctionName, + chains extends readonly Chain[] = SelectChains, +> = UnionCompute< + { + // TODO: Should use `UnionStrictOmit<..., 'chain'>` on `viem_WriteContractParameters` result instead + // temp workaround that doesn't affect runtime behavior for for https://github.com/wevm/wagmi/issues/3981 + [key in keyof chains]: viem_WriteContractParameters< + abi, + functionName, + args, + chains[key], + Account, + chains[key], + allFunctionNames + > + }[number] & + Compute> & + ConnectorParameter & { + /** @deprecated */ + __mode?: 'prepared' + } +> + +export type WriteContractReturnType = viem_WriteContractReturnType + +export type WriteContractErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_WriteContractErrorType + +/** https://wagmi.sh/core/api/actions/writeContract */ +export async function writeContract< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WriteContractParameters, +): Promise { + const { account, chainId, connector, ...request } = parameters + + let client: Client + if (typeof account === 'object' && account?.type === 'local') + client = config.getClient({ chainId }) + else + client = await getConnectorClient(config, { + account: account ?? undefined, + chainId, + connector, + }) + + const action = getAction(client, viem_writeContract, 'writeContract') + const hash = await action({ + ...(request as any), + ...(account ? { account } : {}), + chain: chainId ? { id: chainId } : null, + }) + + return hash +} diff --git a/packages/core/src/connectors/createConnector.test.ts b/packages/core/src/connectors/createConnector.test.ts new file mode 100644 index 0000000000..d1ff9f20af --- /dev/null +++ b/packages/core/src/connectors/createConnector.test.ts @@ -0,0 +1,31 @@ +import type { Address } from 'viem' +import { test } from 'vitest' +import { createConnector } from './createConnector.js' + +test('default', () => { + createConnector(() => { + return { + id: 'test', + name: 'Test Connector', + type: 'test', + async setup() {}, + async connect() { + return { accounts: [] as Address[], chainId: 123 } + }, + async disconnect() {}, + async getAccounts() { + return [] + }, + async getChainId() { + return 123 + }, + async isAuthorized() { + return true + }, + onAccountsChanged() {}, + onChainChanged() {}, + async onDisconnect(_error) {}, + async getProvider() {}, + } + }) +}) diff --git a/packages/core/src/connectors/createConnector.ts b/packages/core/src/connectors/createConnector.ts new file mode 100644 index 0000000000..54b4d9955c --- /dev/null +++ b/packages/core/src/connectors/createConnector.ts @@ -0,0 +1,93 @@ +import type { + AddEthereumChainParameter, + Address, + Chain, + Client, + ProviderConnectInfo, + ProviderMessage, +} from 'viem' + +import type { Transport } from '../createConfig.js' +import type { Emitter } from '../createEmitter.js' +import type { Storage } from '../createStorage.js' +import type { Compute, ExactPartial, StrictOmit } from '../types/utils.js' + +export type ConnectorEventMap = { + change: { + accounts?: readonly Address[] | undefined + chainId?: number | undefined + } + connect: { accounts: readonly Address[]; chainId: number } + disconnect: never + error: { error: Error } + message: { type: string; data?: unknown | undefined } +} + +export type CreateConnectorFn< + provider = unknown, + properties extends Record = Record, + storageItem extends Record = Record, +> = (config: { + chains: readonly [Chain, ...Chain[]] + emitter: Emitter + storage?: Compute> | null | undefined + transports?: Record | undefined +}) => Compute< + { + readonly icon?: string | undefined + readonly id: string + readonly name: string + readonly rdns?: string | readonly string[] | undefined + /** @deprecated */ + readonly supportsSimulation?: boolean | undefined + readonly type: string + + setup?(): Promise + connect( + parameters?: + | { chainId?: number | undefined; isReconnecting?: boolean | undefined } + | undefined, + ): Promise<{ + accounts: readonly Address[] + chainId: number + }> + disconnect(): Promise + getAccounts(): Promise + getChainId(): Promise + getProvider( + parameters?: { chainId?: number | undefined } | undefined, + ): Promise + getClient?( + parameters?: { chainId?: number | undefined } | undefined, + ): Promise + isAuthorized(): Promise + switchChain?( + parameters: Compute<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + }>, + ): Promise + + onAccountsChanged(accounts: string[]): void + onChainChanged(chainId: string): void + onConnect?(connectInfo: ProviderConnectInfo): void + onDisconnect(error?: Error | undefined): void + onMessage?(message: ProviderMessage): void + } & properties +> + +export function createConnector< + provider, + properties extends Record = Record, + storageItem extends Record = Record, + /// + createConnectorFn extends CreateConnectorFn< + provider, + properties, + storageItem + > = CreateConnectorFn, +>(createConnectorFn: createConnectorFn) { + return createConnectorFn +} diff --git a/packages/core/src/connectors/injected.test.ts b/packages/core/src/connectors/injected.test.ts new file mode 100644 index 0000000000..9188a53bae --- /dev/null +++ b/packages/core/src/connectors/injected.test.ts @@ -0,0 +1,25 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { injected } from './injected.js' + +test('setup', () => { + const connectorFn = injected() + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('Injected') +}) + +test.each([ + { wallet: undefined, expected: 'Injected' }, + { wallet: 'coinbaseWallet', expected: 'Coinbase Wallet' }, + { wallet: 'metaMask', expected: 'MetaMask' }, + { wallet: 'phantom', expected: 'Phantom' }, + { wallet: 'rainbow', expected: 'Rainbow' }, +] as const satisfies readonly { + wallet: string | undefined + expected: string +}[])('injected({ wallet: $wallet })', ({ wallet, expected }) => { + const connectorFn = injected({ target: wallet }) + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual(expected) +}) diff --git a/packages/core/src/connectors/injected.ts b/packages/core/src/connectors/injected.ts new file mode 100644 index 0000000000..d686d0aa29 --- /dev/null +++ b/packages/core/src/connectors/injected.ts @@ -0,0 +1,697 @@ +import { + type AddEthereumChainParameter, + type Address, + type EIP1193Provider, + type ProviderConnectInfo, + type ProviderRpcError, + ResourceUnavailableRpcError, + type RpcError, + SwitchChainError, + UserRejectedRequestError, + getAddress, + numberToHex, + withRetry, + withTimeout, +} from 'viem' + +import type { Connector } from '../createConfig.js' +import { ChainNotConfiguredError } from '../errors/config.js' +import { ProviderNotFoundError } from '../errors/connector.js' +import type { Compute } from '../types/utils.js' +import { createConnector } from './createConnector.js' + +export type InjectedParameters = { + /** + * Some injected providers do not support programmatic disconnect. + * This flag simulates the disconnect behavior by keeping track of connection status in storage. + * @default true + */ + shimDisconnect?: boolean | undefined + /** + * [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Provider to target + */ + target?: TargetId | Target | (() => Target | undefined) | undefined + unstable_shimAsyncInject?: boolean | number | undefined +} + +injected.type = 'injected' as const +export function injected(parameters: InjectedParameters = {}) { + const { shimDisconnect = true, unstable_shimAsyncInject } = parameters + + function getTarget(): Compute { + const target = parameters.target + if (typeof target === 'function') { + const result = target() + if (result) return result + } + + if (typeof target === 'object') return target + + if (typeof target === 'string') + return { + ...(targetMap[target as keyof typeof targetMap] ?? { + id: target, + name: `${target[0]!.toUpperCase()}${target.slice(1)}`, + provider: `is${target[0]!.toUpperCase()}${target.slice(1)}`, + }), + } + + return { + id: 'injected', + name: 'Injected', + provider(window) { + return window?.ethereum + }, + } + } + + type Provider = WalletProvider | undefined + type Properties = { + onConnect(connectInfo: ProviderConnectInfo): void + } + type StorageItem = { + [_ in 'injected.connected' | `${string}.disconnected`]: true + } + + let accountsChanged: Connector['onAccountsChanged'] | undefined + let chainChanged: Connector['onChainChanged'] | undefined + let connect: Connector['onConnect'] | undefined + let disconnect: Connector['onDisconnect'] | undefined + + return createConnector((config) => ({ + get icon() { + return getTarget().icon + }, + get id() { + return getTarget().id + }, + get name() { + return getTarget().name + }, + /** @deprecated */ + get supportsSimulation() { + return true + }, + type: injected.type, + async setup() { + const provider = await this.getProvider() + // Only start listening for events if `target` is set, otherwise `injected()` will also receive events + if (provider?.on && parameters.target) { + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect) + } + + // We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet). + // Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead. + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + } + }, + async connect({ chainId, isReconnecting } = {}) { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + let accounts: readonly Address[] = [] + if (isReconnecting) accounts = await this.getAccounts().catch(() => []) + else if (shimDisconnect) { + // Attempt to show another prompt for selecting account if `shimDisconnect` flag is enabled + try { + const permissions = await provider.request({ + method: 'wallet_requestPermissions', + params: [{ eth_accounts: {} }], + }) + accounts = (permissions[0]?.caveats?.[0]?.value as string[])?.map( + (x) => getAddress(x), + ) + // `'wallet_requestPermissions'` can return a different order of accounts than `'eth_accounts'` + // switch to `'eth_accounts'` ordering if more than one account is connected + // https://github.com/wevm/wagmi/issues/4140 + if (accounts.length > 0) { + const sortedAccounts = await this.getAccounts() + accounts = sortedAccounts + } + } catch (err) { + const error = err as RpcError + // Not all injected providers support `wallet_requestPermissions` (e.g. MetaMask iOS). + // Only bubble up error if user rejects request + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + // Or prompt is already open + if (error.code === ResourceUnavailableRpcError.code) throw error + } + } + + try { + if (!accounts?.length && !isReconnecting) { + const requestedAccounts = await provider.request({ + method: 'eth_requestAccounts', + }) + accounts = requestedAccounts.map((x) => getAddress(x)) + } + + // Manage EIP-1193 event listeners + // https://eips.ethereum.org/EIPS/eip-1193#events + if (connect) { + provider.removeListener('connect', connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + + // Switch to chain if provided + let currentChainId = await this.getChainId() + if (chainId && currentChainId !== chainId) { + const chain = await this.switchChain!({ chainId }).catch((error) => { + if (error.code === UserRejectedRequestError.code) throw error + return { id: currentChainId } + }) + currentChainId = chain?.id ?? currentChainId + } + + // Remove disconnected shim if it exists + if (shimDisconnect) + await config.storage?.removeItem(`${this.id}.disconnected`) + + // Add connected shim if no target exists + if (!parameters.target) + await config.storage?.setItem('injected.connected', true) + + return { accounts, chainId: currentChainId } + } catch (err) { + const error = err as RpcError + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + if (error.code === ResourceUnavailableRpcError.code) + throw new ResourceUnavailableRpcError(error) + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + // Manage EIP-1193 event listeners + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect) + } + + // Experimental support for MetaMask disconnect + // https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-2.md + try { + // Adding timeout as not all wallets support this method and can hang + // https://github.com/wevm/wagmi/issues/4064 + await withTimeout( + () => + // TODO: Remove explicit type for viem@3 + provider.request<{ + Method: 'wallet_revokePermissions' + Parameters: [permissions: { eth_accounts: Record }] + ReturnType: null + }>({ + // `'wallet_revokePermissions'` added in `viem@2.10.3` + method: 'wallet_revokePermissions', + params: [{ eth_accounts: {} }], + }), + { timeout: 100 }, + ) + } catch {} + + // Add shim signalling connector is disconnected + if (shimDisconnect) { + await config.storage?.setItem(`${this.id}.disconnected`, true) + } + + if (!parameters.target) + await config.storage?.removeItem('injected.connected') + }, + async getAccounts() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + const accounts = await provider.request({ method: 'eth_accounts' }) + return accounts.map((x) => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + const hexChainId = await provider.request({ method: 'eth_chainId' }) + return Number(hexChainId) + }, + async getProvider() { + if (typeof window === 'undefined') return undefined + + let provider: Provider + const target = getTarget() + if (typeof target.provider === 'function') + provider = target.provider(window as Window | undefined) + else if (typeof target.provider === 'string') + provider = findProvider(window, target.provider) + else provider = target.provider + + // Some wallets do not conform to EIP-1193 (e.g. Trust Wallet) + // https://github.com/wevm/wagmi/issues/3526#issuecomment-1912683002 + if (provider && !provider.removeListener) { + // Try using `off` handler if it exists, otherwise noop + if ('off' in provider && typeof provider.off === 'function') + provider.removeListener = + provider.off as typeof provider.removeListener + else provider.removeListener = () => {} + } + + return provider + }, + async isAuthorized() { + try { + const isDisconnected = + shimDisconnect && + // If shim exists in storage, connector is disconnected + (await config.storage?.getItem(`${this.id}.disconnected`)) + if (isDisconnected) return false + + // Don't allow injected connector to connect if no target is set and it hasn't already connected + // (e.g. flag in storage is not set). This prevents a targetless injected connector from connecting + // automatically whenever there is a targeted connector configured. + if (!parameters.target) { + const connected = await config.storage?.getItem('injected.connected') + if (!connected) return false + } + + const provider = await this.getProvider() + if (!provider) { + if ( + unstable_shimAsyncInject !== undefined && + unstable_shimAsyncInject !== false + ) { + // If no provider is found, check for async injection + // https://github.com/wevm/references/issues/167 + // https://github.com/MetaMask/detect-provider + const handleEthereum = async () => { + if (typeof window !== 'undefined') + window.removeEventListener( + 'ethereum#initialized', + handleEthereum, + ) + const provider = await this.getProvider() + return !!provider + } + const timeout = + typeof unstable_shimAsyncInject === 'number' + ? unstable_shimAsyncInject + : 1_000 + const res = await Promise.race([ + ...(typeof window !== 'undefined' + ? [ + new Promise((resolve) => + window.addEventListener( + 'ethereum#initialized', + () => resolve(handleEthereum()), + { once: true }, + ), + ), + ] + : []), + new Promise((resolve) => + setTimeout(() => resolve(handleEthereum()), timeout), + ), + ]) + if (res) return true + } + + throw new ProviderNotFoundError() + } + + // Use retry strategy as some injected wallets (e.g. MetaMask) fail to + // immediately resolve JSON-RPC requests on page load. + const accounts = await withRetry(() => this.getAccounts()) + return !!accounts.length + } catch { + return false + } + }, + async switchChain({ addEthereumChainParameter, chainId }) { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + const chain = config.chains.find((x) => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + const promise = new Promise((resolve) => { + const listener = ((data) => { + if ('chainId' in data && data.chainId === chainId) { + config.emitter.off('change', listener) + resolve() + } + }) satisfies Parameters[1] + config.emitter.on('change', listener) + }) + + try { + await Promise.all([ + provider + .request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }], + }) + // During `'wallet_switchEthereumChain'`, MetaMask makes a `'net_version'` RPC call to the target chain. + // If this request fails, MetaMask does not emit the `'chainChanged'` event, but will still switch the chain. + // To counter this behavior, we request and emit the current chain ID to confirm the chain switch either via + // this callback or an externally emitted `'chainChanged'` event. + // https://github.com/MetaMask/metamask-extension/issues/24247 + .then(async () => { + const currentChainId = await this.getChainId() + if (currentChainId === chainId) + config.emitter.emit('change', { chainId }) + }), + promise, + ]) + return chain + } catch (err) { + const error = err as RpcError + + // Indicates chain is not added to provider + if ( + error.code === 4902 || + // Unwrapping for MetaMask Mobile + // https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719 + (error as ProviderRpcError<{ originalError?: { code: number } }>) + ?.data?.originalError?.code === 4902 + ) { + try { + const { default: blockExplorer, ...blockExplorers } = + chain.blockExplorers ?? {} + let blockExplorerUrls: string[] | undefined + if (addEthereumChainParameter?.blockExplorerUrls) + blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls + else if (blockExplorer) + blockExplorerUrls = [ + blockExplorer.url, + ...Object.values(blockExplorers).map((x) => x.url), + ] + + let rpcUrls: readonly string[] + if (addEthereumChainParameter?.rpcUrls?.length) + rpcUrls = addEthereumChainParameter.rpcUrls + else rpcUrls = [chain.rpcUrls.default?.http[0] ?? ''] + + const addEthereumChain = { + blockExplorerUrls, + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? + chain.nativeCurrency, + rpcUrls, + } satisfies AddEthereumChainParameter + + await Promise.all([ + provider + .request({ + method: 'wallet_addEthereumChain', + params: [addEthereumChain], + }) + .then(async () => { + const currentChainId = await this.getChainId() + if (currentChainId === chainId) + config.emitter.emit('change', { chainId }) + else + throw new UserRejectedRequestError( + new Error('User rejected switch after adding network.'), + ) + }), + promise, + ]) + + return chain + } catch (error) { + throw new UserRejectedRequestError(error as Error) + } + } + + if (error.code === UserRejectedRequestError.code) + throw new UserRejectedRequestError(error) + throw new SwitchChainError(error) + } + }, + async onAccountsChanged(accounts) { + // Disconnect if there are no accounts + if (accounts.length === 0) this.onDisconnect() + // Connect if emitter is listening for connect event (e.g. is disconnected and connects through wallet interface) + else if (config.emitter.listenerCount('connect')) { + const chainId = (await this.getChainId()).toString() + this.onConnect({ chainId }) + // Remove disconnected shim if it exists + if (shimDisconnect) + await config.storage?.removeItem(`${this.id}.disconnected`) + } + // Regular change event + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onConnect(connectInfo) { + const accounts = await this.getAccounts() + if (accounts.length === 0) return + + const chainId = Number(connectInfo.chainId) + config.emitter.emit('connect', { accounts, chainId }) + + // Manage EIP-1193 event listeners + const provider = await this.getProvider() + if (provider) { + if (connect) { + provider.removeListener('connect', connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on('accountsChanged', accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on('chainChanged', chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on('disconnect', disconnect) + } + } + }, + async onDisconnect(error) { + const provider = await this.getProvider() + + // If MetaMask emits a `code: 1013` error, wait for reconnection before disconnecting + // https://github.com/MetaMask/providers/pull/120 + if (error && (error as RpcError<1013>).code === 1013) { + if (provider && !!(await this.getAccounts()).length) return + } + + // No need to remove `${this.id}.disconnected` from storage because `onDisconnect` is typically + // only called when the wallet is disconnected through the wallet's interface, meaning the wallet + // actually disconnected and we don't need to simulate it. + config.emitter.emit('disconnect') + + // Manage EIP-1193 event listeners + if (provider) { + if (chainChanged) { + provider.removeListener('chainChanged', chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener('disconnect', disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on('connect', connect) + } + } + }, + })) +} + +const targetMap = { + coinbaseWallet: { + id: 'coinbaseWallet', + name: 'Coinbase Wallet', + provider(window) { + if (window?.coinbaseWalletExtension) return window.coinbaseWalletExtension + return findProvider(window, 'isCoinbaseWallet') + }, + }, + metaMask: { + id: 'metaMask', + name: 'MetaMask', + provider(window) { + return findProvider(window, (provider) => { + if (!provider.isMetaMask) return false + // Brave tries to make itself look like MetaMask + // Could also try RPC `web3_clientVersion` if following is unreliable + if (provider.isBraveWallet && !provider._events && !provider._state) + return false + // Other wallets that try to look like MetaMask + const flags = [ + 'isApexWallet', + 'isAvalanche', + 'isBitKeep', + 'isBlockWallet', + 'isKuCoinWallet', + 'isMathWallet', + 'isOkxWallet', + 'isOKExWallet', + 'isOneInchIOSWallet', + 'isOneInchAndroidWallet', + 'isOpera', + 'isPhantom', + 'isPortal', + 'isRabby', + 'isTokenPocket', + 'isTokenary', + 'isUniswapWallet', + 'isZerion', + ] satisfies WalletProviderFlags[] + for (const flag of flags) if (provider[flag]) return false + return true + }) + }, + }, + phantom: { + id: 'phantom', + name: 'Phantom', + provider(window) { + if (window?.phantom?.ethereum) return window.phantom?.ethereum + return findProvider(window, 'isPhantom') + }, + }, +} as const satisfies TargetMap + +type TargetMap = { [_ in TargetId]?: Target | undefined } + +type Target = { + icon?: string | undefined + id: string + name: string + provider: + | WalletProviderFlags + | WalletProvider + | ((window?: Window | undefined) => WalletProvider | undefined) +} + +/** @deprecated */ +type TargetId = Compute extends `is${infer name}` + ? name extends `${infer char}${infer rest}` + ? `${Lowercase}${rest}` + : never + : never + +/** + * @deprecated As of 2024/10/16, we are no longer accepting new provider flags as EIP-6963 should be used instead. + */ +type WalletProviderFlags = + | 'isApexWallet' + | 'isAvalanche' + | 'isBackpack' + | 'isBifrost' + | 'isBitKeep' + | 'isBitski' + | 'isBlockWallet' + | 'isBraveWallet' + | 'isCoinbaseWallet' + | 'isDawn' + | 'isEnkrypt' + | 'isExodus' + | 'isFrame' + | 'isFrontier' + | 'isGamestop' + | 'isHyperPay' + | 'isImToken' + | 'isKuCoinWallet' + | 'isMathWallet' + | 'isMetaMask' + | 'isOkxWallet' + | 'isOKExWallet' + | 'isOneInchAndroidWallet' + | 'isOneInchIOSWallet' + | 'isOpera' + | 'isPhantom' + | 'isPortal' + | 'isRabby' + | 'isRainbow' + | 'isStatus' + | 'isTally' + | 'isTokenPocket' + | 'isTokenary' + | 'isTrust' + | 'isTrustWallet' + | 'isUniswapWallet' + | 'isXDEFI' + | 'isZerion' + +type WalletProvider = Compute< + EIP1193Provider & { + [key in WalletProviderFlags]?: true | undefined + } & { + providers?: WalletProvider[] | undefined + /** Only exists in MetaMask as of 2022/04/03 */ + _events?: { connect?: (() => void) | undefined } | undefined + /** Only exists in MetaMask as of 2022/04/03 */ + _state?: + | { + accounts?: string[] + initialized?: boolean + isConnected?: boolean + isPermanentlyDisconnected?: boolean + isUnlocked?: boolean + } + | undefined + } +> + +type Window = { + coinbaseWalletExtension?: WalletProvider | undefined + ethereum?: WalletProvider | undefined + phantom?: { ethereum: WalletProvider } | undefined +} + +function findProvider( + window: globalThis.Window | Window | undefined, + select?: WalletProviderFlags | ((provider: WalletProvider) => boolean), +) { + function isProvider(provider: WalletProvider) { + if (typeof select === 'function') return select(provider) + if (typeof select === 'string') return provider[select] + return true + } + + const ethereum = (window as Window).ethereum + if (ethereum?.providers) + return ethereum.providers.find((provider) => isProvider(provider)) + if (ethereum && isProvider(ethereum)) return ethereum + return undefined +} diff --git a/packages/core/src/connectors/mock.test.ts b/packages/core/src/connectors/mock.test.ts new file mode 100644 index 0000000000..d8a812a3d7 --- /dev/null +++ b/packages/core/src/connectors/mock.test.ts @@ -0,0 +1,112 @@ +import { accounts, config } from '@wagmi/test' +import { expect, expectTypeOf, test } from 'vitest' + +import type { Connector } from '../createConfig.js' +import type { CreateConnectorFn } from './createConnector.js' +import { mock } from './mock.js' + +test('setup', () => { + const connectorFn = mock({ accounts }) + const connector = config._internal.connectors.setup(connectorFn) + expect(connector.name).toEqual('Mock Connector') + + expectTypeOf< + typeof connectorFn extends CreateConnectorFn ? true : false + >().toEqualTypeOf() + expectTypeOf< + typeof connector extends Connector ? true : false + >().toEqualTypeOf() + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf() +}) + +test('behavior: features.connectError', () => { + const connectorFn = mock({ accounts, features: { connectError: true } }) + const connector = config._internal.connectors.setup(connectorFn) + expect(() => connector.connect()).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to connect. + Version: viem@2.29.2] + `) +}) + +test('behavior: connector.getProvider request errors', async () => { + const connectorFn = mock({ + accounts, + features: { + signMessageError: true, + signTypedDataError: true, + switchChainError: true, + watchAssetError: true, + }, + }) + const connector = config._internal.connectors.setup( + connectorFn, + ) as ReturnType + const provider = await connector.getProvider() + + expect( + provider.request({ + method: 'eth_signTypedData_v4', + params: [] as any, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to sign typed data. + Version: viem@2.29.2] + `) + + expect( + provider.request({ + method: 'wallet_switchEthereumChain', + params: [] as any, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to switch chain. + Version: viem@2.29.2] + `) + + expect( + provider.request({ + method: 'wallet_watchAsset', + params: [] as any, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to switch chain. + Version: viem@2.29.2] + `) + + expect( + provider.request({ + method: 'personal_sign', + params: [] as any, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserRejectedRequestError: User rejected the request. + + Details: Failed to sign message. + Version: viem@2.29.2] + `) +}) + +test('behavior: reconnect', async () => { + const connectorFn = mock({ + accounts, + features: { + defaultConnected: true, + reconnect: true, + }, + }) + const connector = config._internal.connectors.setup(connectorFn) + + await expect(connector.isAuthorized()).resolves.toBeTruthy() +}) diff --git a/packages/core/src/connectors/mock.ts b/packages/core/src/connectors/mock.ts new file mode 100644 index 0000000000..9d4dc9d846 --- /dev/null +++ b/packages/core/src/connectors/mock.ts @@ -0,0 +1,315 @@ +import { + type Address, + type EIP1193RequestFn, + type Hex, + RpcRequestError, + SwitchChainError, + type Transport, + UserRejectedRequestError, + type WalletCallReceipt, + type WalletGetCallsStatusReturnType, + type WalletRpcSchema, + custom, + fromHex, + getAddress, + keccak256, + numberToHex, + stringToHex, +} from 'viem' +import { rpc } from 'viem/utils' + +import { + ChainNotConfiguredError, + ConnectorNotConnectedError, +} from '../errors/config.js' +import { createConnector } from './createConnector.js' + +export type MockParameters = { + accounts: readonly [Address, ...Address[]] + features?: + | { + defaultConnected?: boolean | undefined + connectError?: boolean | Error | undefined + switchChainError?: boolean | Error | undefined + signMessageError?: boolean | Error | undefined + signTypedDataError?: boolean | Error | undefined + reconnect?: boolean | undefined + watchAssetError?: boolean | Error | undefined + } + | undefined +} + +mock.type = 'mock' as const +export function mock(parameters: MockParameters) { + const transactionCache = new Map() + const features = + parameters.features ?? + ({ defaultConnected: false } satisfies MockParameters['features']) + + type Provider = ReturnType< + Transport<'custom', unknown, EIP1193RequestFn> + > + type Properties = { + connect(parameters?: { + chainId?: number | undefined + isReconnecting?: boolean | undefined + foo?: string | undefined + }): Promise<{ + accounts: readonly Address[] + chainId: number + }> + } + let connected = features.defaultConnected + let connectedChainId: number + + return createConnector((config) => ({ + id: 'mock', + name: 'Mock Connector', + type: mock.type, + async setup() { + connectedChainId = config.chains[0].id + }, + async connect({ chainId } = {}) { + if (features.connectError) { + if (typeof features.connectError === 'boolean') + throw new UserRejectedRequestError(new Error('Failed to connect.')) + throw features.connectError + } + + const provider = await this.getProvider() + const accounts = await provider.request({ + method: 'eth_requestAccounts', + }) + + let currentChainId = await this.getChainId() + if (chainId && currentChainId !== chainId) { + const chain = await this.switchChain!({ chainId }) + currentChainId = chain.id + } + + connected = true + + return { + accounts: accounts.map((x) => getAddress(x)), + chainId: currentChainId, + } + }, + async disconnect() { + connected = false + }, + async getAccounts() { + if (!connected) throw new ConnectorNotConnectedError() + const provider = await this.getProvider() + const accounts = await provider.request({ method: 'eth_accounts' }) + return accounts.map((x) => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const hexChainId = await provider.request({ method: 'eth_chainId' }) + return fromHex(hexChainId, 'number') + }, + async isAuthorized() { + if (!features.reconnect) return false + if (!connected) return false + const accounts = await this.getAccounts() + return !!accounts.length + }, + async switchChain({ chainId }) { + const provider = await this.getProvider() + const chain = config.chains.find((x) => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + await provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }], + }) + return chain + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit('change', { + accounts: accounts.map((x) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit('change', { chainId }) + }, + async onDisconnect(_error) { + config.emitter.emit('disconnect') + connected = false + }, + async getProvider({ chainId } = {}) { + const chain = + config.chains.find((x) => x.id === chainId) ?? config.chains[0] + const url = chain.rpcUrls.default.http[0]! + + const request: EIP1193RequestFn = async ({ method, params }) => { + // eth methods + if (method === 'eth_chainId') return numberToHex(connectedChainId) + if (method === 'eth_requestAccounts') return parameters.accounts + if (method === 'eth_signTypedData_v4') + if (features.signTypedDataError) { + if (typeof features.signTypedDataError === 'boolean') + throw new UserRejectedRequestError( + new Error('Failed to sign typed data.'), + ) + throw features.signTypedDataError + } + + // wallet methods + if (method === 'wallet_switchEthereumChain') { + if (features.switchChainError) { + if (typeof features.switchChainError === 'boolean') + throw new UserRejectedRequestError( + new Error('Failed to switch chain.'), + ) + throw features.switchChainError + } + type Params = [{ chainId: Hex }] + connectedChainId = fromHex((params as Params)[0].chainId, 'number') + this.onChainChanged(connectedChainId.toString()) + return + } + + if (method === 'wallet_watchAsset') { + if (features.watchAssetError) { + if (typeof features.watchAssetError === 'boolean') + throw new UserRejectedRequestError( + new Error('Failed to switch chain.'), + ) + throw features.watchAssetError + } + return connected + } + + if (method === 'wallet_getCapabilities') + return { + '0x2105': { + paymasterService: { + supported: + (params as [Hex])[0] === + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + }, + sessionKeys: { + supported: true, + }, + }, + '0x14A34': { + paymasterService: { + supported: + (params as [Hex])[0] === + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + }, + }, + } + + if (method === 'wallet_sendCalls') { + const hashes = [] + const calls = (params as any)[0].calls + for (const call of calls) { + const { result, error } = await rpc.http(url, { + body: { + method: 'eth_sendTransaction', + params: [call], + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url, + }) + hashes.push(result) + } + const id = keccak256(stringToHex(JSON.stringify(calls))) + transactionCache.set(id, hashes) + return { id } + } + + if (method === 'wallet_getCallsStatus') { + const hashes = transactionCache.get((params as any)[0]) + if (!hashes) + return { + atomic: false, + chainId: '0x1', + id: (params as any)[0], + status: 100, + receipts: [], + version: '2.0.0', + } satisfies WalletGetCallsStatusReturnType + + const receipts = await Promise.all( + hashes.map(async (hash) => { + const { result, error } = await rpc.http(url, { + body: { + method: 'eth_getTransactionReceipt', + params: [hash], + id: 0, + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url, + }) + if (!result) return null + return { + blockHash: result.blockHash, + blockNumber: result.blockNumber, + gasUsed: result.gasUsed, + logs: result.logs, + status: result.status, + transactionHash: result.transactionHash, + } satisfies WalletCallReceipt + }), + ) + const receipts_ = receipts.filter((x) => x !== null) + if (receipts_.length === 0) + return { + atomic: false, + chainId: '0x1', + id: (params as any)[0], + status: 100, + receipts: [], + version: '2.0.0', + } satisfies WalletGetCallsStatusReturnType + return { + atomic: false, + chainId: '0x1', + id: (params as any)[0], + status: 200, + receipts: receipts_, + version: '2.0.0', + } satisfies WalletGetCallsStatusReturnType + } + + if (method === 'wallet_showCallsStatus') return + + // other methods + if (method === 'personal_sign') { + if (features.signMessageError) { + if (typeof features.signMessageError === 'boolean') + throw new UserRejectedRequestError( + new Error('Failed to sign message.'), + ) + throw features.signMessageError + } + // Change `personal_sign` to `eth_sign` and swap params + method = 'eth_sign' + type Params = [data: Hex, address: Address] + params = [(params as Params)[1], (params as Params)[0]] + } + + const body = { method, params } + const { error, result } = await rpc.http(url, { body }) + if (error) throw new RpcRequestError({ body, error, url }) + + return result + } + return custom({ request })({ retryCount: 0 }) + }, + })) +} diff --git a/packages/core/src/createConfig.test-d.ts b/packages/core/src/createConfig.test-d.ts new file mode 100644 index 0000000000..3daa57bd5f --- /dev/null +++ b/packages/core/src/createConfig.test-d.ts @@ -0,0 +1,110 @@ +import { accounts } from '@wagmi/test' +import { http, createClient, webSocket } from 'viem' +import { mainnet, sepolia } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { mock } from './connectors/mock.js' +import { type CreateConfigParameters, createConfig } from './createConfig.js' + +test('high-level config', () => { + // Create config without needing to import viem modules. + const config = createConfig({ + cacheTime: 100, + chains: [mainnet, sepolia], + connectors: [mock({ accounts })], + batch: { multicall: true }, + pollingInterval: { [mainnet.id]: 100 }, + transports: { + [mainnet.id]: webSocket(), + [sepolia.id]: http(), + }, + }) + const client = config.getClient({ chainId: mainnet.id }) + expectTypeOf(client.chain).toEqualTypeOf(mainnet) + expectTypeOf(client.transport.type).toEqualTypeOf<'webSocket'>() +}) + +test('low-level config', () => { + // Create a "multi chain" config using viem modules. + const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [mock({ accounts })], + client({ chain }) { + return createClient({ chain, transport: http() }) + }, + }) + const client = config.getClient({ chainId: mainnet.id }) + expectTypeOf(client.chain).toEqualTypeOf(mainnet) +}) + +test('behavior: `chains` must have at least one chain', () => { + createConfig({ + // @ts-expect-error + chains: [], + connectors: [mock({ accounts })], + transports: { + [mainnet.id]: http(), + }, + }) + createConfig({ + // @ts-expect-error + chains: [], + connectors: [mock({ accounts })], + client: ({ chain }) => + createClient({ + chain, + transport: http(), + }), + }) +}) + +test('behavior: missing transport for chain', () => { + createConfig({ + chains: [mainnet, sepolia], + connectors: [mock({ accounts })], + // @ts-expect-error + transports: { + [mainnet.id]: http(), + }, + }) + createConfig({ + chains: [mainnet, sepolia], + connectors: [mock({ accounts })], + transports: { + [mainnet.id]: http(), + // @ts-expect-error + [123]: http(), + }, + }) +}) + +test('behavior: parameters should not include certain client config properties', () => { + type Result = keyof CreateConfigParameters + expectTypeOf<'account' extends Result ? true : false>().toEqualTypeOf() + expectTypeOf<'chain' extends Result ? true : false>().toEqualTypeOf() + expectTypeOf< + 'transport' extends Result ? true : false + >().toEqualTypeOf() +}) + +test('infer connectors', () => { + const connectorFn = mock({ accounts }) + const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [connectorFn], + transports: { + [mainnet.id]: webSocket(), + [sepolia.id]: http(), + }, + }) + + const connector = config.connectors[0]! + expectTypeOf(connector).toEqualTypeOf( + config._internal.connectors.setup(connectorFn), + ) + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf() +}) diff --git a/packages/core/src/createConfig.test.ts b/packages/core/src/createConfig.test.ts new file mode 100644 index 0000000000..a6ae41e280 --- /dev/null +++ b/packages/core/src/createConfig.test.ts @@ -0,0 +1,440 @@ +import { accounts, chain, wait } from '@wagmi/test' +import { + type EIP1193Provider, + type EIP6963ProviderDetail, + announceProvider, +} from 'mipd' +import { http } from 'viem' +import { expect, test, vi } from 'vitest' + +import { connect } from './actions/connect.js' +import { disconnect } from './actions/disconnect.js' +import { switchChain } from './actions/switchChain.js' +import { createConnector } from './connectors/createConnector.js' +import { mock } from './connectors/mock.js' +import { createConfig } from './createConfig.js' +import { createStorage } from './createStorage.js' + +const { mainnet, optimism } = chain + +vi.mock(import('mipd'), async (importOriginal) => { + const mod = await importOriginal() + + let cache: typeof mod | undefined + if (!cache) + cache = { + ...mod, + createStore() { + const store = mod.createStore() + return { + ...store, + getProviders() { + return [ + getProviderDetail({ name: 'Example', rdns: 'com.example' }), + getProviderDetail({ name: 'Mock', rdns: 'com.mock' }), + ] + }, + } + }, + } + return cache +}) + +test('default', () => { + const config = createConfig({ + chains: [mainnet], + connectors: [mock({ accounts })], + transports: { + [mainnet.id]: http(), + }, + }) + expect(config).toBeDefined() +}) + +test('getClient', () => { + const config = createConfig({ + chains: [mainnet, optimism], + connectors: [mock({ accounts })], + syncConnectedChain: true, + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + }) + + { + const client = config.getClient() + expect(client.chain.id).toBe(mainnet.id) + } + + { + const client = config.getClient({ chainId: mainnet.id }) + expect(client.chain.id).toBe(mainnet.id) + } + + expect(() => + config.getClient({ + // @ts-expect-error + chainId: 123456, + }), + ).toThrowErrorMatchingInlineSnapshot(` + [ChainNotConfiguredError: Chain not configured. + + Version: @wagmi/core@x.y.z] + `) + + expect(() => { + // @ts-expect-error + config.state.chainId = 123456 + config.getClient() + }).toThrowErrorMatchingInlineSnapshot(` + [ChainNotConfiguredError: Chain not configured. + + Version: @wagmi/core@x.y.z] + `) +}) + +test('behavior: syncConnectedChain', async () => { + const config = createConfig({ + chains: [mainnet, optimism], + connectors: [mock({ accounts })], + syncConnectedChain: true, + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + }) + // defaults to first chain in `createConfig({ chains })` + expect(config.getClient().chain.id).toBe(mainnet.id) + + // switches to optimism + await switchChain(config, { chainId: optimism.id }) + expect(config.getClient().chain.id).toBe(optimism.id) + + // connects to mainnet + await connect(config, { + chainId: mainnet.id, + connector: config.connectors[0]!, + }) + expect(config.getClient().chain.id).toBe(mainnet.id) + + // switches to optimism + await switchChain(config, { chainId: optimism.id }) + expect(config.getClient().chain.id).toBe(optimism.id) + + // disconnects, still connected to optimism + await disconnect(config) + expect(config.getClient().chain.id).toBe(optimism.id) +}) + +test('behavior: migrate for current version', async () => { + const state = { + 'wagmi.store': JSON.stringify({ + state: { + connections: { + __type: 'Map', + value: [ + [ + '983b8aca245', + { + accounts: [ + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + ], + chainId: 1, + connector: { + id: 'io.metamask', + name: 'MetaMask', + type: 'injected', + uid: '983b8aca245', + }, + }, + ], + ], + }, + chainId: 1, + current: '983b8aca245', + }, + version: Number.NaN, // mocked version is `'x.y.z'`, which will get interpreted as `NaN` + }), + } as Record + Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn((key) => state[key] ?? null), + removeItem: vi.fn((key) => state.delete?.[key]), + setItem: vi.fn((key, value) => { + state[key] = value + }), + }, + writable: true, + }) + + const storage = createStorage<{ store: object }>({ + storage: window.localStorage, + }) + + const config = createConfig({ + chains: [mainnet], + storage, + transports: { [mainnet.id]: http() }, + }) + + await wait(100) + + expect(config.state).toMatchInlineSnapshot(` + { + "chainId": 1, + "connections": Map { + "983b8aca245" => { + "accounts": [ + "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + "0xd2135CfB216b74109775236E36d4b433F1DF507B", + ], + "chainId": 1, + "connector": { + "id": "io.metamask", + "name": "MetaMask", + "type": "injected", + "uid": "983b8aca245", + }, + }, + }, + "current": "983b8aca245", + "status": "disconnected", + } + `) +}) + +test('behavior: migrate chainId', async () => { + const state = { + 'wagmi.store': JSON.stringify({ + state: { chainId: 10 }, + version: 1, + }), + } as Record + Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn((key) => state[key] ?? null), + removeItem: vi.fn((key) => state.delete?.[key]), + setItem: vi.fn((key, value) => { + state[key] = value + }), + }, + writable: true, + }) + + const storage = createStorage<{ store: object }>({ + storage: window.localStorage, + }) + + const config = createConfig({ + chains: [mainnet, optimism], + storage, + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + }) + + await wait(100) + + expect(config.state).toMatchInlineSnapshot(` + { + "chainId": 10, + "connections": Map {}, + "current": null, + "status": "disconnected", + } + `) +}) + +test('behavior: properties passed through to Viem Client via getClient', () => { + { + const properties = { + batch: { + multicall: { + batchSize: 102_400, + }, + }, + cacheTime: 5_000, + pollingInterval: 1_000, + } + + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + ...properties, + }) + + const { + account: _a, + chain: _c, + extend: _e, + key: _k, + name: _n, + request: _r, + transport: _tr, + uid: _u, + type: _ty, + ...rest + } = config.getClient() + expect(rest).toEqual(properties) + } + + { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: http(), + }, + batch: { + [mainnet.id]: { + multicall: { + batchSize: 1024, + }, + }, + }, + }) + + const client = config.getClient() + expect(client.batch).toMatchInlineSnapshot(` + { + "multicall": { + "batchSize": 1024, + }, + } + `) + + const client2 = config.getClient({ chainId: optimism.id }) + expect(client2.batch).toMatchInlineSnapshot(` + { + "multicall": true, + } + `) + } +}) + +test('behavior: restore unconfigured chainId', () => { + const state = { + 'wagmi.store': JSON.stringify({ + state: { chainId: 10 }, + version: 1, + }), + } as Record + Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn((key) => state[key] ?? null), + removeItem: vi.fn((key) => state.delete?.[key]), + setItem: vi.fn((key, value) => { + state[key] = value + }), + }, + writable: true, + }) + + const storage = createStorage<{ store: object }>({ + storage: window.localStorage, + }) + + const config = createConfig({ + chains: [mainnet], + storage, + transports: { + [mainnet.id]: http(), + }, + }) + + expect(config.state).toMatchInlineSnapshot(` + { + "chainId": 1, + "connections": Map {}, + "current": null, + "status": "disconnected", + } + `) + + const client = config.getClient() + expect(client.chain.id).toBe(mainnet.id) +}) + +test('behavior: setup connector', async () => { + const config = createConfig({ + chains: [mainnet], + transports: { + [mainnet.id]: http(), + }, + }) + + const connectorFn = mock({ accounts }) + const connector = config._internal.connectors.setup(connectorFn) + config._internal.connectors.setState((x) => [...x, connector]) + + await connect(config, { + chainId: mainnet.id, + connector: config.connectors.find((x) => x.uid === connector.uid)!, + }) + + expect(config.state.current).toBe(connector.uid) + + await disconnect(config) +}) + +test('behavior: eip 6963 providers', async () => { + const detail_1 = getProviderDetail({ name: 'Foo Wallet', rdns: 'com.foo' }) + const detail_2 = getProviderDetail({ name: 'Bar Wallet', rdns: 'com.bar' }) + const detail_3 = getProviderDetail({ name: 'Mock', rdns: 'com.mock' }) + + const config = createConfig({ + chains: [mainnet], + connectors: [ + createConnector((c) => { + return { + ...mock({ accounts })(c), + rdns: ['com.mock', 'com.baz'], + } + }), + ], + transports: { + [mainnet.id]: http(), + }, + }) + + await wait(100) + announceProvider(detail_1)() + await wait(100) + announceProvider(detail_1)() + await wait(100) + announceProvider(detail_2)() + await wait(100) + announceProvider(detail_3)() + await wait(100) + + expect( + config.connectors.flatMap((x) => x.rdns ?? x.id), + ).toMatchInlineSnapshot(` + [ + "com.mock", + "com.baz", + "com.example", + "com.foo", + "com.bar", + ] + `) +}) + +function getProviderDetail( + info: Pick, +): EIP6963ProviderDetail { + return { + info: { + icon: 'data:image/svg+xml,', + uuid: crypto.randomUUID(), + ...info, + }, + provider: `` as unknown as EIP1193Provider, + } +} diff --git a/packages/core/src/createConfig.ts b/packages/core/src/createConfig.ts new file mode 100644 index 0000000000..154eef5f6d --- /dev/null +++ b/packages/core/src/createConfig.ts @@ -0,0 +1,653 @@ +import { + type EIP6963ProviderDetail, + type Store as MipdStore, + createStore as createMipd, +} from 'mipd' +import { + type Address, + type Chain, + type Client, + type EIP1193RequestFn, + createClient, + type ClientConfig as viem_ClientConfig, + type Transport as viem_Transport, +} from 'viem' +import { persist, subscribeWithSelector } from 'zustand/middleware' +import { type Mutate, type StoreApi, createStore } from 'zustand/vanilla' + +import type { + ConnectorEventMap, + CreateConnectorFn, +} from './connectors/createConnector.js' +import { injected } from './connectors/injected.js' +import { type Emitter, type EventData, createEmitter } from './createEmitter.js' +import { + type Storage, + createStorage, + getDefaultStorage, +} from './createStorage.js' +import { ChainNotConfiguredError } from './errors/config.js' +import type { + Compute, + ExactPartial, + LooseOmit, + OneOf, + RemoveUndefined, +} from './types/utils.js' +import { uid } from './utils/uid.js' +import { version } from './version.js' + +export function createConfig< + const chains extends readonly [Chain, ...Chain[]], + transports extends Record, + const connectorFns extends readonly CreateConnectorFn[], +>( + parameters: CreateConfigParameters, +): Config { + const { + multiInjectedProviderDiscovery = true, + storage = createStorage({ + storage: getDefaultStorage(), + }), + syncConnectedChain = true, + ssr = false, + ...rest + } = parameters + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // Set up connectors, clients, etc. + ///////////////////////////////////////////////////////////////////////////////////////////////// + + const mipd = + typeof window !== 'undefined' && multiInjectedProviderDiscovery + ? createMipd() + : undefined + + const chains = createStore(() => rest.chains) + const connectors = createStore(() => { + const collection = [] + const rdnsSet = new Set() + for (const connectorFns of rest.connectors ?? []) { + const connector = setup(connectorFns) + collection.push(connector) + if (!ssr && connector.rdns) { + const rdnsValues = + typeof connector.rdns === 'string' ? [connector.rdns] : connector.rdns + for (const rdns of rdnsValues) { + rdnsSet.add(rdns) + } + } + } + if (!ssr && mipd) { + const providers = mipd.getProviders() + for (const provider of providers) { + if (rdnsSet.has(provider.info.rdns)) continue + collection.push(setup(providerDetailToConnector(provider))) + } + } + return collection + }) + function setup(connectorFn: CreateConnectorFn): Connector { + // Set up emitter with uid and add to connector so they are "linked" together. + const emitter = createEmitter(uid()) + const connector = { + ...connectorFn({ + emitter, + chains: chains.getState(), + storage, + transports: rest.transports, + }), + emitter, + uid: emitter.uid, + } + + // Start listening for `connect` events on connector setup + // This allows connectors to "connect" themselves without user interaction (e.g. MetaMask's "Manually connect to current site") + emitter.on('connect', connect) + connector.setup?.() + + return connector + } + function providerDetailToConnector(providerDetail: EIP6963ProviderDetail) { + const { info } = providerDetail + const provider = providerDetail.provider as any + return injected({ target: { ...info, id: info.rdns, provider } }) + } + + const clients = new Map>() + function getClient( + config: { chainId?: chainId | chains[number]['id'] | undefined } = {}, + ): Client> { + const chainId = config.chainId ?? store.getState().chainId + const chain = chains.getState().find((x) => x.id === chainId) + + // chainId specified and not configured + if (config.chainId && !chain) throw new ChainNotConfiguredError() + + // If the target chain is not configured, use the client of the current chain. + type Return = Client> + { + const client = clients.get(store.getState().chainId) + if (client && !chain) return client as Return + if (!chain) throw new ChainNotConfiguredError() + } + + // If a memoized client exists for a chain id, use that. + { + const client = clients.get(chainId) + if (client) return client as Return + } + + let client: Client + if (rest.client) client = rest.client({ chain }) + else { + const chainId = chain.id as chains[number]['id'] + const chainIds = chains.getState().map((x) => x.id) + // Grab all properties off `rest` and resolve for use in `createClient` + const properties: Partial = {} + const entries = Object.entries(rest) as [keyof typeof rest, any][] + + for (const [key, value] of entries) { + if ( + key === 'chains' || + key === 'client' || + key === 'connectors' || + key === 'transports' + ) + continue + + if (typeof value === 'object') { + // check if value is chainId-specific since some values can be objects + // e.g. { batch: { multicall: { batchSize: 1024 } } } + if (chainId in value) properties[key] = value[chainId] + else { + // check if value is chainId-specific, but does not have value for current chainId + const hasChainSpecificValue = chainIds.some((x) => x in value) + if (hasChainSpecificValue) continue + properties[key] = value + } + } else properties[key] = value + } + + client = createClient({ + ...properties, + chain, + batch: properties.batch ?? { multicall: true }, + transport: (parameters) => + rest.transports[chainId]({ ...parameters, connectors }), + }) + } + + clients.set(chainId, client) + return client as Return + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // Create store + ///////////////////////////////////////////////////////////////////////////////////////////////// + + function getInitialState(): State { + return { + chainId: chains.getState()[0].id, + connections: new Map(), + current: null, + status: 'disconnected', + } + } + + let currentVersion: number + const prefix = '0.0.0-canary-' + if (version.startsWith(prefix)) + currentVersion = Number.parseInt(version.replace(prefix, '')) + // use package major version to version store + else currentVersion = Number.parseInt(version.split('.')[0] ?? '0') + + const store = createStore( + subscribeWithSelector( + // only use persist middleware if storage exists + storage + ? persist(getInitialState, { + migrate(persistedState, version) { + if (version === currentVersion) return persistedState as State + + const initialState = getInitialState() + const chainId = validatePersistedChainId( + persistedState, + initialState.chainId, + ) + return { ...initialState, chainId } + }, + name: 'store', + partialize(state) { + // Only persist "critical" store properties to preserve storage size. + return { + connections: { + __type: 'Map', + value: Array.from(state.connections.entries()).map( + ([key, connection]) => { + const { id, name, type, uid } = connection.connector + const connector = { id, name, type, uid } + return [key, { ...connection, connector }] + }, + ), + } as unknown as PartializedState['connections'], + chainId: state.chainId, + current: state.current, + } satisfies PartializedState + }, + merge(persistedState, currentState) { + // `status` should not be persisted as it messes with reconnection + if ( + typeof persistedState === 'object' && + persistedState && + 'status' in persistedState + ) + delete persistedState.status + // Make sure persisted `chainId` is valid + const chainId = validatePersistedChainId( + persistedState, + currentState.chainId, + ) + return { + ...currentState, + ...(persistedState as object), + chainId, + } + }, + skipHydration: ssr, + storage: storage as Storage>, + version: currentVersion, + }) + : getInitialState, + ), + ) + store.setState(getInitialState()) + + function validatePersistedChainId( + persistedState: unknown, + defaultChainId: number, + ) { + return persistedState && + typeof persistedState === 'object' && + 'chainId' in persistedState && + typeof persistedState.chainId === 'number' && + chains.getState().some((x) => x.id === persistedState.chainId) + ? persistedState.chainId + : defaultChainId + } + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // Subscribe to changes + ///////////////////////////////////////////////////////////////////////////////////////////////// + + // Update default chain when connector chain changes + if (syncConnectedChain) + store.subscribe( + ({ connections, current }) => + current ? connections.get(current)?.chainId : undefined, + (chainId) => { + // If chain is not configured, then don't switch over to it. + const isChainConfigured = chains + .getState() + .some((x) => x.id === chainId) + if (!isChainConfigured) return + + return store.setState((x) => ({ + ...x, + chainId: chainId ?? x.chainId, + })) + }, + ) + + // EIP-6963 subscribe for new wallet providers + mipd?.subscribe((providerDetails) => { + const connectorIdSet = new Set() + const connectorRdnsSet = new Set() + for (const connector of connectors.getState()) { + connectorIdSet.add(connector.id) + if (connector.rdns) { + const rdnsValues = + typeof connector.rdns === 'string' ? [connector.rdns] : connector.rdns + for (const rdns of rdnsValues) { + connectorRdnsSet.add(rdns) + } + } + } + + const newConnectors: Connector[] = [] + for (const providerDetail of providerDetails) { + if (connectorRdnsSet.has(providerDetail.info.rdns)) continue + const connector = setup(providerDetailToConnector(providerDetail)) + if (connectorIdSet.has(connector.id)) continue + newConnectors.push(connector) + } + + if (storage && !store.persist.hasHydrated()) return + connectors.setState((x) => [...x, ...newConnectors], true) + }) + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // Emitter listeners + ///////////////////////////////////////////////////////////////////////////////////////////////// + + function change(data: EventData) { + store.setState((x) => { + const connection = x.connections.get(data.uid) + if (!connection) return x + return { + ...x, + connections: new Map(x.connections).set(data.uid, { + accounts: + (data.accounts as readonly [Address, ...Address[]]) ?? + connection.accounts, + chainId: data.chainId ?? connection.chainId, + connector: connection.connector, + }), + } + }) + } + function connect(data: EventData) { + // Disable handling if reconnecting/connecting + if ( + store.getState().status === 'connecting' || + store.getState().status === 'reconnecting' + ) + return + + store.setState((x) => { + const connector = connectors.getState().find((x) => x.uid === data.uid) + if (!connector) return x + + if (connector.emitter.listenerCount('connect')) + connector.emitter.off('connect', change) + if (!connector.emitter.listenerCount('change')) + connector.emitter.on('change', change) + if (!connector.emitter.listenerCount('disconnect')) + connector.emitter.on('disconnect', disconnect) + + return { + ...x, + connections: new Map(x.connections).set(data.uid, { + accounts: data.accounts as readonly [Address, ...Address[]], + chainId: data.chainId, + connector: connector, + }), + current: data.uid, + status: 'connected', + } + }) + } + function disconnect(data: EventData) { + store.setState((x) => { + const connection = x.connections.get(data.uid) + if (connection) { + const connector = connection.connector + if (connector.emitter.listenerCount('change')) + connection.connector.emitter.off('change', change) + if (connector.emitter.listenerCount('disconnect')) + connection.connector.emitter.off('disconnect', disconnect) + if (!connector.emitter.listenerCount('connect')) + connection.connector.emitter.on('connect', connect) + } + + x.connections.delete(data.uid) + + if (x.connections.size === 0) + return { + ...x, + connections: new Map(), + current: null, + status: 'disconnected', + } + + const nextConnection = x.connections.values().next().value as Connection + return { + ...x, + connections: new Map(x.connections), + current: nextConnection.connector.uid, + } + }) + } + + return { + get chains() { + return chains.getState() as chains + }, + get connectors() { + return connectors.getState() as Connector[] + }, + storage, + + getClient, + get state() { + return store.getState() as unknown as State + }, + setState(value) { + let newState: State + if (typeof value === 'function') newState = value(store.getState() as any) + else newState = value + + // Reset state if it got set to something not matching the base state + const initialState = getInitialState() + if (typeof newState !== 'object') newState = initialState + const isCorrupt = Object.keys(initialState).some((x) => !(x in newState)) + if (isCorrupt) newState = initialState + + store.setState(newState, true) + }, + subscribe(selector, listener, options) { + return store.subscribe( + selector as unknown as (state: State) => any, + listener, + options + ? ({ + ...options, + fireImmediately: options.emitImmediately, + // Workaround cast since Zustand does not support `'exactOptionalPropertyTypes'` + } as RemoveUndefined) + : undefined, + ) + }, + + _internal: { + mipd, + store, + ssr: Boolean(ssr), + syncConnectedChain, + transports: rest.transports as transports, + chains: { + setState(value) { + const nextChains = ( + typeof value === 'function' ? value(chains.getState()) : value + ) as chains + if (nextChains.length === 0) return + return chains.setState(nextChains, true) + }, + subscribe(listener) { + return chains.subscribe(listener) + }, + }, + connectors: { + providerDetailToConnector, + setup: setup as ( + connectorFn: connectorFn, + ) => Connector, + setState(value) { + return connectors.setState( + typeof value === 'function' ? value(connectors.getState()) : value, + true, + ) + }, + subscribe(listener) { + return connectors.subscribe(listener) + }, + }, + events: { change, connect, disconnect }, + }, + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// Types +///////////////////////////////////////////////////////////////////////////////////////////////// + +export type CreateConfigParameters< + chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], + transports extends Record = Record< + chains[number]['id'], + Transport + >, + connectorFns extends + readonly CreateConnectorFn[] = readonly CreateConnectorFn[], +> = Compute< + { + chains: chains + connectors?: connectorFns | undefined + multiInjectedProviderDiscovery?: boolean | undefined + storage?: Storage | null | undefined + ssr?: boolean | undefined + syncConnectedChain?: boolean | undefined + } & OneOf< + | ({ transports: transports } & { + [key in keyof ClientConfig]?: + | ClientConfig[key] + | { [_ in chains[number]['id']]?: ClientConfig[key] | undefined } + | undefined + }) + | { + client(parameters: { chain: chains[number] }): Client< + transports[chains[number]['id']], + chains[number] + > + } + > +> + +export type Config< + chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], + transports extends Record = Record< + chains[number]['id'], + Transport + >, + connectorFns extends + readonly CreateConnectorFn[] = readonly CreateConnectorFn[], +> = { + readonly chains: chains + readonly connectors: readonly Connector[] + readonly storage: Storage | null + + readonly state: State + setState( + value: State | ((state: State) => State), + ): void + subscribe( + selector: (state: State) => state, + listener: (state: state, previousState: state) => void, + options?: + | { + emitImmediately?: boolean | undefined + equalityFn?: ((a: state, b: state) => boolean) | undefined + } + | undefined, + ): () => void + + getClient(parameters?: { + chainId?: chainId | chains[number]['id'] | undefined + }): Client> + + /** + * Not part of versioned API, proceed with caution. + * @internal + */ + _internal: Internal +} + +type Internal< + chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], + transports extends Record = Record< + chains[number]['id'], + Transport + >, +> = { + readonly mipd: MipdStore | undefined + readonly store: Mutate, [['zustand/persist', any]]> + readonly ssr: boolean + readonly syncConnectedChain: boolean + readonly transports: transports + + chains: { + setState( + value: + | readonly [Chain, ...Chain[]] + | (( + state: readonly [Chain, ...Chain[]], + ) => readonly [Chain, ...Chain[]]), + ): void + subscribe( + listener: ( + state: readonly [Chain, ...Chain[]], + prevState: readonly [Chain, ...Chain[]], + ) => void, + ): () => void + } + connectors: { + providerDetailToConnector( + providerDetail: EIP6963ProviderDetail, + ): CreateConnectorFn + setup( + connectorFn: connectorFn, + ): Connector + setState(value: Connector[] | ((state: Connector[]) => Connector[])): void + subscribe( + listener: (state: Connector[], prevState: Connector[]) => void, + ): () => void + } + events: { + change(data: EventData): void + connect(data: EventData): void + disconnect(data: EventData): void + } +} + +export type State< + chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], +> = { + chainId: chains[number]['id'] + connections: Map + current: string | null + status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting' +} + +export type PartializedState = Compute< + ExactPartial> +> + +export type Connection = { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector +} + +export type Connector< + createConnectorFn extends CreateConnectorFn = CreateConnectorFn, +> = ReturnType & { + emitter: Emitter + uid: string +} + +export type Transport< + type extends string = string, + rpcAttributes = Record, + eip1193RequestFn extends EIP1193RequestFn = EIP1193RequestFn, +> = ( + params: Parameters< + viem_Transport + >[0] & { + connectors?: StoreApi | undefined + }, +) => ReturnType> + +type ClientConfig = LooseOmit< + viem_ClientConfig, + 'account' | 'chain' | 'key' | 'name' | 'transport' | 'type' +> diff --git a/packages/core/src/createEmitter.test.ts b/packages/core/src/createEmitter.test.ts new file mode 100644 index 0000000000..5eb453bb51 --- /dev/null +++ b/packages/core/src/createEmitter.test.ts @@ -0,0 +1,19 @@ +import { expect, test, vi } from 'vitest' + +import type { ConnectorEventMap } from './connectors/createConnector.js' +import { createEmitter } from './createEmitter.js' +import { uid } from './utils/uid.js' + +test('default', () => { + const emitter = createEmitter(uid()) + + const onMessage = vi.fn() + emitter.on('message', onMessage) + emitter.emit('message', { type: 'bar', data: 'baz' }) + + expect(onMessage).toHaveBeenCalledWith({ + type: 'bar', + data: 'baz', + uid: emitter.uid, + }) +}) diff --git a/packages/core/src/createEmitter.ts b/packages/core/src/createEmitter.ts new file mode 100644 index 0000000000..20bf4c5a88 --- /dev/null +++ b/packages/core/src/createEmitter.ts @@ -0,0 +1,68 @@ +import { EventEmitter } from 'eventemitter3' + +type EventMap = Record +type EventKey = string & keyof eventMap +type EventFn = ( + ...parameters: parameters +) => void +export type EventData< + eventMap extends EventMap, + eventName extends keyof eventMap, +> = (eventMap[eventName] extends [never] ? unknown : eventMap[eventName]) & { + uid: string +} + +export class Emitter { + _emitter = new EventEmitter() + + constructor(public uid: string) {} + + on>( + eventName: key, + fn: EventFn< + eventMap[key] extends [never] + ? [{ uid: string }] + : [data: eventMap[key] & { uid: string }] + >, + ) { + this._emitter.on(eventName, fn as EventFn) + } + + once>( + eventName: key, + fn: EventFn< + eventMap[key] extends [never] + ? [{ uid: string }] + : [data: eventMap[key] & { uid: string }] + >, + ) { + this._emitter.once(eventName, fn as EventFn) + } + + off>( + eventName: key, + fn: EventFn< + eventMap[key] extends [never] + ? [{ uid: string }] + : [data: eventMap[key] & { uid: string }] + >, + ) { + this._emitter.off(eventName, fn as EventFn) + } + + emit>( + eventName: key, + ...params: eventMap[key] extends [never] ? [] : [data: eventMap[key]] + ) { + const data = params[0] + this._emitter.emit(eventName, { uid: this.uid, ...data }) + } + + listenerCount>(eventName: key) { + return this._emitter.listenerCount(eventName) + } +} + +export function createEmitter(uid: string) { + return new Emitter(uid) +} diff --git a/packages/core/src/createStorage.test-d.ts b/packages/core/src/createStorage.test-d.ts new file mode 100644 index 0000000000..6bb4c7f300 --- /dev/null +++ b/packages/core/src/createStorage.test-d.ts @@ -0,0 +1,74 @@ +import { expectTypeOf, test } from 'vitest' +import { createStorage } from './createStorage.js' + +import type { Connection } from './createConfig.js' + +test('getItem', () => { + const storage = createStorage({ storage: localStorage }) + + expectTypeOf(storage.getItem('recentConnectorId')).toEqualTypeOf< + string | null | Promise + >() + expectTypeOf(storage.getItem('recentConnectorId', 'foo')).toEqualTypeOf< + string | Promise + >() + expectTypeOf(storage.getItem('foo')).toEqualTypeOf() + // @ts-expect-error incorrect argument type + storage.getItem('recentConnectorId', 1n) + + expectTypeOf(storage.getItem('state')).toEqualTypeOf< + | { + chainId?: number | undefined + connections?: Map | undefined + current?: string | null | undefined + status?: + | 'connected' + | 'connecting' + | 'reconnecting' + | 'disconnected' + | undefined + } + | null + | Promise<{ + chainId?: number | undefined + connections?: Map | undefined + current?: string | null | undefined + status?: + | 'connected' + | 'connecting' + | 'reconnecting' + | 'disconnected' + | undefined + } | null> + >() + + const customStorage = createStorage<{ foo: number }>({ + storage: localStorage, + }) + expectTypeOf(customStorage.getItem('foo')).toEqualTypeOf< + number | null | Promise + >() + expectTypeOf(customStorage.getItem('foo', 1)).toEqualTypeOf< + number | Promise + >() +}) + +test('setItem', () => { + const storage = createStorage({ storage: localStorage }) + + storage.setItem('recentConnectorId', 'foo') + // @ts-expect-error incorrect argument type + storage.setItem('recentConnectorId', 1n) +}) + +test('serialize/deserialize types', () => { + createStorage({ + deserialize(value) { + return value + }, + serialize(value) { + return value + }, + storage: localStorage, + }) +}) diff --git a/packages/core/src/createStorage.test.ts b/packages/core/src/createStorage.test.ts new file mode 100644 index 0000000000..06aa0f89d6 --- /dev/null +++ b/packages/core/src/createStorage.test.ts @@ -0,0 +1,45 @@ +import { expect, test, vi } from 'vitest' + +import { createStorage } from './createStorage.js' + +Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn(() => null), + removeItem: vi.fn(() => null), + setItem: vi.fn(() => null), + }, + writable: true, +}) + +test('inits', () => { + const storage = createStorage({ storage: window.localStorage }) + expect(storage).toBeDefined() +}) + +test('getItem', () => { + const storage = createStorage({ storage: window.localStorage }) + storage.getItem('recentConnectorId') + expect(window.localStorage.getItem).toHaveBeenCalledTimes(1) + expect(window.localStorage.getItem).toHaveBeenCalledWith( + 'wagmi.recentConnectorId', + ) +}) + +test('setItem', () => { + const storage = createStorage({ storage: window.localStorage }) + storage.setItem('recentConnectorId', 'bar') + expect(window.localStorage.setItem).toHaveBeenCalledTimes(1) + expect(window.localStorage.setItem).toHaveBeenCalledWith( + 'wagmi.recentConnectorId', + '"bar"', + ) +}) + +test('removeItem', () => { + const storage = createStorage({ storage: window.localStorage }) + storage.removeItem('recentConnectorId') + expect(window.localStorage.removeItem).toHaveBeenCalledTimes(1) + expect(window.localStorage.removeItem).toHaveBeenCalledWith( + 'wagmi.recentConnectorId', + ) +}) diff --git a/packages/core/src/createStorage.ts b/packages/core/src/createStorage.ts new file mode 100644 index 0000000000..8995ca3202 --- /dev/null +++ b/packages/core/src/createStorage.ts @@ -0,0 +1,112 @@ +import type { PartializedState } from './createConfig.js' +import type { Compute } from './types/utils.js' +import { deserialize as deserialize_ } from './utils/deserialize.js' +import { serialize as serialize_ } from './utils/serialize.js' + +// key-values for loose autocomplete and typing +export type StorageItemMap = { + recentConnectorId: string + state: PartializedState +} + +export type Storage< + itemMap extends Record = Record, + /// + storageItemMap extends StorageItemMap = StorageItemMap & itemMap, +> = { + key: string + getItem< + key extends keyof storageItemMap, + value extends storageItemMap[key], + defaultValue extends value | null | undefined, + >( + key: key, + defaultValue?: defaultValue | undefined, + ): + | (defaultValue extends null ? value | null : value) + | Promise + setItem< + key extends keyof storageItemMap, + value extends storageItemMap[key] | null, + >(key: key, value: value): void | Promise + removeItem(key: keyof storageItemMap): void | Promise +} + +export type BaseStorage = { + getItem( + key: string, + ): string | null | undefined | Promise + setItem(key: string, value: string): void | Promise + removeItem(key: string): void | Promise +} + +export type CreateStorageParameters = { + deserialize?: ((value: string) => type | unknown) | undefined + key?: string | undefined + serialize?: ((value: type | any) => string) | undefined + storage?: Compute | undefined +} + +export function createStorage< + itemMap extends Record = Record, + storageItemMap extends StorageItemMap = StorageItemMap & itemMap, +>(parameters: CreateStorageParameters): Compute> { + const { + deserialize = deserialize_, + key: prefix = 'wagmi', + serialize = serialize_, + storage = noopStorage, + } = parameters + + function unwrap(value: type): type | Promise { + if (value instanceof Promise) return value.then((x) => x).catch(() => null) + return value + } + + return { + ...storage, + key: prefix, + async getItem(key, defaultValue) { + const value = storage.getItem(`${prefix}.${key as string}`) + const unwrapped = await unwrap(value) + if (unwrapped) return deserialize(unwrapped) ?? null + return (defaultValue ?? null) as any + }, + async setItem(key, value) { + const storageKey = `${prefix}.${key as string}` + if (value === null) await unwrap(storage.removeItem(storageKey)) + else await unwrap(storage.setItem(storageKey, serialize(value))) + }, + async removeItem(key) { + await unwrap(storage.removeItem(`${prefix}.${key as string}`)) + }, + } +} + +export const noopStorage = { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, +} satisfies BaseStorage + +export function getDefaultStorage() { + const storage = (() => { + if (typeof window !== 'undefined' && window.localStorage) + return window.localStorage + return noopStorage + })() + return { + getItem(key) { + return storage.getItem(key) + }, + removeItem(key) { + storage.removeItem(key) + }, + setItem(key, value) { + try { + storage.setItem(key, value) + // silence errors by default (QuotaExceededError, SecurityError, etc.) + } catch {} + }, + } satisfies BaseStorage +} diff --git a/packages/core/src/errors/base.test.ts b/packages/core/src/errors/base.test.ts new file mode 100644 index 0000000000..443b1ea3cf --- /dev/null +++ b/packages/core/src/errors/base.test.ts @@ -0,0 +1,155 @@ +import { expect, test } from 'vitest' + +import { BaseError } from './base.js' + +test('BaseError', () => { + expect(new BaseError('An error occurred.')).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Version: @wagmi/core@x.y.z] + `) + + expect( + new BaseError('An error occurred.', { details: 'details' }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Details: details + Version: @wagmi/core@x.y.z] + `) + + expect(new BaseError('', { details: 'details' })).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Details: details + Version: @wagmi/core@x.y.z] + `) +}) + +test('BaseError (w/ docsPath)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Docs: https://wagmi.sh/core/lol.html + Details: details + Version: @wagmi/core@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error', { docsPath: '/docs' }), + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Docs: https://wagmi.sh/core/docs.html + Version: @wagmi/core@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error'), + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Docs: https://wagmi.sh/core/lol.html + Version: @wagmi/core@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + docsSlug: 'test', + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Docs: https://wagmi.sh/core/lol.html#test + Details: details + Version: @wagmi/core@x.y.z] + `) +}) + +test('BaseError (w/ metaMessages)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + metaMessages: ['Reason: idk', 'Cause: lol'], + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An error occurred. + + Reason: idk + Cause: lol + + Details: details + Version: @wagmi/core@x.y.z] + `) +}) + +test('inherited BaseError', () => { + const err = new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }) + expect( + new BaseError('An internal error occurred.', { + cause: err, + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An internal error occurred. + + Docs: https://wagmi.sh/core/lol.html + Details: details + Version: @wagmi/core@x.y.z] + `) +}) + +test('inherited Error', () => { + const err = new Error('details') + expect( + new BaseError('An internal error occurred.', { + cause: err, + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiCoreError: An internal error occurred. + + Docs: https://wagmi.sh/core/lol.html + Details: details + Version: @wagmi/core@x.y.z] + `) +}) + +test('walk: no predicate fn (walks to leaf)', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk()).toMatchInlineSnapshot(` + [WagmiCoreError: test3 + + Version: @wagmi/core@x.y.z] + `) +}) + +test('walk: predicate fn', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk((err) => err instanceof FooError)).toMatchInlineSnapshot(` + [WagmiCoreError: test2 + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/errors/base.ts b/packages/core/src/errors/base.ts new file mode 100644 index 0000000000..8540c1da7b --- /dev/null +++ b/packages/core/src/errors/base.ts @@ -0,0 +1,74 @@ +import type { Compute, OneOf } from '../types/utils.js' +import { getVersion } from '../utils/getVersion.js' + +export type ErrorType = Error & { name: name } + +type BaseErrorOptions = Compute< + OneOf<{ details?: string | undefined } | { cause: BaseError | Error }> & { + docsPath?: string | undefined + docsSlug?: string | undefined + metaMessages?: string[] | undefined + } +> + +export type BaseErrorType = BaseError & { name: 'WagmiCoreError' } +export class BaseError extends Error { + details: string + docsPath?: string | undefined + metaMessages?: string[] | undefined + shortMessage: string + + override name = 'WagmiCoreError' + get docsBaseUrl() { + return 'https://wagmi.sh/core' + } + get version() { + return getVersion() + } + + constructor(shortMessage: string, options: BaseErrorOptions = {}) { + super() + + const details = + options.cause instanceof BaseError + ? options.cause.details + : options.cause?.message + ? options.cause.message + : options.details! + const docsPath = + options.cause instanceof BaseError + ? options.cause.docsPath || options.docsPath + : options.docsPath + + this.message = [ + shortMessage || 'An error occurred.', + '', + ...(options.metaMessages ? [...options.metaMessages, ''] : []), + ...(docsPath + ? [ + `Docs: ${this.docsBaseUrl}${docsPath}.html${ + options.docsSlug ? `#${options.docsSlug}` : '' + }`, + ] + : []), + ...(details ? [`Details: ${details}`] : []), + `Version: ${this.version}`, + ].join('\n') + + if (options.cause) this.cause = options.cause + this.details = details + this.docsPath = docsPath + this.metaMessages = options.metaMessages + this.shortMessage = shortMessage + } + + walk(fn?: (err: unknown) => boolean) { + return this.#walk(this, fn) + } + + #walk(err: unknown, fn?: (err: unknown) => boolean): unknown { + if (fn?.(err)) return err + if ((err as Error).cause) return this.#walk((err as Error).cause, fn) + return err + } +} diff --git a/packages/core/src/errors/config.test.ts b/packages/core/src/errors/config.test.ts new file mode 100644 index 0000000000..1ec07fd107 --- /dev/null +++ b/packages/core/src/errors/config.test.ts @@ -0,0 +1,68 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { + ChainNotConfiguredError, + ConnectorAccountNotFoundError, + ConnectorAlreadyConnectedError, + ConnectorChainMismatchError, + ConnectorNotConnectedError, + ConnectorNotFoundError, + ConnectorUnavailableReconnectingError, +} from './config.js' + +test('constructors', () => { + expect(new ChainNotConfiguredError()).toMatchInlineSnapshot(` + [ChainNotConfiguredError: Chain not configured. + + Version: @wagmi/core@x.y.z] + `) + expect(new ConnectorAlreadyConnectedError()).toMatchInlineSnapshot(` + [ConnectorAlreadyConnectedError: Connector already connected. + + Version: @wagmi/core@x.y.z] + `) + expect(new ConnectorNotConnectedError()).toMatchInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) + expect(new ConnectorNotFoundError()).toMatchInlineSnapshot(` + [ConnectorNotFoundError: Connector not found. + + Version: @wagmi/core@x.y.z] + `) + expect( + new ConnectorAccountNotFoundError({ + address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + connector: config.connectors[0]!, + }), + ).toMatchInlineSnapshot(` + [ConnectorAccountNotFoundError: Account "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e" not found for connector "Mock Connector". + + Version: @wagmi/core@x.y.z] + `) + expect( + new ConnectorChainMismatchError({ + connectionChainId: 1, + connectorChainId: 123, + }), + ).toMatchInlineSnapshot(` + [ConnectorChainMismatchError: The current chain of the connector (id: 123) does not match the connection's chain (id: 1). + + Current Chain ID: 123 + Expected Chain ID: 1 + + Version: @wagmi/core@x.y.z] + `) + expect( + new ConnectorUnavailableReconnectingError({ + connector: { name: 'Rabby Wallet' }, + }), + ).toMatchInlineSnapshot(` + [ConnectorUnavailableReconnectingError: Connector "Rabby Wallet" unavailable while reconnecting. + + Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/errors/config.ts b/packages/core/src/errors/config.ts new file mode 100644 index 0000000000..2956f0132c --- /dev/null +++ b/packages/core/src/errors/config.ts @@ -0,0 +1,103 @@ +import type { Address } from 'viem' + +import type { Connector } from '../createConfig.js' +import { BaseError } from './base.js' + +export type ChainNotConfiguredErrorType = ChainNotConfiguredError & { + name: 'ChainNotConfiguredError' +} +export class ChainNotConfiguredError extends BaseError { + override name = 'ChainNotConfiguredError' + constructor() { + super('Chain not configured.') + } +} + +export type ConnectorAlreadyConnectedErrorType = + ConnectorAlreadyConnectedError & { + name: 'ConnectorAlreadyConnectedError' + } +export class ConnectorAlreadyConnectedError extends BaseError { + override name = 'ConnectorAlreadyConnectedError' + constructor() { + super('Connector already connected.') + } +} + +export type ConnectorNotConnectedErrorType = ConnectorNotConnectedError & { + name: 'ConnectorNotConnectedError' +} +export class ConnectorNotConnectedError extends BaseError { + override name = 'ConnectorNotConnectedError' + constructor() { + super('Connector not connected.') + } +} + +export type ConnectorNotFoundErrorType = ConnectorNotFoundError & { + name: 'ConnectorNotFoundError' +} +export class ConnectorNotFoundError extends BaseError { + override name = 'ConnectorNotFoundError' + constructor() { + super('Connector not found.') + } +} + +export type ConnectorAccountNotFoundErrorType = + ConnectorAccountNotFoundError & { + name: 'ConnectorAccountNotFoundError' + } +export class ConnectorAccountNotFoundError extends BaseError { + override name = 'ConnectorAccountNotFoundError' + constructor({ + address, + connector, + }: { + address: Address + connector: Connector + }) { + super(`Account "${address}" not found for connector "${connector.name}".`) + } +} + +export type ConnectorChainMismatchErrorType = ConnectorAccountNotFoundError & { + name: 'ConnectorChainMismatchError' +} +export class ConnectorChainMismatchError extends BaseError { + override name = 'ConnectorChainMismatchError' + constructor({ + connectionChainId, + connectorChainId, + }: { + connectionChainId: number + connectorChainId: number + }) { + super( + `The current chain of the connector (id: ${connectorChainId}) does not match the connection's chain (id: ${connectionChainId}).`, + { + metaMessages: [ + `Current Chain ID: ${connectorChainId}`, + `Expected Chain ID: ${connectionChainId}`, + ], + }, + ) + } +} + +export type ConnectorUnavailableReconnectingErrorType = + ConnectorUnavailableReconnectingError & { + name: 'ConnectorUnavailableReconnectingError' + } +export class ConnectorUnavailableReconnectingError extends BaseError { + override name = 'ConnectorUnavailableReconnectingError' + constructor({ connector }: { connector: { name: string } }) { + super(`Connector "${connector.name}" unavailable while reconnecting.`, { + details: [ + 'During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.', + 'All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.', + 'This error commonly occurs for connectors that asynchronously inject after reconnection has already started.', + ].join(' '), + }) + } +} diff --git a/packages/core/src/errors/connector.test.ts b/packages/core/src/errors/connector.test.ts new file mode 100644 index 0000000000..258ef834cd --- /dev/null +++ b/packages/core/src/errors/connector.test.ts @@ -0,0 +1,24 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { + ProviderNotFoundError, + SwitchChainNotSupportedError, +} from './connector.js' + +test('constructors', () => { + expect(new ProviderNotFoundError()).toMatchInlineSnapshot(` + [ProviderNotFoundError: Provider not found. + + Version: @wagmi/core@x.y.z] + `) + expect( + new SwitchChainNotSupportedError({ + connector: config.connectors[0]!, + }), + ).toMatchInlineSnapshot(` + [SwitchChainNotSupportedError: "Mock Connector" does not support programmatic chain switching. + + Version: @wagmi/core@x.y.z] + `) +}) diff --git a/packages/core/src/errors/connector.ts b/packages/core/src/errors/connector.ts new file mode 100644 index 0000000000..c6c30ef1a5 --- /dev/null +++ b/packages/core/src/errors/connector.ts @@ -0,0 +1,23 @@ +import type { Connector } from '../createConfig.js' +import { BaseError } from './base.js' + +export type ProviderNotFoundErrorType = ProviderNotFoundError & { + name: 'ProviderNotFoundError' +} +export class ProviderNotFoundError extends BaseError { + override name = 'ProviderNotFoundError' + constructor() { + super('Provider not found.') + } +} + +export type SwitchChainNotSupportedErrorType = SwitchChainNotSupportedError & { + name: 'SwitchChainNotSupportedError' +} +export class SwitchChainNotSupportedError extends BaseError { + override name = 'SwitchChainNotSupportedError' + + constructor({ connector }: { connector: Connector }) { + super(`"${connector.name}" does not support programmatic chain switching.`) + } +} diff --git a/packages/core/src/experimental/actions/writeContracts.test.ts b/packages/core/src/experimental/actions/writeContracts.test.ts new file mode 100644 index 0000000000..b74cd780ef --- /dev/null +++ b/packages/core/src/experimental/actions/writeContracts.test.ts @@ -0,0 +1,99 @@ +import { abi, address, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connect } from '../../actions/connect.js' +import { disconnect } from '../../actions/disconnect.js' +import { writeContracts } from './writeContracts.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + await expect( + writeContracts(config, { + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).resolves.toMatchInlineSnapshot( + ` + { + "id": "0x8913636bd97cf4bcc0a6343c730905a27ead0f7480ff82190072e916439eb212", + } + `, + ) + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + await expect( + writeContracts(config, { + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorNotConnectedError: Connector not connected. + + Version: @wagmi/core@x.y.z] + `) +}) + +test('behavior: account does not exist on connector', async () => { + await connect(config, { connector }) + await expect( + writeContracts(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ConnectorAccountNotFoundError: Account "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e" not found for connector "Mock Connector". + + Version: @wagmi/core@x.y.z] + `) + await disconnect(config, { connector }) +}) diff --git a/packages/core/src/experimental/actions/writeContracts.ts b/packages/core/src/experimental/actions/writeContracts.ts new file mode 100644 index 0000000000..06e6687592 --- /dev/null +++ b/packages/core/src/experimental/actions/writeContracts.ts @@ -0,0 +1,78 @@ +import type { Account, Chain, ContractFunctionParameters } from 'viem' +import { + type WriteContractsErrorType as viem_WriteContractsErrorType, + type WriteContractsParameters as viem_WriteContractsParameters, + type WriteContractsReturnType as viem_WriteContractsReturnType, + writeContracts as viem_writeContracts, +} from 'viem/experimental' + +import { + type GetConnectorClientErrorType, + getConnectorClient, +} from '../../actions/getConnectorClient.js' +import type { Config } from '../../createConfig.js' +import type { BaseErrorType, ErrorType } from '../../errors/base.js' +import type { SelectChains } from '../../types/chain.js' +import type { + ChainIdParameter, + ConnectorParameter, +} from '../../types/properties.js' +import type { Compute } from '../../types/utils.js' + +export type WriteContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + /// + chains extends readonly Chain[] = SelectChains, +> = { + [key in keyof chains]: Compute< + Omit< + viem_WriteContractsParameters< + contracts, + chains[key], + Account, + chains[key] + >, + 'chain' + > & + ChainIdParameter & + ConnectorParameter + > +}[number] + +export type WriteContractsReturnType = viem_WriteContractsReturnType + +export type WriteContractsErrorType = + // getConnectorClient() + | GetConnectorClientErrorType + // base + | BaseErrorType + | ErrorType + // viem + | viem_WriteContractsErrorType + +/** https://wagmi.sh/core/api/actions/writeContracts */ +export async function writeContracts< + const contracts extends readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + parameters: WriteContractsParameters, +): Promise { + const { account, chainId, connector, ...rest } = parameters + + const client = await getConnectorClient(config, { + account, + chainId, + connector, + }) + + return viem_writeContracts(client, { + ...(rest as any), + ...(account ? { account } : {}), + chain: chainId ? { id: chainId } : undefined, + }) +} diff --git a/packages/core/src/experimental/query/writeContracts.test.ts b/packages/core/src/experimental/query/writeContracts.test.ts new file mode 100644 index 0000000000..5f4a1f28dd --- /dev/null +++ b/packages/core/src/experimental/query/writeContracts.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { writeContractsMutationOptions } from './writeContracts.js' + +test('default', () => { + expect(writeContractsMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "writeContracts", + ], + } + `) +}) diff --git a/packages/core/src/experimental/query/writeContracts.ts b/packages/core/src/experimental/query/writeContracts.ts new file mode 100644 index 0000000000..192a842ca0 --- /dev/null +++ b/packages/core/src/experimental/query/writeContracts.ts @@ -0,0 +1,70 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import type { Config } from '../../createConfig.js' +import type { Compute } from '../../types/utils.js' +import { + type WriteContractsErrorType, + type WriteContractsParameters, + type WriteContractsReturnType, + writeContracts, +} from '../actions/writeContracts.js' + +export function writeContractsMutationOptions< + const contracts extends readonly unknown[], + config extends Config, +>(config: config) { + return { + mutationFn(variables) { + return writeContracts(config, variables as any) as any + }, + mutationKey: ['writeContracts'], + } as const satisfies MutationOptions< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables + > +} + +export type WriteContractsData = Compute + +export type WriteContractsVariables< + contracts extends readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], +> = WriteContractsParameters + +export type WriteContractsMutate< + contracts extends readonly unknown[], + config extends Config, + context = unknown, +> = ( + variables: WriteContractsVariables, + options?: + | Compute< + MutateOptions< + WriteContractsData, + WriteContractsErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type WriteContractsMutateAsync< + contracts extends readonly unknown[], + config extends Config, + context = unknown, +> = ( + variables: WriteContractsVariables, + options?: + | Compute< + MutateOptions< + WriteContractsData, + WriteContractsErrorType, + Compute>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/exports/actions.test.ts b/packages/core/src/exports/actions.test.ts new file mode 100644 index 0000000000..eaaedba14f --- /dev/null +++ b/packages/core/src/exports/actions.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from 'vitest' + +import * as actions from './actions.js' + +test('exports', () => { + expect(Object.keys(actions)).toMatchInlineSnapshot(` + [ + "call", + "connect", + "deployContract", + "disconnect", + "estimateGas", + "estimateFeesPerGas", + "estimateMaxPriorityFeePerGas", + "getAccount", + "getBalance", + "fetchBalance", + "getBlock", + "getBlockNumber", + "fetchBlockNumber", + "getBlockTransactionCount", + "getBytecode", + "getCallsStatus", + "getCapabilities", + "getChainId", + "getChains", + "getClient", + "getConnections", + "getConnectors", + "getConnectorClient", + "getEnsAddress", + "fetchEnsAddress", + "getEnsAvatar", + "fetchEnsAvatar", + "getEnsName", + "fetchEnsName", + "getEnsResolver", + "fetchEnsResolver", + "getEnsText", + "getFeeHistory", + "getGasPrice", + "getProof", + "getPublicClient", + "getStorageAt", + "getToken", + "fetchToken", + "getTransaction", + "fetchTransaction", + "getTransactionConfirmations", + "getTransactionCount", + "getTransactionReceipt", + "getWalletClient", + "multicall", + "prepareTransactionRequest", + "readContract", + "readContracts", + "reconnect", + "sendCalls", + "sendTransaction", + "showCallsStatus", + "signMessage", + "signTypedData", + "simulateContract", + "switchAccount", + "switchChain", + "switchNetwork", + "verifyMessage", + "verifyTypedData", + "waitForCallsStatus", + "watchAccount", + "watchAsset", + "watchBlocks", + "watchBlockNumber", + "watchChainId", + "watchClient", + "watchConnections", + "watchConnectors", + "watchContractEvent", + "watchPendingTransactions", + "watchPublicClient", + "waitForTransactionReceipt", + "waitForTransaction", + "writeContract", + ] + `) +}) diff --git a/packages/core/src/exports/actions.ts b/packages/core/src/exports/actions.ts new file mode 100644 index 0000000000..d03c2adb76 --- /dev/null +++ b/packages/core/src/exports/actions.ts @@ -0,0 +1,460 @@ +//////////////////////////////////////////////////////////////////////////////// +// Actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type CallErrorType, + type CallParameters, + type CallReturnType, + call, +} from '../actions/call.js' + +export { + type ConnectErrorType, + type ConnectParameters, + type ConnectReturnType, + connect, +} from '../actions/connect.js' + +export { + type DeployContractErrorType, + type DeployContractParameters, + type DeployContractReturnType, + deployContract, +} from '../actions/deployContract.js' + +export { + type DisconnectErrorType, + type DisconnectParameters, + type DisconnectReturnType, + disconnect, +} from '../actions/disconnect.js' + +export { + type EstimateGasErrorType, + type EstimateGasParameters, + type EstimateGasReturnType, + estimateGas, +} from '../actions/estimateGas.js' + +export { + type EstimateFeesPerGasErrorType, + type EstimateFeesPerGasParameters, + type EstimateFeesPerGasReturnType, + estimateFeesPerGas, +} from '../actions/estimateFeesPerGas.js' + +export { + type EstimateMaxPriorityFeePerGasErrorType, + type EstimateMaxPriorityFeePerGasParameters, + type EstimateMaxPriorityFeePerGasReturnType, + estimateMaxPriorityFeePerGas, +} from '../actions/estimateMaxPriorityFeePerGas.js' + +export { + type GetAccountReturnType, + getAccount, +} from '../actions/getAccount.js' + +export { + type GetBalanceParameters, + type GetBalanceReturnType, + type GetBalanceErrorType, + getBalance, + /** @deprecated use `getBalance` instead */ + getBalance as fetchBalance, +} from '../actions/getBalance.js' + +export { + type GetBlockErrorType, + type GetBlockParameters, + type GetBlockReturnType, + getBlock, +} from '../actions/getBlock.js' + +export { + type GetBlockNumberErrorType, + type GetBlockNumberParameters, + type GetBlockNumberReturnType, + getBlockNumber, + /** @deprecated use `getBlockNumber` instead */ + getBlockNumber as fetchBlockNumber, +} from '../actions/getBlockNumber.js' + +export { + type GetBlockTransactionCountErrorType, + type GetBlockTransactionCountParameters, + type GetBlockTransactionCountReturnType, + getBlockTransactionCount, +} from '../actions/getBlockTransactionCount.js' + +export { + type GetBytecodeErrorType, + type GetBytecodeParameters, + type GetBytecodeReturnType, + getBytecode, +} from '../actions/getBytecode.js' + +export { + type GetCallsStatusErrorType, + type GetCallsStatusParameters, + type GetCallsStatusReturnType, + getCallsStatus, +} from '../actions/getCallsStatus.js' + +export { + type GetCapabilitiesErrorType, + type GetCapabilitiesParameters, + type GetCapabilitiesReturnType, + getCapabilities, +} from '../actions/getCapabilities.js' + +export { + type GetChainIdReturnType, + getChainId, +} from '../actions/getChainId.js' + +export { + type GetChainsReturnType, + getChains, +} from '../actions/getChains.js' + +export { + type GetClientParameters, + type GetClientReturnType, + getClient, +} from '../actions/getClient.js' + +export { + type GetConnectionsReturnType, + getConnections, +} from '../actions/getConnections.js' + +export { + type GetConnectorsReturnType, + getConnectors, +} from '../actions/getConnectors.js' + +export { + type GetConnectorClientErrorType, + type GetConnectorClientParameters, + type GetConnectorClientReturnType, + getConnectorClient, +} from '../actions/getConnectorClient.js' + +export { + type GetEnsAddressErrorType, + type GetEnsAddressParameters, + type GetEnsAddressReturnType, + getEnsAddress, + /** @deprecated use `getEnsAddress` instead */ + getEnsAddress as fetchEnsAddress, +} from '../actions/getEnsAddress.js' + +export { + type GetEnsAvatarErrorType, + type GetEnsAvatarParameters, + type GetEnsAvatarReturnType, + getEnsAvatar, + /** @deprecated use `getEnsAvatar` instead */ + getEnsAvatar as fetchEnsAvatar, +} from '../actions/getEnsAvatar.js' + +export { + type GetEnsNameErrorType, + type GetEnsNameParameters, + type GetEnsNameReturnType, + getEnsName, + /** @deprecated */ + getEnsName as fetchEnsName, +} from '../actions/getEnsName.js' + +export { + type GetEnsResolverErrorType, + type GetEnsResolverParameters, + type GetEnsResolverReturnType, + getEnsResolver, + /** @deprecated use `getEnsResolver` instead */ + getEnsResolver as fetchEnsResolver, +} from '../actions/getEnsResolver.js' + +export { + type GetEnsTextErrorType, + type GetEnsTextParameters, + type GetEnsTextReturnType, + getEnsText, +} from '../actions/getEnsText.js' + +export { + type GetFeeHistoryErrorType, + type GetFeeHistoryParameters, + type GetFeeHistoryReturnType, + getFeeHistory, +} from '../actions/getFeeHistory.js' + +export { + type GetGasPriceErrorType, + type GetGasPriceParameters, + type GetGasPriceReturnType, + getGasPrice, +} from '../actions/getGasPrice.js' + +export { + type GetProofErrorType, + type GetProofParameters, + type GetProofReturnType, + getProof, +} from '../actions/getProof.js' + +export { + type GetPublicClientParameters, + type GetPublicClientReturnType, + getPublicClient, +} from '../actions/getPublicClient.js' + +export { + type GetStorageAtErrorType, + type GetStorageAtParameters, + type GetStorageAtReturnType, + getStorageAt, +} from '../actions/getStorageAt.js' + +export { + type GetTokenErrorType, + type GetTokenParameters, + type GetTokenReturnType, + getToken, + /** @deprecated use `getToken` instead */ + getToken as fetchToken, +} from '../actions/getToken.js' + +export { + type GetTransactionErrorType, + type GetTransactionParameters, + type GetTransactionReturnType, + getTransaction, + /** @deprecated use `getTransaction` instead */ + getTransaction as fetchTransaction, +} from '../actions/getTransaction.js' + +export { + type GetTransactionConfirmationsErrorType, + type GetTransactionConfirmationsParameters, + type GetTransactionConfirmationsReturnType, + getTransactionConfirmations, +} from '../actions/getTransactionConfirmations.js' + +export { + type GetTransactionCountErrorType, + type GetTransactionCountParameters, + type GetTransactionCountReturnType, + getTransactionCount, +} from '../actions/getTransactionCount.js' + +export { + type GetTransactionReceiptErrorType, + type GetTransactionReceiptParameters, + type GetTransactionReceiptReturnType, + getTransactionReceipt, +} from '../actions/getTransactionReceipt.js' + +export { + type GetWalletClientErrorType, + type GetWalletClientParameters, + type GetWalletClientReturnType, + getWalletClient, +} from '../actions/getWalletClient.js' + +export { + type MulticallParameters, + type MulticallReturnType, + multicall, +} from '../actions/multicall.js' + +export { + type PrepareTransactionRequestParameters, + type PrepareTransactionRequestReturnType, + type PrepareTransactionRequestErrorType, + prepareTransactionRequest, +} from '../actions/prepareTransactionRequest.js' + +export { + type ReadContractParameters, + type ReadContractReturnType, + type ReadContractErrorType, + readContract, +} from '../actions/readContract.js' + +export { + type ReadContractsParameters, + type ReadContractsReturnType, + type ReadContractsErrorType, + readContracts, +} from '../actions/readContracts.js' + +export { + type ReconnectErrorType, + type ReconnectParameters, + type ReconnectReturnType, + reconnect, +} from '../actions/reconnect.js' + +export { + type SendCallsErrorType, + type SendCallsParameters, + type SendCallsReturnType, + sendCalls, +} from '../actions/sendCalls.js' + +export { + type SendTransactionErrorType, + type SendTransactionParameters, + type SendTransactionReturnType, + sendTransaction, +} from '../actions/sendTransaction.js' + +export { + type ShowCallsStatusErrorType, + type ShowCallsStatusParameters, + type ShowCallsStatusReturnType, + showCallsStatus, +} from '../actions/showCallsStatus.js' + +export { + type SignMessageErrorType, + type SignMessageParameters, + type SignMessageReturnType, + signMessage, +} from '../actions/signMessage.js' + +export { + type SignTypedDataErrorType, + type SignTypedDataParameters, + type SignTypedDataReturnType, + signTypedData, +} from '../actions/signTypedData.js' + +export { + type SimulateContractErrorType, + type SimulateContractParameters, + type SimulateContractReturnType, + simulateContract, +} from '../actions/simulateContract.js' + +export { + type SwitchAccountErrorType, + type SwitchAccountParameters, + type SwitchAccountReturnType, + switchAccount, +} from '../actions/switchAccount.js' + +export { + type SwitchChainErrorType, + type SwitchChainParameters, + type SwitchChainReturnType, + switchChain, + /** @deprecated use `switchChain` instead */ + switchChain as switchNetwork, +} from '../actions/switchChain.js' + +export { + type VerifyMessageParameters, + type VerifyMessageReturnType, + verifyMessage, +} from '../actions/verifyMessage.js' + +export { + type VerifyTypedDataParameters, + type VerifyTypedDataReturnType, + verifyTypedData, +} from '../actions/verifyTypedData.js' + +export { + type WaitForCallsStatusErrorType, + type WaitForCallsStatusParameters, + type WaitForCallsStatusReturnType, + waitForCallsStatus, +} from '../actions/waitForCallsStatus.js' + +export { + type WatchAccountParameters, + type WatchAccountReturnType, + watchAccount, +} from '../actions/watchAccount.js' + +export { + type WatchAssetParameters, + type WatchAssetReturnType, + watchAsset, +} from '../actions/watchAsset.js' + +export { + type WatchBlocksParameters, + type WatchBlocksReturnType, + watchBlocks, +} from '../actions/watchBlocks.js' + +export { + type WatchBlockNumberParameters, + type WatchBlockNumberReturnType, + watchBlockNumber, +} from '../actions/watchBlockNumber.js' + +export { + type WatchChainIdParameters, + type WatchChainIdReturnType, + watchChainId, +} from '../actions/watchChainId.js' + +export { + type WatchClientParameters, + type WatchClientReturnType, + watchClient, +} from '../actions/watchClient.js' + +export { + type WatchConnectionsParameters, + type WatchConnectionsReturnType, + watchConnections, +} from '../actions/watchConnections.js' + +export { + type WatchConnectorsParameters, + type WatchConnectorsReturnType, + watchConnectors, +} from '../actions/watchConnectors.js' + +export { + type WatchContractEventParameters, + type WatchContractEventReturnType, + watchContractEvent, +} from '../actions/watchContractEvent.js' + +export { + type WatchPendingTransactionsParameters, + type WatchPendingTransactionsReturnType, + watchPendingTransactions, +} from '../actions/watchPendingTransactions.js' + +export { + type WatchPublicClientParameters, + type WatchPublicClientReturnType, + watchPublicClient, +} from '../actions/watchPublicClient.js' + +export { + type WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptParameters, + type WaitForTransactionReceiptReturnType, + waitForTransactionReceipt, + /** @deprecated use `waitForTransactionReceipt` instead */ + waitForTransactionReceipt as waitForTransaction, +} from '../actions/waitForTransactionReceipt.js' + +export { + type WriteContractErrorType, + type WriteContractParameters, + type WriteContractReturnType, + writeContract, +} from '../actions/writeContract.js' diff --git a/packages/core/src/exports/chains.ts b/packages/core/src/exports/chains.ts new file mode 100644 index 0000000000..1fca7f537f --- /dev/null +++ b/packages/core/src/exports/chains.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// viem/chains +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from 'viem/chains' diff --git a/packages/core/src/exports/codegen.test.ts b/packages/core/src/exports/codegen.test.ts new file mode 100644 index 0000000000..c947b7d188 --- /dev/null +++ b/packages/core/src/exports/codegen.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from 'vitest' + +import * as codegen from './codegen.js' + +test('exports', () => { + expect(Object.keys(codegen)).toMatchInlineSnapshot(` + [ + "createSimulateContract", + "createReadContract", + "createWatchContractEvent", + "createWriteContract", + ] + `) +}) diff --git a/packages/core/src/exports/codegen.ts b/packages/core/src/exports/codegen.ts new file mode 100644 index 0000000000..bed8aa1cc7 --- /dev/null +++ b/packages/core/src/exports/codegen.ts @@ -0,0 +1,24 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type CreateSimulateContractParameters, + type CreateSimulateContractReturnType, + createSimulateContract, +} from '../actions/codegen/createSimulateContract.js' + +export { + type CreateReadContractParameters, + type CreateReadContractReturnType, + createReadContract, +} from '../actions/codegen/createReadContract.js' + +export { + type CreateWatchContractEventParameters, + type CreateWatchContractEventReturnType, + createWatchContractEvent, +} from '../actions/codegen/createWatchContractEvent.js' + +export { + type CreateWriteContractParameters, + type CreateWriteContractReturnType, + createWriteContract, +} from '../actions/codegen/createWriteContract.js' diff --git a/packages/core/src/exports/experimental.ts b/packages/core/src/exports/experimental.ts new file mode 100644 index 0000000000..f74baa82cb --- /dev/null +++ b/packages/core/src/exports/experimental.ts @@ -0,0 +1,158 @@ +//////////////////////////////////////////////////////////////////////////////// +// Actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusErrorType } from '@wagmi/core'` instead. */ + type GetCallsStatusErrorType, + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusParameters } from '@wagmi/core'` instead. */ + type GetCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusReturnType } from '@wagmi/core'` instead. */ + type GetCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { getCallsStatus } from '@wagmi/core'` instead. */ + getCallsStatus, +} from '../actions/getCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesErrorType } from '@wagmi/core'` instead. */ + type GetCapabilitiesErrorType, + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesParameters } from '@wagmi/core'` instead. */ + type GetCapabilitiesParameters, + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesReturnType } from '@wagmi/core'` instead. */ + type GetCapabilitiesReturnType, + /** @deprecated This is no longer experimental – use `import { getCapabilities } from '@wagmi/core'` instead. */ + getCapabilities, +} from '../actions/getCapabilities.js' + +export { + /** @deprecated This is no longer experimental – use `import type { SendCallsErrorType } from '@wagmi/core'` instead. */ + type SendCallsErrorType, + /** @deprecated This is no longer experimental – use `import type { SendCallsParameters } from '@wagmi/core'` instead. */ + type SendCallsParameters, + /** @deprecated This is no longer experimental – use `import type { SendCallsReturnType } from '@wagmi/core'` instead. */ + type SendCallsReturnType, + /** @deprecated This is no longer experimental – use `import { sendCalls } from '@wagmi/core'` instead. */ + sendCalls, +} from '../actions/sendCalls.js' + +export { + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusErrorType } from '@wagmi/core'` instead. */ + type ShowCallsStatusErrorType, + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusParameters } from '@wagmi/core'` instead. */ + type ShowCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusReturnType } from '@wagmi/core'` instead. */ + type ShowCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { showCallsStatus } from '@wagmi/core'` instead. */ + showCallsStatus, +} from '../actions/showCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusErrorType } from '@wagmi/core'` instead. */ + type WaitForCallsStatusErrorType, + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusParameters } from '@wagmi/core'` instead. */ + type WaitForCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusReturnType } from '@wagmi/core'` instead. */ + type WaitForCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { waitForCallsStatus } from '@wagmi/core'` instead. */ + waitForCallsStatus, +} from '../actions/waitForCallsStatus.js' + +export { + /** @deprecated Use `SendCallsErrorType` instead. */ + type WriteContractsErrorType, + /** @deprecated Use `SendCallsParameters` instead. */ + type WriteContractsParameters, + /** @deprecated Use `SendCallsReturnType` instead. */ + type WriteContractsReturnType, + /** @deprecated Use `sendCalls` instead. */ + writeContracts, +} from '../experimental/actions/writeContracts.js' + +//////////////////////////////////////////////////////////////////////////////// +// Tanstack Query +//////////////////////////////////////////////////////////////////////////////// + +export { + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusData } from '@wagmi/core/query'` instead. */ + type GetCallsStatusData, + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusOptions } from '@wagmi/core/query'` instead. */ + type GetCallsStatusOptions, + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusQueryFnData } from '@wagmi/core/query'` instead. */ + type GetCallsStatusQueryFnData, + /** @deprecated This is no longer experimental – use `import type { GetCallsStatusQueryKey } from '@wagmi/core/query'` instead. */ + type GetCallsStatusQueryKey, + /** @deprecated This is no longer experimental – use `import { getCallsStatusQueryOptions } from '@wagmi/core/query'` instead. */ + getCallsStatusQueryOptions, + /** @deprecated This is no longer experimental – use `import { getCallsStatusQueryKey } from '@wagmi/core/query'` instead. */ + getCallsStatusQueryKey, +} from '../query/getCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesData } from '@wagmi/core/query'` instead. */ + type GetCapabilitiesData, + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesOptions } from '@wagmi/core/query'` instead. */ + type GetCapabilitiesOptions, + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesQueryFnData } from '@wagmi/core/query'` instead. */ + type GetCapabilitiesQueryFnData, + /** @deprecated This is no longer experimental – use `import type { GetCapabilitiesQueryKey } from '@wagmi/core/query'` instead. */ + type GetCapabilitiesQueryKey, + /** @deprecated This is no longer experimental – use `import { getCapabilitiesQueryOptions } from '@wagmi/core/query'` instead. */ + getCapabilitiesQueryOptions, + /** @deprecated This is no longer experimental – use `import { getCapabilitiesQueryKey } from '@wagmi/core/query'` instead. */ + getCapabilitiesQueryKey, +} from '../query/getCapabilities.js' + +export { + /** @deprecated This is no longer experimental – use `import type { SendCallsData } from '@wagmi/core/query'` instead. */ + type SendCallsData, + /** @deprecated This is no longer experimental – use `import type { SendCallsMutate } from '@wagmi/core/query'` instead. */ + type SendCallsMutate, + /** @deprecated This is no longer experimental – use `import type { SendCallsMutateAsync } from '@wagmi/core/query'` instead. */ + type SendCallsMutateAsync, + /** @deprecated This is no longer experimental – use `import type { SendCallsVariables } from '@wagmi/core/query'` instead. */ + type SendCallsVariables, + /** @deprecated This is no longer experimental – use `import { sendCallsMutationOptions } from '@wagmi/core/query'` instead. */ + sendCallsMutationOptions, +} from '../query/sendCalls.js' + +export { + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusData } from '@wagmi/core/query'` instead. */ + type ShowCallsStatusData, + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusMutate } from '@wagmi/core/query'` instead. */ + type ShowCallsStatusMutate, + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusMutateAsync } from '@wagmi/core/query'` instead. */ + type ShowCallsStatusMutateAsync, + /** @deprecated This is no longer experimental – use `import type { ShowCallsStatusVariables } from '@wagmi/core/query'` instead. */ + type ShowCallsStatusVariables, + /** @deprecated This is no longer experimental – use `import { showCallsStatusMutationOptions } from '@wagmi/core/query'` instead. */ + showCallsStatusMutationOptions, +} from '../query/showCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusData } from '@wagmi/core/query'` instead. */ + type WaitForCallsStatusData, + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusOptions } from '@wagmi/core/query'` instead. */ + type WaitForCallsStatusOptions, + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusQueryFnData } from '@wagmi/core/query'` instead. */ + type WaitForCallsStatusQueryFnData, + /** @deprecated This is no longer experimental – use `import type { WaitForCallsStatusQueryKey } from '@wagmi/core/query'` instead. */ + type WaitForCallsStatusQueryKey, + /** @deprecated This is no longer experimental – use `import { waitForCallsStatusQueryKey } from '@wagmi/core/query'` instead. */ + waitForCallsStatusQueryKey, + /** @deprecated This is no longer experimental – use `import { waitForCallsStatusQueryOptions } from '@wagmi/core/query'` instead. */ + waitForCallsStatusQueryOptions, +} from '../query/waitForCallsStatus.js' + +export { + /** @deprecated Use `SendCallsData` instead. */ + type WriteContractsData, + /** @deprecated Use `SendCallsMutate` instead. */ + type WriteContractsMutate, + /** @deprecated Use `SendCallsMutateAsync` instead. */ + type WriteContractsMutateAsync, + /** @deprecated Use `SendCallsVariables` instead. */ + type WriteContractsVariables, + /** @deprecated Use `sendCallsMutationOptions` instead. */ + writeContractsMutationOptions, +} from '../experimental/query/writeContracts.js' diff --git a/packages/core/src/exports/index.test.ts b/packages/core/src/exports/index.test.ts new file mode 100644 index 0000000000..55f3885806 --- /dev/null +++ b/packages/core/src/exports/index.test.ts @@ -0,0 +1,117 @@ +import { expect, test } from 'vitest' + +import * as core from './index.js' + +test('exports', () => { + expect(Object.keys(core)).toMatchInlineSnapshot(` + [ + "call", + "connect", + "deployContract", + "disconnect", + "estimateGas", + "estimateFeesPerGas", + "estimateMaxPriorityFeePerGas", + "getAccount", + "getBalance", + "fetchBalance", + "getBlock", + "getBlockNumber", + "fetchBlockNumber", + "getBlockTransactionCount", + "getBytecode", + "getCallsStatus", + "getCapabilities", + "getChainId", + "getChains", + "getClient", + "getConnections", + "getConnectors", + "getConnectorClient", + "getEnsAddress", + "fetchEnsAddress", + "getEnsAvatar", + "fetchEnsAvatar", + "getEnsName", + "fetchEnsName", + "getEnsResolver", + "fetchEnsResolver", + "getEnsText", + "getFeeHistory", + "getGasPrice", + "getProof", + "getPublicClient", + "getStorageAt", + "getToken", + "fetchToken", + "getTransaction", + "fetchTransaction", + "getTransactionConfirmations", + "getTransactionCount", + "getTransactionReceipt", + "getWalletClient", + "multicall", + "prepareTransactionRequest", + "readContract", + "readContracts", + "reconnect", + "sendCalls", + "sendTransaction", + "showCallsStatus", + "signMessage", + "signTypedData", + "simulateContract", + "switchAccount", + "switchChain", + "switchNetwork", + "verifyMessage", + "verifyTypedData", + "waitForCallsStatus", + "watchAccount", + "watchAsset", + "watchBlocks", + "watchBlockNumber", + "watchChainId", + "watchClient", + "watchConnections", + "watchConnectors", + "watchContractEvent", + "watchPendingTransactions", + "watchPublicClient", + "waitForTransactionReceipt", + "waitForTransaction", + "writeContract", + "createConnector", + "injected", + "mock", + "createConfig", + "createStorage", + "noopStorage", + "hydrate", + "BaseError", + "ChainNotConfiguredError", + "ConnectorNotConnectedError", + "ConnectorAlreadyConnectedError", + "ConnectorNotFoundError", + "ConnectorAccountNotFoundError", + "ConnectorChainMismatchError", + "ConnectorUnavailableReconnectingError", + "ProviderNotFoundError", + "SwitchChainNotSupportedError", + "custom", + "http", + "webSocket", + "unstable_connector", + "fallback", + "cookieStorage", + "cookieToInitialState", + "parseCookie", + "deepEqual", + "deserialize", + "extractRpcUrls", + "normalizeChainId", + "serialize", + "version", + ] + `) +}) diff --git a/packages/core/src/exports/index.ts b/packages/core/src/exports/index.ts new file mode 100644 index 0000000000..1e0e210ea2 --- /dev/null +++ b/packages/core/src/exports/index.ts @@ -0,0 +1,594 @@ +//////////////////////////////////////////////////////////////////////////////// +// Actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type CallErrorType, + type CallParameters, + type CallReturnType, + call, +} from '../actions/call.js' + +export { + type ConnectErrorType, + type ConnectParameters, + type ConnectReturnType, + connect, +} from '../actions/connect.js' + +export { + type DeployContractErrorType, + type DeployContractParameters, + type DeployContractReturnType, + deployContract, +} from '../actions/deployContract.js' + +export { + type DisconnectErrorType, + type DisconnectParameters, + type DisconnectReturnType, + disconnect, +} from '../actions/disconnect.js' + +export { + type EstimateGasErrorType, + type EstimateGasParameters, + type EstimateGasReturnType, + estimateGas, +} from '../actions/estimateGas.js' + +export { + type EstimateFeesPerGasErrorType, + type EstimateFeesPerGasParameters, + type EstimateFeesPerGasReturnType, + estimateFeesPerGas, +} from '../actions/estimateFeesPerGas.js' + +export { + type EstimateMaxPriorityFeePerGasErrorType, + type EstimateMaxPriorityFeePerGasParameters, + type EstimateMaxPriorityFeePerGasReturnType, + estimateMaxPriorityFeePerGas, +} from '../actions/estimateMaxPriorityFeePerGas.js' + +export { + type GetAccountReturnType, + getAccount, +} from '../actions/getAccount.js' + +export { + type GetBalanceParameters, + type GetBalanceReturnType, + type GetBalanceErrorType, + getBalance, + /** @deprecated use `getBalance` instead */ + getBalance as fetchBalance, +} from '../actions/getBalance.js' + +export { + type GetBlockErrorType, + type GetBlockParameters, + type GetBlockReturnType, + getBlock, +} from '../actions/getBlock.js' + +export { + type GetBlockNumberErrorType, + type GetBlockNumberParameters, + type GetBlockNumberReturnType, + getBlockNumber, + /** @deprecated use `getBlockNumber` instead */ + getBlockNumber as fetchBlockNumber, +} from '../actions/getBlockNumber.js' + +export { + type GetBlockTransactionCountErrorType, + type GetBlockTransactionCountParameters, + type GetBlockTransactionCountReturnType, + getBlockTransactionCount, +} from '../actions/getBlockTransactionCount.js' + +export { + type GetBytecodeErrorType, + type GetBytecodeParameters, + type GetBytecodeReturnType, + getBytecode, +} from '../actions/getBytecode.js' + +export { + type GetCallsStatusErrorType, + type GetCallsStatusParameters, + type GetCallsStatusReturnType, + getCallsStatus, +} from '../actions/getCallsStatus.js' + +export { + type GetCapabilitiesErrorType, + type GetCapabilitiesParameters, + type GetCapabilitiesReturnType, + getCapabilities, +} from '../actions/getCapabilities.js' + +export { + type GetChainIdReturnType, + getChainId, +} from '../actions/getChainId.js' + +export { + type GetChainsReturnType, + getChains, +} from '../actions/getChains.js' + +export { + type GetClientParameters, + type GetClientReturnType, + getClient, +} from '../actions/getClient.js' + +export { + type GetConnectionsReturnType, + getConnections, +} from '../actions/getConnections.js' + +export { + type GetConnectorsReturnType, + getConnectors, +} from '../actions/getConnectors.js' + +export { + type GetConnectorClientErrorType, + type GetConnectorClientParameters, + type GetConnectorClientReturnType, + getConnectorClient, +} from '../actions/getConnectorClient.js' + +export { + type GetEnsAddressErrorType, + type GetEnsAddressParameters, + type GetEnsAddressReturnType, + getEnsAddress, + /** @deprecated use `getEnsAddress` instead */ + getEnsAddress as fetchEnsAddress, +} from '../actions/getEnsAddress.js' + +export { + type GetEnsAvatarErrorType, + type GetEnsAvatarParameters, + type GetEnsAvatarReturnType, + getEnsAvatar, + /** @deprecated use `getEnsAvatar` instead */ + getEnsAvatar as fetchEnsAvatar, +} from '../actions/getEnsAvatar.js' + +export { + type GetEnsNameErrorType, + type GetEnsNameParameters, + type GetEnsNameReturnType, + getEnsName, + /** @deprecated */ + getEnsName as fetchEnsName, +} from '../actions/getEnsName.js' + +export { + type GetEnsResolverErrorType, + type GetEnsResolverParameters, + type GetEnsResolverReturnType, + getEnsResolver, + /** @deprecated use `getEnsResolver` instead */ + getEnsResolver as fetchEnsResolver, +} from '../actions/getEnsResolver.js' + +export { + type GetEnsTextErrorType, + type GetEnsTextParameters, + type GetEnsTextReturnType, + getEnsText, +} from '../actions/getEnsText.js' + +export { + type GetFeeHistoryErrorType, + type GetFeeHistoryParameters, + type GetFeeHistoryReturnType, + getFeeHistory, +} from '../actions/getFeeHistory.js' + +export { + type GetGasPriceErrorType, + type GetGasPriceParameters, + type GetGasPriceReturnType, + getGasPrice, +} from '../actions/getGasPrice.js' + +export { + type GetProofErrorType, + type GetProofParameters, + type GetProofReturnType, + getProof, +} from '../actions/getProof.js' + +export { + type GetPublicClientParameters, + type GetPublicClientReturnType, + getPublicClient, +} from '../actions/getPublicClient.js' + +export { + type GetStorageAtErrorType, + type GetStorageAtParameters, + type GetStorageAtReturnType, + getStorageAt, +} from '../actions/getStorageAt.js' + +export { + type GetTokenErrorType, + type GetTokenParameters, + type GetTokenReturnType, + getToken, + /** @deprecated use `getToken` instead */ + getToken as fetchToken, +} from '../actions/getToken.js' + +export { + type GetTransactionErrorType, + type GetTransactionParameters, + type GetTransactionReturnType, + getTransaction, + /** @deprecated use `getTransaction` instead */ + getTransaction as fetchTransaction, +} from '../actions/getTransaction.js' + +export { + type GetTransactionConfirmationsErrorType, + type GetTransactionConfirmationsParameters, + type GetTransactionConfirmationsReturnType, + getTransactionConfirmations, +} from '../actions/getTransactionConfirmations.js' + +export { + type GetTransactionCountErrorType, + type GetTransactionCountParameters, + type GetTransactionCountReturnType, + getTransactionCount, +} from '../actions/getTransactionCount.js' + +export { + type GetTransactionReceiptErrorType, + type GetTransactionReceiptParameters, + type GetTransactionReceiptReturnType, + getTransactionReceipt, +} from '../actions/getTransactionReceipt.js' + +export { + type GetWalletClientErrorType, + type GetWalletClientParameters, + type GetWalletClientReturnType, + getWalletClient, +} from '../actions/getWalletClient.js' + +export { + type MulticallParameters, + type MulticallReturnType, + multicall, +} from '../actions/multicall.js' + +export { + type PrepareTransactionRequestErrorType, + type PrepareTransactionRequestParameters, + type PrepareTransactionRequestReturnType, + prepareTransactionRequest, +} from '../actions/prepareTransactionRequest.js' + +export { + type ReadContractParameters, + type ReadContractReturnType, + type ReadContractErrorType, + readContract, +} from '../actions/readContract.js' + +export { + type ReadContractsParameters, + type ReadContractsReturnType, + type ReadContractsErrorType, + readContracts, +} from '../actions/readContracts.js' + +export { + type ReconnectErrorType, + type ReconnectParameters, + type ReconnectReturnType, + reconnect, +} from '../actions/reconnect.js' + +export { + type SendCallsErrorType, + type SendCallsParameters, + type SendCallsReturnType, + sendCalls, +} from '../actions/sendCalls.js' + +export { + type SendTransactionErrorType, + type SendTransactionParameters, + type SendTransactionReturnType, + sendTransaction, +} from '../actions/sendTransaction.js' + +export { + type ShowCallsStatusErrorType, + type ShowCallsStatusParameters, + type ShowCallsStatusReturnType, + showCallsStatus, +} from '../actions/showCallsStatus.js' + +export { + type SignMessageErrorType, + type SignMessageParameters, + type SignMessageReturnType, + signMessage, +} from '../actions/signMessage.js' + +export { + type SignTypedDataErrorType, + type SignTypedDataParameters, + type SignTypedDataReturnType, + signTypedData, +} from '../actions/signTypedData.js' + +export { + type SimulateContractErrorType, + type SimulateContractParameters, + type SimulateContractReturnType, + simulateContract, +} from '../actions/simulateContract.js' + +export { + type SwitchAccountErrorType, + type SwitchAccountParameters, + type SwitchAccountReturnType, + switchAccount, +} from '../actions/switchAccount.js' + +export { + type SwitchChainErrorType, + type SwitchChainParameters, + type SwitchChainReturnType, + switchChain, + /** @deprecated use `switchChain` instead */ + switchChain as switchNetwork, +} from '../actions/switchChain.js' + +export { + type VerifyMessageErrorType, + type VerifyMessageParameters, + type VerifyMessageReturnType, + verifyMessage, +} from '../actions/verifyMessage.js' + +export { + type VerifyTypedDataErrorType, + type VerifyTypedDataParameters, + type VerifyTypedDataReturnType, + verifyTypedData, +} from '../actions/verifyTypedData.js' + +export { + type WaitForCallsStatusErrorType, + type WaitForCallsStatusParameters, + type WaitForCallsStatusReturnType, + waitForCallsStatus, +} from '../actions/waitForCallsStatus.js' + +export { + type WatchAccountParameters, + type WatchAccountReturnType, + watchAccount, +} from '../actions/watchAccount.js' + +export { + type WatchAssetParameters, + type WatchAssetErrorType, + type WatchAssetReturnType, + watchAsset, +} from '../actions/watchAsset.js' + +export { + type WatchBlocksParameters, + type WatchBlocksReturnType, + watchBlocks, +} from '../actions/watchBlocks.js' + +export { + type WatchBlockNumberParameters, + type WatchBlockNumberReturnType, + watchBlockNumber, +} from '../actions/watchBlockNumber.js' + +export { + type WatchChainIdParameters, + type WatchChainIdReturnType, + watchChainId, +} from '../actions/watchChainId.js' + +export { + type WatchClientParameters, + type WatchClientReturnType, + watchClient, +} from '../actions/watchClient.js' + +export { + type WatchConnectionsParameters, + type WatchConnectionsReturnType, + watchConnections, +} from '../actions/watchConnections.js' + +export { + type WatchConnectorsParameters, + type WatchConnectorsReturnType, + watchConnectors, +} from '../actions/watchConnectors.js' + +export { + type WatchContractEventParameters, + type WatchContractEventReturnType, + watchContractEvent, +} from '../actions/watchContractEvent.js' + +export { + type WatchPendingTransactionsParameters, + type WatchPendingTransactionsReturnType, + watchPendingTransactions, +} from '../actions/watchPendingTransactions.js' + +export { + type WatchPublicClientParameters, + type WatchPublicClientReturnType, + watchPublicClient, +} from '../actions/watchPublicClient.js' + +export { + type WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptParameters, + type WaitForTransactionReceiptReturnType, + waitForTransactionReceipt, + /** @deprecated use `waitForTransactionReceipt` instead */ + waitForTransactionReceipt as waitForTransaction, +} from '../actions/waitForTransactionReceipt.js' + +export { + type WriteContractErrorType, + type WriteContractParameters, + type WriteContractReturnType, + writeContract, +} from '../actions/writeContract.js' + +//////////////////////////////////////////////////////////////////////////////// +// Connectors +//////////////////////////////////////////////////////////////////////////////// + +export { + type ConnectorEventMap, + type CreateConnectorFn, + createConnector, +} from '../connectors/createConnector.js' + +export { + type InjectedParameters, + injected, +} from '../connectors/injected.js' + +export { + type MockParameters, + mock, +} from '../connectors/mock.js' + +//////////////////////////////////////////////////////////////////////////////// +// createConfig +//////////////////////////////////////////////////////////////////////////////// + +export { + type Connection, + type Connector, + type Config, + type CreateConfigParameters, + type PartializedState, + type State, + type Transport, + createConfig, +} from '../createConfig.js' + +//////////////////////////////////////////////////////////////////////////////// +// createStorage +//////////////////////////////////////////////////////////////////////////////// + +export { + type CreateStorageParameters, + type Storage, + type StorageItemMap, + createStorage, + noopStorage, +} from '../createStorage.js' + +//////////////////////////////////////////////////////////////////////////////// +// Hydrate +//////////////////////////////////////////////////////////////////////////////// + +export { hydrate } from '../hydrate.js' + +//////////////////////////////////////////////////////////////////////////////// +// Errors +//////////////////////////////////////////////////////////////////////////////// + +export { BaseError } from '../errors/base.js' + +export { + type ChainNotConfiguredErrorType, + ChainNotConfiguredError, + type ConnectorNotConnectedErrorType, + ConnectorNotConnectedError, + type ConnectorAlreadyConnectedErrorType, + ConnectorAlreadyConnectedError, + type ConnectorNotFoundErrorType, + ConnectorNotFoundError, + type ConnectorAccountNotFoundErrorType, + ConnectorAccountNotFoundError, + type ConnectorChainMismatchErrorType, + ConnectorChainMismatchError, + type ConnectorUnavailableReconnectingErrorType, + ConnectorUnavailableReconnectingError, +} from '../errors/config.js' + +export { + type ProviderNotFoundErrorType, + ProviderNotFoundError, + type SwitchChainNotSupportedErrorType, + SwitchChainNotSupportedError, +} from '../errors/connector.js' + +//////////////////////////////////////////////////////////////////////////////// +// Transports +//////////////////////////////////////////////////////////////////////////////// + +export { custom, http, webSocket } from 'viem' + +export { + type ConnectorTransportConfig, + type ConnectorTransport, + unstable_connector, +} from '../transports/connector.js' + +export { fallback } from '../transports/fallback.js' + +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// + +export type { SelectChains } from '../types/chain.js' + +export type { Register, ResolvedRegister } from '../types/register.js' + +//////////////////////////////////////////////////////////////////////////////// +// Utilities +//////////////////////////////////////////////////////////////////////////////// + +export { + cookieStorage, + cookieToInitialState, + parseCookie, +} from '../utils/cookie.js' + +export { deepEqual } from '../utils/deepEqual.js' + +export { deserialize } from '../utils/deserialize.js' + +export { extractRpcUrls } from '../utils/extractRpcUrls.js' + +export { normalizeChainId } from '../utils/normalizeChainId.js' + +export { serialize } from '../utils/serialize.js' + +//////////////////////////////////////////////////////////////////////////////// +// Version +//////////////////////////////////////////////////////////////////////////////// + +export { version } from '../version.js' diff --git a/packages/core/src/exports/internal.test.ts b/packages/core/src/exports/internal.test.ts new file mode 100644 index 0000000000..425a1b4eba --- /dev/null +++ b/packages/core/src/exports/internal.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from 'vitest' + +import * as internal from './internal.js' + +test('exports', () => { + expect(Object.keys(internal)).toMatchInlineSnapshot(` + [ + "watchChains", + "Emitter", + "createEmitter", + "deepEqual", + "uid", + ] + `) +}) diff --git a/packages/core/src/exports/internal.ts b/packages/core/src/exports/internal.ts new file mode 100644 index 0000000000..670420d89a --- /dev/null +++ b/packages/core/src/exports/internal.ts @@ -0,0 +1,52 @@ +//////////////////////////////////////////////////////////////////////////////// +// Actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type WatchChainsParameters, + type WatchChainsReturnType, + watchChains, +} from '../actions/watchChains.js' + +//////////////////////////////////////////////////////////////////////////////// +// Emitter +//////////////////////////////////////////////////////////////////////////////// + +export { + type EventData, + Emitter, + createEmitter, +} from '../createEmitter.js' + +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// + +export type { SelectChains } from '../types/chain.js' + +export type { + ChainIdParameter, + ConnectorParameter, + ScopeKeyParameter, +} from '../types/properties.js' + +export type { + Compute, + ExactPartial, + Mutable, + StrictOmit as Omit, + OneOf, + RemoveUndefined, + UnionCompute, + UnionStrictOmit, + UnionExactPartial, +} from '../types/utils.js' + +//////////////////////////////////////////////////////////////////////////////// +// Utilities +//////////////////////////////////////////////////////////////////////////////// + +export { deepEqual } from '../utils/deepEqual.js' + +export { uid } from '../utils/uid.js' diff --git a/packages/core/src/exports/query.test.ts b/packages/core/src/exports/query.test.ts new file mode 100644 index 0000000000..cbdaf925de --- /dev/null +++ b/packages/core/src/exports/query.test.ts @@ -0,0 +1,97 @@ +import { expect, test } from 'vitest' + +import * as query from './query.js' + +test('exports', () => { + expect(Object.keys(query)).toMatchInlineSnapshot(` + [ + "callQueryKey", + "callQueryOptions", + "connectMutationOptions", + "deployContractMutationOptions", + "disconnectMutationOptions", + "estimateFeesPerGasQueryKey", + "estimateFeesPerGasQueryOptions", + "estimateGasQueryKey", + "estimateGasQueryOptions", + "estimateMaxPriorityFeePerGasQueryKey", + "estimateMaxPriorityFeePerGasQueryOptions", + "getBalanceQueryKey", + "getBalanceQueryOptions", + "getBlockQueryKey", + "getBlockQueryOptions", + "getBlockNumberQueryKey", + "getBlockNumberQueryOptions", + "getBlockTransactionCountQueryKey", + "getBlockTransactionCountQueryOptions", + "getBytecodeQueryKey", + "getBytecodeQueryOptions", + "getCallsStatusQueryKey", + "getCallsStatusQueryOptions", + "getCapabilitiesQueryKey", + "getCapabilitiesQueryOptions", + "getConnectorClientQueryKey", + "getConnectorClientQueryOptions", + "getEnsAddressQueryKey", + "getEnsAddressQueryOptions", + "getEnsAvatarQueryKey", + "getEnsAvatarQueryOptions", + "getEnsNameQueryKey", + "getEnsNameQueryOptions", + "getEnsResolverQueryKey", + "getEnsResolverQueryOptions", + "getEnsTextQueryKey", + "getEnsTextQueryOptions", + "getFeeHistoryQueryKey", + "getFeeHistoryQueryOptions", + "getGasPriceQueryKey", + "getGasPriceQueryOptions", + "getProofQueryKey", + "getProofQueryOptions", + "getStorageAtQueryKey", + "getStorageAtQueryOptions", + "getTokenQueryKey", + "getTokenQueryOptions", + "getTransactionQueryKey", + "getTransactionQueryOptions", + "getTransactionConfirmationsQueryKey", + "getTransactionConfirmationsQueryOptions", + "getTransactionCountQueryKey", + "getTransactionCountQueryOptions", + "getTransactionReceiptQueryKey", + "getTransactionReceiptQueryOptions", + "getWalletClientQueryKey", + "getWalletClientQueryOptions", + "infiniteReadContractsQueryKey", + "infiniteReadContractsQueryOptions", + "prepareTransactionRequestQueryKey", + "prepareTransactionRequestQueryOptions", + "readContractQueryKey", + "readContractQueryOptions", + "readContractsQueryKey", + "readContractsQueryOptions", + "reconnectMutationOptions", + "sendCallsMutationOptions", + "showCallsStatusMutationOptions", + "sendTransactionMutationOptions", + "signMessageMutationOptions", + "signTypedDataMutationOptions", + "switchAccountMutationOptions", + "simulateContractQueryKey", + "simulateContractQueryOptions", + "switchChainMutationOptions", + "verifyMessageQueryKey", + "verifyMessageQueryOptions", + "verifyTypedDataQueryKey", + "verifyTypedDataQueryOptions", + "waitForCallsStatusQueryKey", + "waitForCallsStatusQueryOptions", + "waitForTransactionReceiptQueryKey", + "waitForTransactionReceiptQueryOptions", + "watchAssetMutationOptions", + "writeContractMutationOptions", + "hashFn", + "structuralSharing", + ] + `) +}) diff --git a/packages/core/src/exports/query.ts b/packages/core/src/exports/query.ts new file mode 100644 index 0000000000..d689f558d5 --- /dev/null +++ b/packages/core/src/exports/query.ts @@ -0,0 +1,434 @@ +//////////////////////////////////////////////////////////////////////////////// +// Tanstack Query +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type CallData, + type CallOptions, + type CallQueryFnData, + type CallQueryKey, + callQueryKey, + callQueryOptions, +} from '../query/call.js' + +export { + type ConnectData, + type ConnectVariables, + type ConnectMutate, + type ConnectMutateAsync, + connectMutationOptions, +} from '../query/connect.js' + +export { + type DeployContractData, + type DeployContractVariables, + type DeployContractMutate, + type DeployContractMutateAsync, + deployContractMutationOptions, +} from '../query/deployContract.js' + +export { + type DisconnectData, + type DisconnectVariables, + type DisconnectMutate, + type DisconnectMutateAsync, + disconnectMutationOptions, +} from '../query/disconnect.js' + +export { + type EstimateFeesPerGasData, + type EstimateFeesPerGasOptions, + type EstimateFeesPerGasQueryFnData, + type EstimateFeesPerGasQueryKey, + estimateFeesPerGasQueryKey, + estimateFeesPerGasQueryOptions, +} from '../query/estimateFeesPerGas.js' + +export { + type EstimateGasData, + type EstimateGasOptions, + type EstimateGasQueryFnData, + type EstimateGasQueryKey, + estimateGasQueryKey, + estimateGasQueryOptions, +} from '../query/estimateGas.js' + +export { + type EstimateMaxPriorityFeePerGasData, + type EstimateMaxPriorityFeePerGasOptions, + type EstimateMaxPriorityFeePerGasQueryFnData, + type EstimateMaxPriorityFeePerGasQueryKey, + estimateMaxPriorityFeePerGasQueryKey, + estimateMaxPriorityFeePerGasQueryOptions, +} from '../query/estimateMaxPriorityFeePerGas.js' + +export { + type GetBalanceData, + type GetBalanceOptions, + type GetBalanceQueryFnData, + type GetBalanceQueryKey, + getBalanceQueryKey, + getBalanceQueryOptions, +} from '../query/getBalance.js' + +export { + type GetBlockData, + type GetBlockOptions, + type GetBlockQueryFnData, + type GetBlockQueryKey, + getBlockQueryKey, + getBlockQueryOptions, +} from '../query/getBlock.js' + +export { + type GetBlockNumberData, + type GetBlockNumberOptions, + type GetBlockNumberQueryFnData, + type GetBlockNumberQueryKey, + getBlockNumberQueryKey, + getBlockNumberQueryOptions, +} from '../query/getBlockNumber.js' + +export { + type GetBlockTransactionCountData, + type GetBlockTransactionCountOptions, + type GetBlockTransactionCountQueryFnData, + type GetBlockTransactionCountQueryKey, + getBlockTransactionCountQueryKey, + getBlockTransactionCountQueryOptions, +} from '../query/getBlockTransactionCount.js' + +export { + type GetBytecodeData, + type GetBytecodeOptions, + type GetBytecodeQueryFnData, + type GetBytecodeQueryKey, + getBytecodeQueryKey, + getBytecodeQueryOptions, +} from '../query/getBytecode.js' + +export { + type GetCallsStatusData, + type GetCallsStatusOptions, + type GetCallsStatusQueryFnData, + type GetCallsStatusQueryKey, + getCallsStatusQueryKey, + getCallsStatusQueryOptions, +} from '../query/getCallsStatus.js' + +export { + type GetCapabilitiesData, + type GetCapabilitiesOptions, + type GetCapabilitiesQueryFnData, + type GetCapabilitiesQueryKey, + getCapabilitiesQueryKey, + getCapabilitiesQueryOptions, +} from '../query/getCapabilities.js' + +export { + type GetConnectorClientData, + type GetConnectorClientOptions, + type GetConnectorClientQueryFnData, + type GetConnectorClientQueryKey, + getConnectorClientQueryKey, + getConnectorClientQueryOptions, +} from '../query/getConnectorClient.js' + +export { + type GetEnsAddressData, + type GetEnsAddressOptions, + type GetEnsAddressQueryFnData, + type GetEnsAddressQueryKey, + getEnsAddressQueryKey, + getEnsAddressQueryOptions, +} from '../query/getEnsAddress.js' + +export { + type GetEnsAvatarData, + type GetEnsAvatarOptions, + type GetEnsAvatarQueryFnData, + type GetEnsAvatarQueryKey, + getEnsAvatarQueryKey, + getEnsAvatarQueryOptions, +} from '../query/getEnsAvatar.js' + +export { + type GetEnsNameData, + type GetEnsNameOptions, + type GetEnsNameQueryFnData, + type GetEnsNameQueryKey, + getEnsNameQueryKey, + getEnsNameQueryOptions, +} from '../query/getEnsName.js' + +export { + type GetEnsResolverData, + type GetEnsResolverOptions, + type GetEnsResolverQueryFnData, + type GetEnsResolverQueryKey, + getEnsResolverQueryKey, + getEnsResolverQueryOptions, +} from '../query/getEnsResolver.js' + +export { + type GetEnsTextData, + type GetEnsTextOptions, + type GetEnsTextQueryFnData, + type GetEnsTextQueryKey, + getEnsTextQueryKey, + getEnsTextQueryOptions, +} from '../query/getEnsText.js' + +export { + type GetFeeHistoryData, + type GetFeeHistoryOptions, + type GetFeeHistoryQueryFnData, + type GetFeeHistoryQueryKey, + getFeeHistoryQueryKey, + getFeeHistoryQueryOptions, +} from '../query/getFeeHistory.js' + +export { + type GetGasPriceData, + type GetGasPriceOptions, + type GetGasPriceQueryFnData, + type GetGasPriceQueryKey, + getGasPriceQueryKey, + getGasPriceQueryOptions, +} from '../query/getGasPrice.js' + +export { + type GetProofData, + type GetProofOptions, + type GetProofQueryFnData, + type GetProofQueryKey, + getProofQueryKey, + getProofQueryOptions, +} from '../query/getProof.js' + +export { + type GetStorageAtData, + type GetStorageAtOptions, + type GetStorageAtQueryFnData, + type GetStorageAtQueryKey, + getStorageAtQueryKey, + getStorageAtQueryOptions, +} from '../query/getStorageAt.js' + +export { + type GetTokenData, + type GetTokenOptions, + type GetTokenQueryFnData, + type GetTokenQueryKey, + getTokenQueryKey, + getTokenQueryOptions, +} from '../query/getToken.js' + +export { + type GetTransactionData, + type GetTransactionOptions, + type GetTransactionQueryFnData, + type GetTransactionQueryKey, + getTransactionQueryKey, + getTransactionQueryOptions, +} from '../query/getTransaction.js' + +export { + type GetTransactionConfirmationsData, + type GetTransactionConfirmationsOptions, + type GetTransactionConfirmationsQueryFnData, + type GetTransactionConfirmationsQueryKey, + getTransactionConfirmationsQueryKey, + getTransactionConfirmationsQueryOptions, +} from '../query/getTransactionConfirmations.js' + +export { + type GetTransactionCountData, + type GetTransactionCountOptions, + type GetTransactionCountQueryFnData, + type GetTransactionCountQueryKey, + getTransactionCountQueryKey, + getTransactionCountQueryOptions, +} from '../query/getTransactionCount.js' + +export { + type GetTransactionReceiptData, + type GetTransactionReceiptOptions, + type GetTransactionReceiptQueryFnData, + type GetTransactionReceiptQueryKey, + getTransactionReceiptQueryKey, + getTransactionReceiptQueryOptions, +} from '../query/getTransactionReceipt.js' + +export { + type GetWalletClientData, + type GetWalletClientOptions, + type GetWalletClientQueryFnData, + type GetWalletClientQueryKey, + getWalletClientQueryKey, + getWalletClientQueryOptions, +} from '../query/getWalletClient.js' + +export { + type InfiniteReadContractsData, + type InfiniteReadContractsOptions, + type InfiniteReadContractsQueryFnData, + type InfiniteReadContractsQueryKey, + infiniteReadContractsQueryKey, + infiniteReadContractsQueryOptions, +} from '../query/infiniteReadContracts.js' + +export { + type PrepareTransactionRequestData, + type PrepareTransactionRequestOptions, + type PrepareTransactionRequestQueryFnData, + type PrepareTransactionRequestQueryKey, + prepareTransactionRequestQueryKey, + prepareTransactionRequestQueryOptions, +} from '../query/prepareTransactionRequest.js' + +export { + type ReadContractData, + type ReadContractOptions, + type ReadContractQueryFnData, + type ReadContractQueryKey, + readContractQueryKey, + readContractQueryOptions, +} from '../query/readContract.js' + +export { + type ReadContractsData, + type ReadContractsOptions, + type ReadContractsQueryFnData, + type ReadContractsQueryKey, + readContractsQueryKey, + readContractsQueryOptions, +} from '../query/readContracts.js' + +export { + type ReconnectData, + type ReconnectVariables, + type ReconnectMutate, + type ReconnectMutateAsync, + reconnectMutationOptions, +} from '../query/reconnect.js' + +export { + type SendCallsData, + type SendCallsVariables, + type SendCallsMutate, + type SendCallsMutateAsync, + sendCallsMutationOptions, +} from '../query/sendCalls.js' + +export { + type ShowCallsStatusData, + type ShowCallsStatusVariables, + type ShowCallsStatusMutate, + type ShowCallsStatusMutateAsync, + showCallsStatusMutationOptions, +} from '../query/showCallsStatus.js' + +export { + type SendTransactionData, + type SendTransactionVariables, + type SendTransactionMutate, + type SendTransactionMutateAsync, + sendTransactionMutationOptions, +} from '../query/sendTransaction.js' + +export { + type SignMessageData, + type SignMessageVariables, + type SignMessageMutate, + type SignMessageMutateAsync, + signMessageMutationOptions, +} from '../query/signMessage.js' + +export { + type SignTypedDataData, + type SignTypedDataVariables, + type SignTypedDataMutate, + type SignTypedDataMutateAsync, + signTypedDataMutationOptions, +} from '../query/signTypedData.js' + +export { + type SwitchAccountData, + type SwitchAccountVariables, + type SwitchAccountMutate, + type SwitchAccountMutateAsync, + switchAccountMutationOptions, +} from '../query/switchAccount.js' + +export { + type SimulateContractData, + type SimulateContractOptions, + type SimulateContractQueryFnData, + type SimulateContractQueryKey, + simulateContractQueryKey, + simulateContractQueryOptions, +} from '../query/simulateContract.js' + +export { + type SwitchChainData, + type SwitchChainVariables, + type SwitchChainMutate, + type SwitchChainMutateAsync, + switchChainMutationOptions, +} from '../query/switchChain.js' + +export { + type VerifyMessageData, + type VerifyMessageOptions, + type VerifyMessageQueryFnData, + type VerifyMessageQueryKey, + verifyMessageQueryKey, + verifyMessageQueryOptions, +} from '../query/verifyMessage.js' + +export { + type VerifyTypedDataData, + type VerifyTypedDataOptions, + type VerifyTypedDataQueryFnData, + type VerifyTypedDataQueryKey, + verifyTypedDataQueryKey, + verifyTypedDataQueryOptions, +} from '../query/verifyTypedData.js' + +export { + type WaitForCallsStatusData, + type WaitForCallsStatusOptions, + type WaitForCallsStatusQueryFnData, + type WaitForCallsStatusQueryKey, + waitForCallsStatusQueryKey, + waitForCallsStatusQueryOptions, +} from '../query/waitForCallsStatus.js' + +export { + type WaitForTransactionReceiptData, + type WaitForTransactionReceiptOptions, + type WaitForTransactionReceiptQueryFnData, + type WaitForTransactionReceiptQueryKey, + waitForTransactionReceiptQueryKey, + waitForTransactionReceiptQueryOptions, +} from '../query/waitForTransactionReceipt.js' + +export { + type WatchAssetData, + type WatchAssetVariables, + type WatchAssetMutate, + type WatchAssetMutateAsync, + watchAssetMutationOptions, +} from '../query/watchAsset.js' + +export { + type WriteContractData, + type WriteContractVariables, + type WriteContractMutate, + type WriteContractMutateAsync, + writeContractMutationOptions, +} from '../query/writeContract.js' + +export { hashFn, structuralSharing } from '../query/utils.js' diff --git a/packages/core/src/hydrate.test.ts b/packages/core/src/hydrate.test.ts new file mode 100644 index 0000000000..3dc6e28549 --- /dev/null +++ b/packages/core/src/hydrate.test.ts @@ -0,0 +1,114 @@ +import { accounts, config, wait } from '@wagmi/test' +import type { EIP1193Provider } from 'mipd' +import { http } from 'viem' +import { mainnet } from 'viem/chains' +import { expect, test, vi } from 'vitest' + +import { createConnector } from './connectors/createConnector.js' +import { mock } from './connectors/mock.js' +import { createConfig } from './createConfig.js' +import { createStorage } from './createStorage.js' +import { hydrate } from './hydrate.js' +import { cookieStorage } from './utils/cookie.js' + +vi.mock(import('mipd'), async (importOriginal) => { + const mod = await importOriginal() + + let cache: typeof mod | undefined + if (!cache) + cache = { + ...mod, + createStore() { + const store = mod.createStore() + return { + ...store, + getProviders() { + const info = { + icon: 'data:image/svg+xml,', + uuid: crypto.randomUUID(), + } as const + const provider = '' as unknown as EIP1193Provider + return [ + { info: { ...info, name: 'Foo', rdns: 'com.foo' }, provider }, + { info: { ...info, name: 'Bar', rdns: 'com.bar' }, provider }, + { info: { ...info, name: 'Mock', rdns: 'com.mock' }, provider }, + ] + }, + } + }, + } + return cache +}) + +test('default', () => { + const { onMount } = hydrate(config, { + initialState: undefined, + reconnectOnMount: false, + }) + onMount() + + expect(onMount).toBeDefined() +}) + +test('initialState', () => { + const config = createConfig({ + chains: [mainnet], + transports: { [mainnet.id]: http() }, + ssr: true, + storage: createStorage({ storage: cookieStorage }), + }) + + const { onMount } = hydrate(config, { + initialState: { + chainId: 1, + current: null, + connections: new Map(), + status: 'disconnected', + }, + reconnectOnMount: true, + }) + onMount() + + expect(onMount).toBeDefined() +}) + +test('ssr', async () => { + const config = createConfig({ + chains: [mainnet], + connectors: [ + createConnector((c) => { + return { + ...mock({ accounts })(c), + rdns: 'com.mock', + } + }), + ], + ssr: true, + storage: createStorage({ storage: cookieStorage }), + transports: { [mainnet.id]: http() }, + }) + + const { onMount } = hydrate(config, { + initialState: { + chainId: 10, + current: null, + connections: new Map(), + status: 'disconnected', + }, + reconnectOnMount: false, + }) + onMount() + expect(onMount).toBeDefined() + expect(config.chains[0].id).toBe(1) + + await wait(100) + expect(config.connectors.map((x) => x.rdns ?? x.id)).toMatchInlineSnapshot( + ` + [ + "com.mock", + "com.foo", + "com.bar", + ] + `, + ) +}) diff --git a/packages/core/src/hydrate.ts b/packages/core/src/hydrate.ts new file mode 100644 index 0000000000..70bc2199e6 --- /dev/null +++ b/packages/core/src/hydrate.ts @@ -0,0 +1,62 @@ +import { reconnect } from './actions/reconnect.js' +import type { Config, State } from './createConfig.js' + +type HydrateParameters = { + initialState?: State | undefined + reconnectOnMount?: boolean | undefined +} + +export function hydrate(config: Config, parameters: HydrateParameters) { + const { initialState, reconnectOnMount } = parameters + + if (initialState && !config._internal.store.persist.hasHydrated()) + config.setState({ + ...initialState, + chainId: config.chains.some((x) => x.id === initialState.chainId) + ? initialState.chainId + : config.chains[0].id, + connections: reconnectOnMount ? initialState.connections : new Map(), + status: reconnectOnMount ? 'reconnecting' : 'disconnected', + }) + + return { + async onMount() { + if (config._internal.ssr) { + await config._internal.store.persist.rehydrate() + if (config._internal.mipd) { + config._internal.connectors.setState((connectors) => { + const rdnsSet = new Set() + for (const connector of connectors ?? []) { + if (connector.rdns) { + const rdnsValues = Array.isArray(connector.rdns) + ? connector.rdns + : [connector.rdns] + for (const rdns of rdnsValues) { + rdnsSet.add(rdns) + } + } + } + const mipdConnectors = [] + const providers = config._internal.mipd?.getProviders() ?? [] + for (const provider of providers) { + if (rdnsSet.has(provider.info.rdns)) continue + const connectorFn = + config._internal.connectors.providerDetailToConnector(provider) + const connector = config._internal.connectors.setup(connectorFn) + mipdConnectors.push(connector) + } + return [...connectors, ...mipdConnectors] + }) + } + } + + if (reconnectOnMount) reconnect(config) + else if (config.storage) + // Reset connections that may have been hydrated from storage. + config.setState((x) => ({ + ...x, + connections: new Map(), + })) + }, + } +} diff --git a/packages/core/src/query/call.test.ts b/packages/core/src/query/call.test.ts new file mode 100644 index 0000000000..1e9ee03f7a --- /dev/null +++ b/packages/core/src/query/call.test.ts @@ -0,0 +1,306 @@ +import { accounts, address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { parseEther, parseGwei } from 'viem' +import { callQueryOptions } from './call.js' + +const name4bytes = '0x06fdde03' +const account = accounts[0] + +test('default', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: accessList', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + accessList: [ + { + address: '0x1', + storageKeys: ['0x1'], + }, + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "accessList": [ + { + "address": "0x1", + "storageKeys": [ + "0x1", + ], + }, + ], + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + blockNumber: 1234567890n, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 1234567890n, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + blockTag: 'safe', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "safe", + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + chainId: chain.mainnet2.id, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: gas', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + gas: 100000n, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "gas": 100000n, + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: gasPrice', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + gasPrice: parseGwei('20'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "gasPrice": 20000000000n, + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: maxFeePerGas', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + maxFeePerGas: parseGwei('20'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "maxFeePerGas": 20000000000n, + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: maxPriorityFeePerGas', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + maxPriorityFeePerGas: parseGwei('2'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "maxPriorityFeePerGas": 2000000000n, + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: nonce', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + nonce: 123, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "nonce": 123, + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: type', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + type: 'eip1559', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "type": "eip1559", + }, + ], + } + `) +}) + +test('parameters: value', () => { + expect( + callQueryOptions(config, { + account, + data: name4bytes, + to: address.wagmiMintExample, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "value": 1000000000000000000n, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/call.ts b/packages/core/src/query/call.ts new file mode 100644 index 0000000000..2ca4491b86 --- /dev/null +++ b/packages/core/src/query/call.ts @@ -0,0 +1,51 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type CallErrorType, + type CallParameters, + type CallReturnType, + call, +} from '../actions/call.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type CallOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function callQueryOptions( + config: config, + options: CallOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const data = await call(config, { + ...parameters, + } as CallParameters) + return data ?? null + }, + queryKey: callQueryKey(options), + } as const satisfies QueryOptions< + CallQueryFnData, + CallErrorType, + CallData, + CallQueryKey + > +} + +export type CallQueryFnData = CallReturnType + +export type CallData = CallQueryFnData + +export function callQueryKey( + options: CallOptions, +) { + return ['call', filterQueryOptions(options)] as const +} + +export type CallQueryKey = ReturnType< + typeof callQueryKey +> diff --git a/packages/core/src/query/connect.test.ts b/packages/core/src/query/connect.test.ts new file mode 100644 index 0000000000..b8300ee0ee --- /dev/null +++ b/packages/core/src/query/connect.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { connectMutationOptions } from './connect.js' + +test('default', () => { + expect(connectMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "connect", + ], + } + `) +}) diff --git a/packages/core/src/query/connect.ts b/packages/core/src/query/connect.ts new file mode 100644 index 0000000000..f521512789 --- /dev/null +++ b/packages/core/src/query/connect.ts @@ -0,0 +1,70 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { + type ConnectErrorType, + type ConnectParameters, + type ConnectReturnType, + connect, +} from '../actions/connect.js' +import type { Config, Connector } from '../createConfig.js' + +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import type { Compute } from '../types/utils.js' + +export function connectMutationOptions(config: config) { + return { + mutationFn(variables) { + return connect(config, variables) + }, + mutationKey: ['connect'], + } as const satisfies MutationOptions< + ConnectData, + ConnectErrorType, + ConnectVariables + > +} + +export type ConnectData = ConnectReturnType + +export type ConnectVariables< + config extends Config, + connector extends Connector | CreateConnectorFn, +> = ConnectParameters + +export type ConnectMutate = < + connector extends + | config['connectors'][number] + | Connector + | CreateConnectorFn, +>( + variables: ConnectVariables, + options?: + | Compute< + MutateOptions< + ConnectData, + ConnectErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type ConnectMutateAsync = < + connector extends + | config['connectors'][number] + | Connector + | CreateConnectorFn, +>( + variables: ConnectVariables, + options?: + | Compute< + MutateOptions< + ConnectData, + ConnectErrorType, + Compute>, + context + > + > + | undefined, +) => Promise> diff --git a/packages/core/src/query/deployContract.test.ts b/packages/core/src/query/deployContract.test.ts new file mode 100644 index 0000000000..51157c03df --- /dev/null +++ b/packages/core/src/query/deployContract.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { deployContractMutationOptions } from './deployContract.js' + +test('default', () => { + expect(deployContractMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "deployContract", + ], + } + `) +}) diff --git a/packages/core/src/query/deployContract.ts b/packages/core/src/query/deployContract.ts new file mode 100644 index 0000000000..d4fac04531 --- /dev/null +++ b/packages/core/src/query/deployContract.ts @@ -0,0 +1,73 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' +import type { Abi, ContractConstructorArgs } from 'viem' + +import { + type DeployContractErrorType, + type DeployContractParameters, + type DeployContractReturnType, + deployContract, +} from '../actions/deployContract.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function deployContractMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return deployContract(config, variables) + }, + mutationKey: ['deployContract'], + } as const satisfies MutationOptions< + DeployContractData, + DeployContractErrorType, + DeployContractVariables + > +} + +export type DeployContractData = Compute + +export type DeployContractVariables< + abi extends Abi | readonly unknown[], + config extends Config, + chainId extends config['chains'][number]['id'], + /// + allArgs = ContractConstructorArgs, +> = DeployContractParameters + +export type DeployContractMutate = < + abi extends Abi | readonly unknown[], + chainId extends config['chains'][number]['id'], +>( + variables: DeployContractVariables, + options?: + | Compute< + MutateOptions< + DeployContractData, + DeployContractErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type DeployContractMutateAsync< + config extends Config, + context = unknown, +> = < + abi extends Abi | readonly unknown[], + chainId extends config['chains'][number]['id'], +>( + variables: DeployContractVariables, + options?: + | Compute< + MutateOptions< + DeployContractData, + DeployContractErrorType, + Compute>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/query/disconnect.test.ts b/packages/core/src/query/disconnect.test.ts new file mode 100644 index 0000000000..4637c7e88f --- /dev/null +++ b/packages/core/src/query/disconnect.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { disconnectMutationOptions } from './disconnect.js' + +test('default', () => { + expect(disconnectMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "disconnect", + ], + } + `) +}) diff --git a/packages/core/src/query/disconnect.ts b/packages/core/src/query/disconnect.ts new file mode 100644 index 0000000000..018873da03 --- /dev/null +++ b/packages/core/src/query/disconnect.ts @@ -0,0 +1,43 @@ +import type { MutationOptions } from '@tanstack/query-core' + +import { + type DisconnectErrorType, + type DisconnectParameters, + type DisconnectReturnType, + disconnect, +} from '../actions/disconnect.js' +import type { Config } from '../createConfig.js' +import type { Mutate, MutateAsync } from './types.js' + +export function disconnectMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return disconnect(config, variables) + }, + mutationKey: ['disconnect'], + } as const satisfies MutationOptions< + DisconnectData, + DisconnectErrorType, + DisconnectVariables + > +} + +export type DisconnectData = DisconnectReturnType + +export type DisconnectVariables = DisconnectParameters | undefined + +export type DisconnectMutate = Mutate< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context +> + +export type DisconnectMutateAsync = MutateAsync< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context +> diff --git a/packages/core/src/query/estimateFeesPerGas.test.ts b/packages/core/src/query/estimateFeesPerGas.test.ts new file mode 100644 index 0000000000..6b9965e651 --- /dev/null +++ b/packages/core/src/query/estimateFeesPerGas.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { estimateFeesPerGasQueryOptions } from './estimateFeesPerGas.js' + +test('default', () => { + expect(estimateFeesPerGasQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "estimateFeesPerGas", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + estimateFeesPerGasQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "estimateFeesPerGas", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/estimateFeesPerGas.ts b/packages/core/src/query/estimateFeesPerGas.ts new file mode 100644 index 0000000000..7cf2f607c8 --- /dev/null +++ b/packages/core/src/query/estimateFeesPerGas.ts @@ -0,0 +1,56 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { FeeValuesType } from 'viem' + +import { + type EstimateFeesPerGasErrorType, + type EstimateFeesPerGasParameters, + type EstimateFeesPerGasReturnType, + estimateFeesPerGas, +} from '../actions/estimateFeesPerGas.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type EstimateFeesPerGasOptions< + type extends FeeValuesType, + config extends Config, +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function estimateFeesPerGasQueryOptions< + config extends Config, + type extends FeeValuesType = 'eip1559', +>(config: config, options: EstimateFeesPerGasOptions = {}) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + return estimateFeesPerGas(config, parameters) + }, + queryKey: estimateFeesPerGasQueryKey(options), + } as const satisfies QueryOptions< + EstimateFeesPerGasQueryFnData, + EstimateFeesPerGasErrorType, + EstimateFeesPerGasData, + EstimateFeesPerGasQueryKey + > +} + +export type EstimateFeesPerGasQueryFnData = + EstimateFeesPerGasReturnType + +export type EstimateFeesPerGasData = + EstimateFeesPerGasQueryFnData + +export function estimateFeesPerGasQueryKey< + config extends Config, + type extends FeeValuesType = 'eip1559', +>(options: EstimateFeesPerGasOptions = {}) { + return ['estimateFeesPerGas', filterQueryOptions(options)] as const +} + +export type EstimateFeesPerGasQueryKey< + config extends Config, + type extends FeeValuesType, +> = ReturnType> diff --git a/packages/core/src/query/estimateGas.test-d.ts b/packages/core/src/query/estimateGas.test-d.ts new file mode 100644 index 0000000000..e48c4b089a --- /dev/null +++ b/packages/core/src/query/estimateGas.test-d.ts @@ -0,0 +1,55 @@ +import { http, type Address, parseEther } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type EstimateGasOptions, + estimateGasQueryOptions, +} from './estimateGas.js' + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = EstimateGasOptions< + typeof config, + (typeof config)['chains'][number]['id'] + > + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + estimateGasQueryOptions(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result2 = EstimateGasOptions + expectTypeOf().toMatchTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + feeCurrency?: `0x${string}` | undefined + }>() + estimateGasQueryOptions(config, { + chainId: celo.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x', + }) + + type Result3 = EstimateGasOptions + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + estimateGasQueryOptions(config, { + chainId: mainnet.id, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/query/estimateGas.test.ts b/packages/core/src/query/estimateGas.test.ts new file mode 100644 index 0000000000..6c31cadbc1 --- /dev/null +++ b/packages/core/src/query/estimateGas.test.ts @@ -0,0 +1,25 @@ +import { config } from '@wagmi/test' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { estimateGasQueryOptions } from './estimateGas.js' + +test('default', () => { + expect( + estimateGasQueryOptions(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "estimateGas", + { + "to": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "value": 10000000000000000n, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/estimateGas.ts b/packages/core/src/query/estimateGas.ts new file mode 100644 index 0000000000..749002f92c --- /dev/null +++ b/packages/core/src/query/estimateGas.ts @@ -0,0 +1,56 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type EstimateGasErrorType, + type EstimateGasParameters, + type EstimateGasReturnType, + estimateGas, +} from '../actions/estimateGas.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { UnionExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type EstimateGasOptions< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = UnionExactPartial> & + ScopeKeyParameter + +export function estimateGasQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: EstimateGasOptions = {} as any) { + return { + async queryFn({ queryKey }) { + const { connector } = options + const { account, scopeKey: _, ...parameters } = queryKey[1] + if (!account && !connector) + throw new Error('account or connector is required') + return estimateGas(config, { account, connector, ...(parameters as any) }) + }, + queryKey: estimateGasQueryKey(options), + } as const satisfies QueryOptions< + EstimateGasQueryFnData, + EstimateGasErrorType, + EstimateGasData, + EstimateGasQueryKey + > +} + +export type EstimateGasQueryFnData = EstimateGasReturnType + +export type EstimateGasData = EstimateGasQueryFnData + +export function estimateGasQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +>(options: EstimateGasOptions = {} as any) { + const { connector: _, ...rest } = options + return ['estimateGas', filterQueryOptions(rest)] as const +} + +export type EstimateGasQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = ReturnType> diff --git a/packages/core/src/query/estimateMaxPriorityFeePerGas.test.ts b/packages/core/src/query/estimateMaxPriorityFeePerGas.test.ts new file mode 100644 index 0000000000..38bcde076d --- /dev/null +++ b/packages/core/src/query/estimateMaxPriorityFeePerGas.test.ts @@ -0,0 +1,36 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { estimateMaxPriorityFeePerGasQueryOptions } from './estimateMaxPriorityFeePerGas.js' + +test('default', () => { + expect( + estimateMaxPriorityFeePerGasQueryOptions(config), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "estimateMaxPriorityFeePerGas", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + estimateMaxPriorityFeePerGasQueryOptions(config, { + chainId: chain.mainnet.id, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "estimateMaxPriorityFeePerGas", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/estimateMaxPriorityFeePerGas.ts b/packages/core/src/query/estimateMaxPriorityFeePerGas.ts new file mode 100644 index 0000000000..cb58e65a79 --- /dev/null +++ b/packages/core/src/query/estimateMaxPriorityFeePerGas.ts @@ -0,0 +1,51 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type EstimateMaxPriorityFeePerGasErrorType, + type EstimateMaxPriorityFeePerGasParameters, + type EstimateMaxPriorityFeePerGasReturnType, + estimateMaxPriorityFeePerGas, +} from '../actions/estimateMaxPriorityFeePerGas.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type EstimateMaxPriorityFeePerGasOptions = + Compute< + ExactPartial> & + ScopeKeyParameter + > + +export function estimateMaxPriorityFeePerGasQueryOptions( + config: config, + options: EstimateMaxPriorityFeePerGasOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + return estimateMaxPriorityFeePerGas(config, parameters) + }, + queryKey: estimateMaxPriorityFeePerGasQueryKey(options), + } as const satisfies QueryOptions< + EstimateMaxPriorityFeePerGasQueryFnData, + EstimateMaxPriorityFeePerGasErrorType, + EstimateMaxPriorityFeePerGasData, + EstimateMaxPriorityFeePerGasQueryKey + > +} + +export type EstimateMaxPriorityFeePerGasQueryFnData = + EstimateMaxPriorityFeePerGasReturnType + +export type EstimateMaxPriorityFeePerGasData = + EstimateMaxPriorityFeePerGasQueryFnData + +export function estimateMaxPriorityFeePerGasQueryKey( + options: EstimateMaxPriorityFeePerGasOptions = {}, +) { + return ['estimateMaxPriorityFeePerGas', filterQueryOptions(options)] as const +} + +export type EstimateMaxPriorityFeePerGasQueryKey = + ReturnType> diff --git a/packages/core/src/query/getBalance.test.ts b/packages/core/src/query/getBalance.test.ts new file mode 100644 index 0000000000..1f9e1ecda9 --- /dev/null +++ b/packages/core/src/query/getBalance.test.ts @@ -0,0 +1,63 @@ +import { accounts, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBalanceQueryOptions } from './getBalance.js' + +const address = accounts[0] + +test('default', () => { + expect(getBalanceQueryOptions(config, { address })).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getBalanceQueryOptions(config, { address, chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + } + `) +}) + +test.todo('parameters: token') + +test('parameters: unit', () => { + expect( + getBalanceQueryOptions(config, { + address, + chainId: chain.mainnet.id, + token: '0x0000000000000000000000000000000000000000', + unit: 'gwei', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "token": "0x0000000000000000000000000000000000000000", + "unit": "gwei", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getBalance.ts b/packages/core/src/query/getBalance.ts new file mode 100644 index 0000000000..e1dd287633 --- /dev/null +++ b/packages/core/src/query/getBalance.ts @@ -0,0 +1,53 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetBalanceErrorType, + type GetBalanceParameters, + type GetBalanceReturnType, + getBalance, +} from '../actions/getBalance.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, PartialBy } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetBalanceOptions = Compute< + PartialBy, 'address'> & ScopeKeyParameter +> + +export function getBalanceQueryOptions( + config: config, + options: GetBalanceOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, ...parameters } = queryKey[1] + if (!address) throw new Error('address is required') + const balance = await getBalance(config, { + ...(parameters as GetBalanceParameters), + address, + }) + return balance ?? null + }, + queryKey: getBalanceQueryKey(options), + } as const satisfies QueryOptions< + GetBalanceQueryFnData, + GetBalanceErrorType, + GetBalanceData, + GetBalanceQueryKey + > +} + +export type GetBalanceQueryFnData = Compute + +export type GetBalanceData = GetBalanceQueryFnData + +export function getBalanceQueryKey( + options: GetBalanceOptions = {}, +) { + return ['balance', filterQueryOptions(options)] as const +} + +export type GetBalanceQueryKey = ReturnType< + typeof getBalanceQueryKey +> diff --git a/packages/core/src/query/getBlock.test.ts b/packages/core/src/query/getBlock.test.ts new file mode 100644 index 0000000000..f31fb8c26e --- /dev/null +++ b/packages/core/src/query/getBlock.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlockQueryOptions } from './getBlock.js' + +test('default', () => { + expect(getBlockQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "block", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getBlockQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "block", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getBlock.ts b/packages/core/src/query/getBlock.ts new file mode 100644 index 0000000000..f8f4db8417 --- /dev/null +++ b/packages/core/src/query/getBlock.ts @@ -0,0 +1,84 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { BlockTag } from 'viem' + +import { + type GetBlockErrorType, + type GetBlockParameters, + type GetBlockReturnType, + getBlock, +} from '../actions/getBlock.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetBlockOptions< + includeTransactions extends boolean, + blockTag extends BlockTag, + config extends Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = Compute< + ExactPartial< + GetBlockParameters + > & + ScopeKeyParameter +> + +export function getBlockQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], + includeTransactions extends boolean, + blockTag extends BlockTag, +>( + config: config, + options: GetBlockOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const block = await getBlock(config, parameters) + return (block ?? null) as any + }, + queryKey: getBlockQueryKey(options), + } as const satisfies QueryOptions< + GetBlockQueryFnData, + GetBlockErrorType, + GetBlockData, + GetBlockQueryKey + > +} + +export type GetBlockQueryFnData< + includeTransactions extends boolean, + blockTag extends BlockTag, + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetBlockReturnType + +export type GetBlockData< + includeTransactions extends boolean, + blockTag extends BlockTag, + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetBlockQueryFnData + +export function getBlockQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', +>( + options: GetBlockOptions = {}, +) { + return ['block', filterQueryOptions(options)] as const +} + +export type GetBlockQueryKey< + includeTransactions extends boolean, + blockTag extends BlockTag, + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType< + typeof getBlockQueryKey +> diff --git a/packages/core/src/query/getBlockNumber.test.ts b/packages/core/src/query/getBlockNumber.test.ts new file mode 100644 index 0000000000..b2ff0c2d14 --- /dev/null +++ b/packages/core/src/query/getBlockNumber.test.ts @@ -0,0 +1,34 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlockNumberQueryOptions } from './getBlockNumber.js' + +test('default', () => { + expect(getBlockNumberQueryOptions(config)).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "blockNumber", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getBlockNumberQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "blockNumber", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getBlockNumber.ts b/packages/core/src/query/getBlockNumber.ts new file mode 100644 index 0000000000..9585068a92 --- /dev/null +++ b/packages/core/src/query/getBlockNumber.ts @@ -0,0 +1,55 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetBlockNumberErrorType, + type GetBlockNumberParameters, + type GetBlockNumberReturnType, + getBlockNumber, +} from '../actions/getBlockNumber.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetBlockNumberOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getBlockNumberQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetBlockNumberOptions = {}) { + return { + gcTime: 0, + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const blockNumber = await getBlockNumber(config, parameters) + return blockNumber ?? null + }, + queryKey: getBlockNumberQueryKey(options), + } as const satisfies QueryOptions< + GetBlockNumberQueryFnData, + GetBlockNumberErrorType, + GetBlockNumberData, + GetBlockNumberQueryKey + > +} + +export type GetBlockNumberQueryFnData = GetBlockNumberReturnType + +export type GetBlockNumberData = GetBlockNumberQueryFnData + +export function getBlockNumberQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetBlockNumberOptions = {}) { + return ['blockNumber', filterQueryOptions(options)] as const +} + +export type GetBlockNumberQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getBlockTransactionCount.test.ts b/packages/core/src/query/getBlockTransactionCount.test.ts new file mode 100644 index 0000000000..d6c45ee09b --- /dev/null +++ b/packages/core/src/query/getBlockTransactionCount.test.ts @@ -0,0 +1,50 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBlockTransactionCountQueryOptions } from './getBlockTransactionCount.js' + +test('default', () => { + expect(getBlockTransactionCountQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "blockTransactionCount", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getBlockTransactionCountQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "blockTransactionCount", + { + "chainId": 1, + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + getBlockTransactionCountQueryOptions(config, { + blockTag: 'earliest', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "blockTransactionCount", + { + "blockTag": "earliest", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getBlockTransactionCount.ts b/packages/core/src/query/getBlockTransactionCount.ts new file mode 100644 index 0000000000..453e95e001 --- /dev/null +++ b/packages/core/src/query/getBlockTransactionCount.ts @@ -0,0 +1,62 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetBlockTransactionCountErrorType, + type GetBlockTransactionCountParameters, + type GetBlockTransactionCountReturnType, + getBlockTransactionCount, +} from '../actions/getBlockTransactionCount.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { ExactPartial, UnionCompute } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetBlockTransactionCountOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = UnionCompute< + ExactPartial> & + ScopeKeyParameter +> + +export function getBlockTransactionCountQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + options: GetBlockTransactionCountOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const blockTransactionCount = await getBlockTransactionCount( + config, + parameters, + ) + return blockTransactionCount ?? null + }, + queryKey: getBlockTransactionCountQueryKey(options), + } as const satisfies QueryOptions< + GetBlockTransactionCountQueryFnData, + GetBlockTransactionCountErrorType, + GetBlockTransactionCountData, + GetBlockTransactionCountQueryKey + > +} + +export type GetBlockTransactionCountQueryFnData = + GetBlockTransactionCountReturnType + +export type GetBlockTransactionCountData = GetBlockTransactionCountQueryFnData + +export function getBlockTransactionCountQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetBlockTransactionCountOptions = {}) { + return ['blockTransactionCount', filterQueryOptions(options)] as const +} + +export type GetBlockTransactionCountQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getBytecode.test.ts b/packages/core/src/query/getBytecode.test.ts new file mode 100644 index 0000000000..83f22ebf2e --- /dev/null +++ b/packages/core/src/query/getBytecode.test.ts @@ -0,0 +1,82 @@ +import { address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getBytecodeQueryOptions } from './getBytecode.js' + +test('default', () => { + expect( + getBytecodeQueryOptions(config, { + address: address.wagmiMintExample, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getBytecodeQueryOptions(config, { + address: address.wagmiMintExample, + chainId: chain.mainnet2.id, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 456, + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + getBytecodeQueryOptions(config, { + address: address.wagmiMintExample, + blockNumber: 1234567890n, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockNumber": 1234567890n, + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + getBytecodeQueryOptions(config, { + address: address.wagmiMintExample, + blockTag: 'safe', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockTag": "safe", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getBytecode.ts b/packages/core/src/query/getBytecode.ts new file mode 100644 index 0000000000..7000c50eaa --- /dev/null +++ b/packages/core/src/query/getBytecode.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetBytecodeErrorType, + type GetBytecodeParameters, + type GetBytecodeReturnType, + getBytecode, +} from '../actions/getBytecode.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetBytecodeOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getBytecodeQueryOptions( + config: config, + options: GetBytecodeOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, ...parameters } = queryKey[1] + if (!address) throw new Error('address is required') + const bytecode = await getBytecode(config, { ...parameters, address }) + return (bytecode ?? null) as any + }, + queryKey: getBytecodeQueryKey(options), + } as const satisfies QueryOptions< + GetBytecodeQueryFnData, + GetBytecodeErrorType, + GetBytecodeData, + GetBytecodeQueryKey + > +} +export type GetBytecodeQueryFnData = GetBytecodeReturnType + +export type GetBytecodeData = GetBytecodeQueryFnData + +export function getBytecodeQueryKey( + options: GetBytecodeOptions, +) { + return ['getBytecode', filterQueryOptions(options)] as const +} + +export type GetBytecodeQueryKey = ReturnType< + typeof getBytecodeQueryKey +> diff --git a/packages/core/src/query/getCallsStatus.test.ts b/packages/core/src/query/getCallsStatus.test.ts new file mode 100644 index 0000000000..fe834ecc79 --- /dev/null +++ b/packages/core/src/query/getCallsStatus.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getCallsStatusQueryOptions } from './getCallsStatus.js' + +test('default', () => { + expect( + getCallsStatusQueryOptions(config, { + id: '0x0000000000000000000000000000000000000000', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "callsStatus", + { + "id": "0x0000000000000000000000000000000000000000", + }, + ], + "retry": [Function], + } + `) +}) diff --git a/packages/core/src/query/getCallsStatus.ts b/packages/core/src/query/getCallsStatus.ts new file mode 100644 index 0000000000..869263eef0 --- /dev/null +++ b/packages/core/src/query/getCallsStatus.ts @@ -0,0 +1,50 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetCallsStatusErrorType, + type GetCallsStatusParameters, + type GetCallsStatusReturnType, + getCallsStatus, +} from '../actions/getCallsStatus.js' +import type { Config } from '../createConfig.js' +import { ConnectorNotConnectedError } from '../errors/config.js' +import { filterQueryOptions } from '../query/utils.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute } from '../types/utils.js' + +export type GetCallsStatusOptions = Compute< + GetCallsStatusParameters & ScopeKeyParameter +> + +export function getCallsStatusQueryOptions( + config: config, + options: GetCallsStatusOptions, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const status = await getCallsStatus(config, parameters) + return status + }, + queryKey: getCallsStatusQueryKey(options), + retry(failureCount, error) { + if (error instanceof ConnectorNotConnectedError) return false + return failureCount < 3 + }, + } as const satisfies QueryOptions< + GetCallsStatusQueryFnData, + GetCallsStatusErrorType, + GetCallsStatusData, + GetCallsStatusQueryKey + > +} + +export type GetCallsStatusQueryFnData = GetCallsStatusReturnType + +export type GetCallsStatusData = GetCallsStatusQueryFnData + +export function getCallsStatusQueryKey(options: GetCallsStatusOptions) { + return ['callsStatus', filterQueryOptions(options)] as const +} + +export type GetCallsStatusQueryKey = ReturnType diff --git a/packages/core/src/query/getCapabilities.test.ts b/packages/core/src/query/getCapabilities.test.ts new file mode 100644 index 0000000000..942eb37e04 --- /dev/null +++ b/packages/core/src/query/getCapabilities.test.ts @@ -0,0 +1,36 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getCapabilitiesQueryOptions } from './getCapabilities.js' + +test('default', () => { + expect(getCapabilitiesQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "capabilities", + {}, + ], + "retry": [Function], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getCapabilitiesQueryOptions(config, { + account: '0x0000000000000000000000000000000000000000', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "capabilities", + { + "account": "0x0000000000000000000000000000000000000000", + }, + ], + "retry": [Function], + } + `) +}) diff --git a/packages/core/src/query/getCapabilities.ts b/packages/core/src/query/getCapabilities.ts new file mode 100644 index 0000000000..754b77b551 --- /dev/null +++ b/packages/core/src/query/getCapabilities.ts @@ -0,0 +1,65 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetCapabilitiesErrorType, + type GetCapabilitiesParameters, + type GetCapabilitiesReturnType, + getCapabilities, +} from '../actions/getCapabilities.js' +import type { Config } from '../createConfig.js' +import { ConnectorNotConnectedError } from '../errors/config.js' +import { filterQueryOptions } from '../query/utils.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' + +export type GetCapabilitiesOptions< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getCapabilitiesQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +>(config: config, options: GetCapabilitiesOptions = {}) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const capabilities = await getCapabilities(config, parameters) + return capabilities + }, + queryKey: getCapabilitiesQueryKey(options), + retry(failureCount, error) { + if (error instanceof ConnectorNotConnectedError) return false + return failureCount < 3 + }, + } as const satisfies QueryOptions< + GetCapabilitiesQueryFnData, + GetCapabilitiesErrorType, + GetCapabilitiesData, + GetCapabilitiesQueryKey + > +} + +export type GetCapabilitiesQueryFnData< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = GetCapabilitiesReturnType + +export type GetCapabilitiesData< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = GetCapabilitiesQueryFnData + +export function getCapabilitiesQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +>(options: GetCapabilitiesOptions = {}) { + return ['capabilities', filterQueryOptions(options)] as const +} + +export type GetCapabilitiesQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = ReturnType> diff --git a/packages/core/src/query/getConnectorClient.test.ts b/packages/core/src/query/getConnectorClient.test.ts new file mode 100644 index 0000000000..fdec57969d --- /dev/null +++ b/packages/core/src/query/getConnectorClient.test.ts @@ -0,0 +1,37 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getConnectorClientQueryOptions } from './getConnectorClient.js' + +test('default', () => { + expect(getConnectorClientQueryOptions(config)).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "connectorClient", + { + "connectorUid": undefined, + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getConnectorClientQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "connectorClient", + { + "chainId": 1, + "connectorUid": undefined, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getConnectorClient.ts b/packages/core/src/query/getConnectorClient.ts new file mode 100644 index 0000000000..7bc65029f0 --- /dev/null +++ b/packages/core/src/query/getConnectorClient.ts @@ -0,0 +1,69 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetConnectorClientErrorType, + type GetConnectorClientParameters, + type GetConnectorClientReturnType, + getConnectorClient, +} from '../actions/getConnectorClient.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetConnectorClientOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & + ScopeKeyParameter +> + +export function getConnectorClientQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetConnectorClientOptions = {}) { + return { + gcTime: 0, + async queryFn({ queryKey }) { + const { connector } = options + const { connectorUid: _, scopeKey: _s, ...parameters } = queryKey[1] + return getConnectorClient(config, { + ...parameters, + connector, + }) as unknown as Promise> + }, + queryKey: getConnectorClientQueryKey(options), + } as const satisfies QueryOptions< + GetConnectorClientQueryFnData, + GetConnectorClientErrorType, + GetConnectorClientData, + GetConnectorClientQueryKey + > +} + +export type GetConnectorClientQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetConnectorClientReturnType + +export type GetConnectorClientData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetConnectorClientQueryFnData + +export function getConnectorClientQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetConnectorClientOptions = {}) { + const { connector, ...parameters } = options + return [ + 'connectorClient', + { ...filterQueryOptions(parameters), connectorUid: connector?.uid }, + ] as const +} + +export type GetConnectorClientQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getEnsAddress.test.ts b/packages/core/src/query/getEnsAddress.test.ts new file mode 100644 index 0000000000..0e88461e61 --- /dev/null +++ b/packages/core/src/query/getEnsAddress.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsAddressQueryOptions } from './getEnsAddress.js' + +test('default', () => { + expect(getEnsAddressQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensAddress", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getEnsAddressQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensAddress", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getEnsAddress.ts b/packages/core/src/query/getEnsAddress.ts new file mode 100644 index 0000000000..7f18c9dd63 --- /dev/null +++ b/packages/core/src/query/getEnsAddress.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetEnsAddressErrorType, + type GetEnsAddressParameters, + type GetEnsAddressReturnType, + getEnsAddress, +} from '../actions/getEnsAddress.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetEnsAddressOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getEnsAddressQueryOptions( + config: config, + options: GetEnsAddressOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { name, scopeKey: _, ...parameters } = queryKey[1] + if (!name) throw new Error('name is required') + return getEnsAddress(config, { ...parameters, name }) + }, + queryKey: getEnsAddressQueryKey(options), + } as const satisfies QueryOptions< + GetEnsAddressQueryFnData, + GetEnsAddressErrorType, + GetEnsAddressData, + GetEnsAddressQueryKey + > +} + +export type GetEnsAddressQueryFnData = GetEnsAddressReturnType + +export type GetEnsAddressData = GetEnsAddressQueryFnData + +export function getEnsAddressQueryKey( + options: GetEnsAddressOptions = {}, +) { + return ['ensAddress', filterQueryOptions(options)] as const +} + +export type GetEnsAddressQueryKey = ReturnType< + typeof getEnsAddressQueryKey +> diff --git a/packages/core/src/query/getEnsAvatar.test.ts b/packages/core/src/query/getEnsAvatar.test.ts new file mode 100644 index 0000000000..8b62526426 --- /dev/null +++ b/packages/core/src/query/getEnsAvatar.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsAvatarQueryOptions } from './getEnsAvatar.js' + +test('default', () => { + expect(getEnsAvatarQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensAvatar", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getEnsAvatarQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensAvatar", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getEnsAvatar.ts b/packages/core/src/query/getEnsAvatar.ts new file mode 100644 index 0000000000..f399736e08 --- /dev/null +++ b/packages/core/src/query/getEnsAvatar.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetEnsAvatarErrorType, + type GetEnsAvatarParameters, + type GetEnsAvatarReturnType, + getEnsAvatar, +} from '../actions/getEnsAvatar.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetEnsAvatarOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getEnsAvatarQueryOptions( + config: config, + options: GetEnsAvatarOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { name, scopeKey: _, ...parameters } = queryKey[1] + if (!name) throw new Error('name is required') + return getEnsAvatar(config, { ...parameters, name }) + }, + queryKey: getEnsAvatarQueryKey(options), + } as const satisfies QueryOptions< + GetEnsAvatarQueryFnData, + GetEnsAvatarErrorType, + GetEnsAvatarData, + GetEnsAvatarQueryKey + > +} + +export type GetEnsAvatarQueryFnData = GetEnsAvatarReturnType + +export type GetEnsAvatarData = GetEnsAvatarQueryFnData + +export function getEnsAvatarQueryKey( + options: GetEnsAvatarOptions = {}, +) { + return ['ensAvatar', filterQueryOptions(options)] as const +} + +export type GetEnsAvatarQueryKey = ReturnType< + typeof getEnsAvatarQueryKey +> diff --git a/packages/core/src/query/getEnsName.test.ts b/packages/core/src/query/getEnsName.test.ts new file mode 100644 index 0000000000..006c76a12d --- /dev/null +++ b/packages/core/src/query/getEnsName.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsNameQueryOptions } from './getEnsName.js' + +test('default', () => { + expect(getEnsNameQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensName", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getEnsNameQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensName", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getEnsName.ts b/packages/core/src/query/getEnsName.ts new file mode 100644 index 0000000000..cff1534e07 --- /dev/null +++ b/packages/core/src/query/getEnsName.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetEnsNameErrorType, + type GetEnsNameParameters, + type GetEnsNameReturnType, + getEnsName, +} from '../actions/getEnsName.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetEnsNameOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getEnsNameQueryOptions( + config: config, + options: GetEnsNameOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, ...parameters } = queryKey[1] + if (!address) throw new Error('address is required') + return getEnsName(config, { ...parameters, address }) + }, + queryKey: getEnsNameQueryKey(options), + } as const satisfies QueryOptions< + GetEnsNameQueryFnData, + GetEnsNameErrorType, + GetEnsNameData, + GetEnsNameQueryKey + > +} + +export type GetEnsNameQueryFnData = GetEnsNameReturnType + +export type GetEnsNameData = GetEnsNameQueryFnData + +export function getEnsNameQueryKey( + options: GetEnsNameOptions = {}, +) { + return ['ensName', filterQueryOptions(options)] as const +} + +export type GetEnsNameQueryKey = ReturnType< + typeof getEnsNameQueryKey +> diff --git a/packages/core/src/query/getEnsResolver.test.ts b/packages/core/src/query/getEnsResolver.test.ts new file mode 100644 index 0000000000..36baf9ba90 --- /dev/null +++ b/packages/core/src/query/getEnsResolver.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsResolverQueryOptions } from './getEnsResolver.js' + +test('default', () => { + expect(getEnsResolverQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensResolver", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getEnsResolverQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensResolver", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getEnsResolver.ts b/packages/core/src/query/getEnsResolver.ts new file mode 100644 index 0000000000..124499325d --- /dev/null +++ b/packages/core/src/query/getEnsResolver.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetEnsResolverErrorType, + type GetEnsResolverParameters, + type GetEnsResolverReturnType, + getEnsResolver, +} from '../actions/getEnsResolver.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetEnsResolverOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getEnsResolverQueryOptions( + config: config, + options: GetEnsResolverOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { name, scopeKey: _, ...parameters } = queryKey[1] + if (!name) throw new Error('name is required') + return getEnsResolver(config, { ...parameters, name }) + }, + queryKey: getEnsResolverQueryKey(options), + } as const satisfies QueryOptions< + GetEnsResolverQueryFnData, + GetEnsResolverErrorType, + GetEnsResolverData, + GetEnsResolverQueryKey + > +} + +export type GetEnsResolverQueryFnData = GetEnsResolverReturnType + +export type GetEnsResolverData = GetEnsResolverQueryFnData + +export function getEnsResolverQueryKey( + options: GetEnsResolverOptions = {}, +) { + return ['ensResolver', filterQueryOptions(options)] as const +} + +export type GetEnsResolverQueryKey = ReturnType< + typeof getEnsResolverQueryKey +> diff --git a/packages/core/src/query/getEnsText.test.ts b/packages/core/src/query/getEnsText.test.ts new file mode 100644 index 0000000000..1370410633 --- /dev/null +++ b/packages/core/src/query/getEnsText.test.ts @@ -0,0 +1,46 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getEnsTextQueryOptions } from './getEnsText.js' + +test('default', () => { + expect( + getEnsTextQueryOptions(config, { + name: 'wevm.eth', + key: 'com.twitter', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensText", + { + "key": "com.twitter", + "name": "wevm.eth", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getEnsTextQueryOptions(config, { + chainId: chain.mainnet.id, + name: 'wevm.eth', + key: 'com.twitter', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "ensText", + { + "chainId": 1, + "key": "com.twitter", + "name": "wevm.eth", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getEnsText.ts b/packages/core/src/query/getEnsText.ts new file mode 100644 index 0000000000..2f1c464dea --- /dev/null +++ b/packages/core/src/query/getEnsText.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetEnsTextErrorType, + type GetEnsTextParameters, + type GetEnsTextReturnType, + getEnsText, +} from '../actions/getEnsText.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetEnsTextOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getEnsTextQueryOptions( + config: config, + options: GetEnsTextOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { key, name, scopeKey: _, ...parameters } = queryKey[1] + if (!key || !name) throw new Error('key and name are required') + return getEnsText(config, { ...parameters, key, name }) + }, + queryKey: getEnsTextQueryKey(options), + } as const satisfies QueryOptions< + GetEnsTextQueryFnData, + GetEnsTextErrorType, + GetEnsTextData, + GetEnsTextQueryKey + > +} + +export type GetEnsTextQueryFnData = GetEnsTextReturnType + +export type GetEnsTextData = GetEnsTextQueryFnData + +export function getEnsTextQueryKey( + options: GetEnsTextOptions = {}, +) { + return ['ensText', filterQueryOptions(options)] as const +} + +export type GetEnsTextQueryKey = ReturnType< + typeof getEnsTextQueryKey +> diff --git a/packages/core/src/query/getFeeHistory.test.ts b/packages/core/src/query/getFeeHistory.test.ts new file mode 100644 index 0000000000..aa40793235 --- /dev/null +++ b/packages/core/src/query/getFeeHistory.test.ts @@ -0,0 +1,128 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getFeeHistoryQueryOptions } from './getFeeHistory.js' + +test('default', async () => { + expect( + getFeeHistoryQueryOptions(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + } + `) +}) + +test('parameters: chainId', async () => { + expect( + getFeeHistoryQueryOptions(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + chainId: chain.mainnet2.id, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 456, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + } + `) +}) + +test('parameters: blockNumber', async () => { + expect( + getFeeHistoryQueryOptions(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + blockNumber: 18677379n, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "blockNumber": 18677379n, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + } + `) +}) + +test('parameters: blockTag', async () => { + expect( + getFeeHistoryQueryOptions(config, { + blockCount: 4, + rewardPercentiles: [25, 75], + blockTag: 'safe', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "blockTag": "safe", + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + } + `) +}) + +test('behavior: blockCount is required', async () => { + const options = getFeeHistoryQueryOptions(config, {}) + expect( + options.queryFn({ + queryKey: options.queryKey, + signal: new AbortSignal(), + meta: undefined, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: blockCount is required]', + ) +}) + +test('behavior: rewardPercentiles is required', async () => { + const options = getFeeHistoryQueryOptions(config, { blockCount: 4 }) + expect( + options.queryFn({ + queryKey: options.queryKey, + signal: new AbortSignal(), + meta: undefined, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '[Error: rewardPercentiles is required]', + ) +}) diff --git a/packages/core/src/query/getFeeHistory.ts b/packages/core/src/query/getFeeHistory.ts new file mode 100644 index 0000000000..6eeef0d681 --- /dev/null +++ b/packages/core/src/query/getFeeHistory.ts @@ -0,0 +1,69 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetFeeHistoryErrorType, + type GetFeeHistoryParameters, + type GetFeeHistoryReturnType, + getFeeHistory, +} from '../actions/getFeeHistory.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, PartialBy } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetFeeHistoryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + PartialBy< + GetFeeHistoryParameters, + 'blockCount' | 'rewardPercentiles' + > & + ScopeKeyParameter +> + +export function getFeeHistoryQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetFeeHistoryOptions = {}) { + return { + async queryFn({ queryKey }) { + const { + blockCount, + rewardPercentiles, + scopeKey: _, + ...parameters + } = queryKey[1] + if (!blockCount) throw new Error('blockCount is required') + if (!rewardPercentiles) throw new Error('rewardPercentiles is required') + const feeHistory = await getFeeHistory(config, { + ...(parameters as GetFeeHistoryParameters), + blockCount, + rewardPercentiles, + }) + return feeHistory ?? null + }, + queryKey: getFeeHistoryQueryKey(options), + } as const satisfies QueryOptions< + GetFeeHistoryQueryFnData, + GetFeeHistoryErrorType, + GetFeeHistoryData, + GetFeeHistoryQueryKey + > +} + +export type GetFeeHistoryQueryFnData = GetFeeHistoryReturnType + +export type GetFeeHistoryData = GetFeeHistoryQueryFnData + +export function getFeeHistoryQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetFeeHistoryOptions = {}) { + return ['feeHistory', filterQueryOptions(options)] as const +} + +export type GetFeeHistoryQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getGasPrice.test.ts b/packages/core/src/query/getGasPrice.test.ts new file mode 100644 index 0000000000..2b90303669 --- /dev/null +++ b/packages/core/src/query/getGasPrice.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getGasPriceQueryOptions } from './getGasPrice.js' + +test('default', () => { + expect(getGasPriceQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "gasPrice", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getGasPriceQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "gasPrice", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getGasPrice.ts b/packages/core/src/query/getGasPrice.ts new file mode 100644 index 0000000000..153d4a102d --- /dev/null +++ b/packages/core/src/query/getGasPrice.ts @@ -0,0 +1,54 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetGasPriceErrorType, + type GetGasPriceParameters, + type GetGasPriceReturnType, + getGasPrice, +} from '../actions/getGasPrice.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetGasPriceOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getGasPriceQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetGasPriceOptions = {}) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, ...parameters } = queryKey[1] + const gasPrice = await getGasPrice(config, parameters) + return gasPrice ?? null + }, + queryKey: getGasPriceQueryKey(options), + } as const satisfies QueryOptions< + GetGasPriceQueryFnData, + GetGasPriceErrorType, + GetGasPriceData, + GetGasPriceQueryKey + > +} + +export type GetGasPriceQueryFnData = GetGasPriceReturnType + +export type GetGasPriceData = GetGasPriceQueryFnData + +export function getGasPriceQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetGasPriceOptions = {}) { + return ['gasPrice', filterQueryOptions(options)] as const +} + +export type GetGasPriceQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getProof.test.ts b/packages/core/src/query/getProof.test.ts new file mode 100644 index 0000000000..30c02256c7 --- /dev/null +++ b/packages/core/src/query/getProof.test.ts @@ -0,0 +1,106 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getProofQueryOptions } from './getProof.js' + +test('default', () => { + expect( + getProofQueryOptions(config, { + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + getProofQueryOptions(config, { + address: '0x4200000000000000000000000000000000000016', + blockNumber: 1234567890n, + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "blockNumber": 1234567890n, + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + getProofQueryOptions(config, { + address: '0x4200000000000000000000000000000000000016', + blockTag: 'safe', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "blockTag": "safe", + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getProofQueryOptions(config, { + address: '0x4200000000000000000000000000000000000016', + chainId: chain.mainnet2.id, + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "chainId": 456, + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getProof.ts b/packages/core/src/query/getProof.ts new file mode 100644 index 0000000000..8da0b42b17 --- /dev/null +++ b/packages/core/src/query/getProof.ts @@ -0,0 +1,50 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetProofErrorType, + type GetProofParameters, + type GetProofReturnType, + getProof, +} from '../actions/getProof.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetProofOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getProofQueryOptions( + config: config, + options: GetProofOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, storageKeys, ...parameters } = queryKey[1] + if (!address || !storageKeys) + throw new Error('address and storageKeys are required') + return getProof(config, { ...parameters, address, storageKeys }) + }, + queryKey: getProofQueryKey(options), + } as const satisfies QueryOptions< + GetProofQueryFnData, + GetProofErrorType, + GetProofData, + GetProofQueryKey + > +} + +export type GetProofQueryFnData = GetProofReturnType + +export type GetProofData = GetProofQueryFnData + +export function getProofQueryKey( + options: GetProofOptions, +) { + return ['getProof', filterQueryOptions(options)] as const +} + +export type GetProofQueryKey = ReturnType< + typeof getProofQueryKey +> diff --git a/packages/core/src/query/getStorageAt.test.ts b/packages/core/src/query/getStorageAt.test.ts new file mode 100644 index 0000000000..87193c1e6f --- /dev/null +++ b/packages/core/src/query/getStorageAt.test.ts @@ -0,0 +1,90 @@ +import { address, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getStorageAtQueryOptions } from './getStorageAt.js' + +test('default', () => { + expect( + getStorageAtQueryOptions(config, { + address: address.wagmiMintExample, + slot: '0x0', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "slot": "0x0", + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + getStorageAtQueryOptions(config, { + address: address.wagmiMintExample, + blockNumber: 16280770n, + slot: '0x0', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockNumber": 16280770n, + "slot": "0x0", + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + getStorageAtQueryOptions(config, { + address: address.wagmiMintExample, + blockTag: 'safe', + slot: '0x0', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockTag": "safe", + "slot": "0x0", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getStorageAtQueryOptions(config, { + address: address.wagmiMintExample, + chainId: chain.mainnet2.id, + slot: '0x0', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 456, + "slot": "0x0", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getStorageAt.ts b/packages/core/src/query/getStorageAt.ts new file mode 100644 index 0000000000..c2ed90c927 --- /dev/null +++ b/packages/core/src/query/getStorageAt.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetStorageAtErrorType, + type GetStorageAtParameters, + type GetStorageAtReturnType, + getStorageAt, +} from '../actions/getStorageAt.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetStorageAtOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getStorageAtQueryOptions( + config: config, + options: GetStorageAtOptions = {}, +) { + return { + queryFn({ queryKey }) { + const { address, slot, scopeKey: _, ...parameters } = queryKey[1] + if (!address || !slot) throw new Error('address and slot are required') + return getStorageAt(config, { ...parameters, address, slot }) + }, + queryKey: getStorageAtQueryKey(options), + } as const satisfies QueryOptions< + GetStorageAtQueryFnData, + GetStorageAtErrorType, + GetStorageAtData, + GetStorageAtQueryKey + > +} + +export type GetStorageAtQueryFnData = GetStorageAtReturnType + +export type GetStorageAtData = GetStorageAtQueryFnData + +export function getStorageAtQueryKey( + options: GetStorageAtOptions, +) { + return ['getStorageAt', filterQueryOptions(options)] as const +} + +export type GetStorageAtQueryKey = ReturnType< + typeof getStorageAtQueryKey +> diff --git a/packages/core/src/query/getToken.test.ts b/packages/core/src/query/getToken.test.ts new file mode 100644 index 0000000000..87ec1731c9 --- /dev/null +++ b/packages/core/src/query/getToken.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTokenQueryOptions } from './getToken.js' + +test('default', () => { + expect(getTokenQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "token", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getTokenQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "token", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getToken.ts b/packages/core/src/query/getToken.ts new file mode 100644 index 0000000000..8e4a2b866a --- /dev/null +++ b/packages/core/src/query/getToken.ts @@ -0,0 +1,49 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetTokenErrorType, + type GetTokenParameters, + type GetTokenReturnType, + getToken, +} from '../actions/getToken.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetTokenOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getTokenQueryOptions( + config: config, + options: GetTokenOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, ...parameters } = queryKey[1] + if (!address) throw new Error('address is required') + return getToken(config, { ...parameters, address }) + }, + queryKey: getTokenQueryKey(options), + } as const satisfies QueryOptions< + GetTokenQueryFnData, + GetTokenErrorType, + GetTokenData, + GetTokenQueryKey + > +} + +export type GetTokenQueryFnData = GetTokenReturnType + +export type GetTokenData = GetTokenQueryFnData + +export function getTokenQueryKey( + options: GetTokenOptions = {}, +) { + return ['token', filterQueryOptions(options)] as const +} + +export type GetTokenQueryKey = ReturnType< + typeof getTokenQueryKey +> diff --git a/packages/core/src/query/getTransaction.test.ts b/packages/core/src/query/getTransaction.test.ts new file mode 100644 index 0000000000..e1db72b325 --- /dev/null +++ b/packages/core/src/query/getTransaction.test.ts @@ -0,0 +1,32 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransactionQueryOptions } from './getTransaction.js' + +test('default', () => { + expect(getTransactionQueryOptions(config)).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transaction", + {}, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getTransactionQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transaction", + { + "chainId": 1, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getTransaction.ts b/packages/core/src/query/getTransaction.ts new file mode 100644 index 0000000000..8564854774 --- /dev/null +++ b/packages/core/src/query/getTransaction.ts @@ -0,0 +1,69 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetTransactionErrorType, + type GetTransactionParameters, + type GetTransactionReturnType, + getTransaction, +} from '../actions/getTransaction.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetTransactionOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getTransactionQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetTransactionOptions = {}) { + return { + async queryFn({ queryKey }) { + const { blockHash, blockNumber, blockTag, hash, index } = queryKey[1] + if (!blockHash && !blockNumber && !blockTag && !hash) + throw new Error('blockHash, blockNumber, blockTag, or hash is required') + if (!hash && !index) + throw new Error( + 'index is required for blockHash, blockNumber, or blockTag', + ) + const { scopeKey: _, ...rest } = queryKey[1] + return getTransaction( + config, + rest as GetTransactionParameters, + ) as unknown as Promise> + }, + queryKey: getTransactionQueryKey(options), + } as const satisfies QueryOptions< + GetTransactionQueryFnData, + GetTransactionErrorType, + GetTransactionData, + GetTransactionQueryKey + > +} + +export type GetTransactionQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetTransactionReturnType + +export type GetTransactionData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetTransactionQueryFnData + +export function getTransactionQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetTransactionOptions = {}) { + return ['transaction', filterQueryOptions(options)] as const +} + +export type GetTransactionQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getTransactionConfirmations.test.ts b/packages/core/src/query/getTransactionConfirmations.test.ts new file mode 100644 index 0000000000..ac4d6d4f4a --- /dev/null +++ b/packages/core/src/query/getTransactionConfirmations.test.ts @@ -0,0 +1,42 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransactionConfirmationsQueryOptions } from './getTransactionConfirmations.js' + +test('default', () => { + expect( + getTransactionConfirmationsQueryOptions(config, { + hash: '0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionConfirmations", + { + "hash": "0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getTransactionConfirmationsQueryOptions(config, { + chainId: chain.mainnet.id, + hash: '0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionConfirmations", + { + "chainId": 1, + "hash": "0xa559259bd2d0e8372421e222ff3545f705b5da60005bd787a23c2e68d6d7fefd", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getTransactionConfirmations.ts b/packages/core/src/query/getTransactionConfirmations.ts new file mode 100644 index 0000000000..5c34decf37 --- /dev/null +++ b/packages/core/src/query/getTransactionConfirmations.ts @@ -0,0 +1,78 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetTransactionConfirmationsErrorType, + type GetTransactionConfirmationsParameters, + type GetTransactionConfirmationsReturnType, + getTransactionConfirmations, +} from '../actions/getTransactionConfirmations.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { UnionExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetTransactionConfirmationsOptions< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +> = UnionExactPartial> & + ScopeKeyParameter + +export function getTransactionConfirmationsQueryOptions< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +>( + config: config, + options: GetTransactionConfirmationsOptions = {} as any, +) { + return { + async queryFn({ queryKey }) { + const { + hash, + transactionReceipt, + scopeKey: _, + ...parameters + } = queryKey[1] + if (!hash && !transactionReceipt) + throw new Error('hash or transactionReceipt is required') + + const confirmations = await getTransactionConfirmations(config, { + hash, + transactionReceipt, + ...(parameters as any), + }) + return confirmations ?? null + }, + queryKey: getTransactionConfirmationsQueryKey(options), + } as const satisfies QueryOptions< + GetTransactionConfirmationsQueryFnData, + GetTransactionConfirmationsErrorType, + GetTransactionConfirmationsData, + GetTransactionConfirmationsQueryKey + > +} + +export type GetTransactionConfirmationsQueryFnData = + GetTransactionConfirmationsReturnType + +export type GetTransactionConfirmationsData = + GetTransactionConfirmationsQueryFnData + +export function getTransactionConfirmationsQueryKey< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +>(options: GetTransactionConfirmationsOptions = {} as any) { + return ['transactionConfirmations', filterQueryOptions(options)] as const +} + +export type GetTransactionConfirmationsQueryKey< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getTransactionCount.test.ts b/packages/core/src/query/getTransactionCount.test.ts new file mode 100644 index 0000000000..666953629b --- /dev/null +++ b/packages/core/src/query/getTransactionCount.test.ts @@ -0,0 +1,82 @@ +import { accounts, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransactionCountQueryOptions } from './getTransactionCount.js' + +const address = accounts[0] + +test('default', () => { + expect( + getTransactionCountQueryOptions(config, { address }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getTransactionCountQueryOptions(config, { + address, + chainId: chain.mainnet.id, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + getTransactionCountQueryOptions(config, { + address, + blockNumber: 13677382n, + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 13677382n, + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + getTransactionCountQueryOptions(config, { + address, + blockTag: 'earliest', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "earliest", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getTransactionCount.ts b/packages/core/src/query/getTransactionCount.ts new file mode 100644 index 0000000000..31fd2c1d8f --- /dev/null +++ b/packages/core/src/query/getTransactionCount.ts @@ -0,0 +1,55 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetTransactionCountErrorType, + type GetTransactionCountParameters, + type GetTransactionCountReturnType, + getTransactionCount, +} from '../actions/getTransactionCount.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, PartialBy } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetTransactionCountOptions = Compute< + PartialBy, 'address'> & + ScopeKeyParameter +> + +export function getTransactionCountQueryOptions( + config: config, + options: GetTransactionCountOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, scopeKey: _, ...parameters } = queryKey[1] + if (!address) throw new Error('address is required') + const transactionCount = await getTransactionCount(config, { + ...(parameters as GetTransactionCountParameters), + address, + }) + return transactionCount ?? null + }, + queryKey: getTransactionCountQueryKey(options), + } as const satisfies QueryOptions< + GetTransactionCountQueryFnData, + GetTransactionCountErrorType, + GetTransactionCountData, + GetTransactionCountQueryKey + > +} + +export type GetTransactionCountQueryFnData = + Compute + +export type GetTransactionCountData = GetTransactionCountQueryFnData + +export function getTransactionCountQueryKey( + options: GetTransactionCountOptions = {}, +) { + return ['transactionCount', filterQueryOptions(options)] as const +} + +export type GetTransactionCountQueryKey = ReturnType< + typeof getTransactionCountQueryKey +> diff --git a/packages/core/src/query/getTransactionReceipt.test.ts b/packages/core/src/query/getTransactionReceipt.test.ts new file mode 100644 index 0000000000..7c719b8d87 --- /dev/null +++ b/packages/core/src/query/getTransactionReceipt.test.ts @@ -0,0 +1,42 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getTransactionReceiptQueryOptions } from './getTransactionReceipt.js' + +test('default', () => { + expect( + getTransactionReceiptQueryOptions(config, { + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getTransactionReceipt", + { + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getTransactionReceiptQueryOptions(config, { + chainId: chain.mainnet2.id, + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 456, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getTransactionReceipt.ts b/packages/core/src/query/getTransactionReceipt.ts new file mode 100644 index 0000000000..0d7d559d49 --- /dev/null +++ b/packages/core/src/query/getTransactionReceipt.ts @@ -0,0 +1,60 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetTransactionReceiptErrorType, + type GetTransactionReceiptParameters, + getTransactionReceipt, +} from '../actions/getTransactionReceipt.js' +import type { GetTransactionReceiptReturnType } from '../actions/getTransactionReceipt.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetTransactionReceiptOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & + ScopeKeyParameter +> + +export function getTransactionReceiptQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetTransactionReceiptOptions = {}) { + return { + queryFn({ queryKey }) { + const { hash, scopeKey: _, ...parameters } = queryKey[1] + if (!hash) throw new Error('hash is required') + return getTransactionReceipt(config, { ...parameters, hash }) + }, + queryKey: getTransactionReceiptQueryKey(options), + } as const satisfies QueryOptions< + GetTransactionReceiptQueryFnData, + GetTransactionReceiptErrorType, + GetTransactionReceiptData, + GetTransactionReceiptQueryKey + > +} +export type GetTransactionReceiptQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetTransactionReceiptReturnType + +export type GetTransactionReceiptData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetTransactionReceiptQueryFnData + +export function getTransactionReceiptQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetTransactionReceiptOptions) { + return ['getTransactionReceipt', filterQueryOptions(options)] as const +} + +export type GetTransactionReceiptQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/getWalletClient.test.ts b/packages/core/src/query/getWalletClient.test.ts new file mode 100644 index 0000000000..899b480882 --- /dev/null +++ b/packages/core/src/query/getWalletClient.test.ts @@ -0,0 +1,37 @@ +import { chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { getWalletClientQueryOptions } from './getWalletClient.js' + +test('default', () => { + expect(getWalletClientQueryOptions(config)).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "walletClient", + { + "connectorUid": undefined, + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + getWalletClientQueryOptions(config, { chainId: chain.mainnet.id }), + ).toMatchInlineSnapshot(` + { + "gcTime": 0, + "queryFn": [Function], + "queryKey": [ + "walletClient", + { + "chainId": 1, + "connectorUid": undefined, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/getWalletClient.ts b/packages/core/src/query/getWalletClient.ts new file mode 100644 index 0000000000..3c64b39a07 --- /dev/null +++ b/packages/core/src/query/getWalletClient.ts @@ -0,0 +1,65 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type GetWalletClientErrorType, + type GetWalletClientParameters, + type GetWalletClientReturnType, + getWalletClient, +} from '../actions/getWalletClient.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type GetWalletClientOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function getWalletClientQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>(config: config, options: GetWalletClientOptions = {}) { + return { + gcTime: 0, + async queryFn({ queryKey }) { + const { connector } = options + const { connectorUid: _, scopeKey: _s, ...parameters } = queryKey[1] + return getWalletClient(config, { ...parameters, connector }) + }, + queryKey: getWalletClientQueryKey(options), + } as const satisfies QueryOptions< + GetWalletClientQueryFnData, + GetWalletClientErrorType, + GetWalletClientData, + GetWalletClientQueryKey + > +} + +export type GetWalletClientQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetWalletClientReturnType + +export type GetWalletClientData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = GetWalletClientQueryFnData + +export function getWalletClientQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: GetWalletClientOptions = {}) { + const { connector, ...parameters } = options + return [ + 'walletClient', + { ...filterQueryOptions(parameters), connectorUid: connector?.uid }, + ] as const +} + +export type GetWalletClientQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/infiniteReadContracts.test-d.ts b/packages/core/src/query/infiniteReadContracts.test-d.ts new file mode 100644 index 0000000000..c76dd3bccc --- /dev/null +++ b/packages/core/src/query/infiniteReadContracts.test-d.ts @@ -0,0 +1,201 @@ +import { abi, config } from '@wagmi/test' +import type { MulticallResponse } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { infiniteReadContractsQueryOptions } from './infiniteReadContracts.js' + +test('default', async () => { + const options = infiniteReadContractsQueryOptions(config, { + cacheKey: 'foo', + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf(options.initialPageParam) + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + query: { + initialPageParam: 0, + getNextPageParam(lastPage, allPages, lastPageParam, allPageParams) { + expectTypeOf(lastPage).toEqualTypeOf< + [MulticallResponse, MulticallResponse] + >() + expectTypeOf(allPages).toEqualTypeOf< + [MulticallResponse, MulticallResponse][] + >() + expectTypeOf(lastPageParam).toEqualTypeOf(options.initialPageParam) + expectTypeOf(allPageParams).toEqualTypeOf([options.initialPageParam]) + return lastPageParam + 1 + }, + }, + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf< + [MulticallResponse, MulticallResponse] + >() +}) + +test('allowFailure: false', async () => { + const options = infiniteReadContractsQueryOptions(config, { + allowFailure: false, + cacheKey: 'foo', + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf(options.initialPageParam) + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + query: { + initialPageParam: 0, + getNextPageParam(lastPage, allPages, lastPageParam, allPageParams) { + expectTypeOf(lastPage).toEqualTypeOf<[bigint, string]>() + expectTypeOf(allPages).toEqualTypeOf<[bigint, string][]>() + expectTypeOf(lastPageParam).toEqualTypeOf(options.initialPageParam) + expectTypeOf(allPageParams).toEqualTypeOf([options.initialPageParam]) + return lastPageParam + 1 + }, + }, + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf<[bigint, string]>() +}) + +test('initialPageParam', async () => { + const options = infiniteReadContractsQueryOptions(config, { + allowFailure: false, + cacheKey: 'foo', + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf(options.initialPageParam) + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + query: { + initialPageParam: 'bar', + getNextPageParam(lastPage, allPages, lastPageParam, allPageParams) { + expectTypeOf(lastPage).toEqualTypeOf<[bigint, string]>() + expectTypeOf(allPages).toEqualTypeOf<[bigint, string][]>() + expectTypeOf(lastPageParam).toEqualTypeOf(options.initialPageParam) + expectTypeOf(allPageParams).toEqualTypeOf([options.initialPageParam]) + return lastPageParam + 1 + }, + }, + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf<[bigint, string]>() +}) + +test('behavior: `contracts` after `getNextPageParam`', async () => { + const options = infiniteReadContractsQueryOptions(config, { + allowFailure: false, + cacheKey: 'foo', + query: { + initialPageParam: 0, + getNextPageParam(lastPage, allPages, lastPageParam, allPageParams) { + expectTypeOf(lastPage).toEqualTypeOf() + expectTypeOf(allPages).toEqualTypeOf() + expectTypeOf(lastPageParam).toEqualTypeOf(options.initialPageParam) + expectTypeOf(allPageParams).toEqualTypeOf([options.initialPageParam]) + return lastPageParam + 1 + }, + }, + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf(options.initialPageParam) + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf() +}) + +test('overloads', async () => { + const options = infiniteReadContractsQueryOptions(config, { + allowFailure: false, + cacheKey: 'foo', + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf() + return [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }, + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }, + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }, + ] + }, + query: { + initialPageParam: 0, + getNextPageParam(_, allPages) { + return allPages.length + 1 + }, + }, + }) + + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf< + [ + number, + string, + { + foo: `0x${string}` + bar: `0x${string}` + }, + ] + >() +}) diff --git a/packages/core/src/query/infiniteReadContracts.test.ts b/packages/core/src/query/infiniteReadContracts.test.ts new file mode 100644 index 0000000000..509e984838 --- /dev/null +++ b/packages/core/src/query/infiniteReadContracts.test.ts @@ -0,0 +1,60 @@ +import { abi, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { infiniteReadContractsQueryOptions } from './infiniteReadContracts.js' + +test('default', () => { + expect( + infiniteReadContractsQueryOptions(config, { + allowFailure: true, + batchSize: 1024, + blockNumber: 123n, + blockTag: 'latest', + cacheKey: 'foo', + chainId: 1, + multicallAddress: '0x', + scopeKey: 'bar', + contracts(_pageParam) { + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + query: { + initialPageParam: 0, + getNextPageParam(_lastPage, _allPages, lastPageParam, _allPageParams) { + return lastPageParam + 1 + }, + }, + }), + ).toMatchInlineSnapshot(` + { + "getNextPageParam": [Function], + "initialPageParam": 0, + "queryFn": [Function], + "queryKey": [ + "infiniteReadContracts", + { + "allowFailure": true, + "batchSize": 1024, + "blockNumber": 123n, + "blockTag": "latest", + "cacheKey": "foo", + "chainId": 1, + "multicallAddress": "0x", + "scopeKey": "bar", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/infiniteReadContracts.ts b/packages/core/src/query/infiniteReadContracts.ts new file mode 100644 index 0000000000..28a8d3f52a --- /dev/null +++ b/packages/core/src/query/infiniteReadContracts.ts @@ -0,0 +1,127 @@ +import type { ContractFunctionParameters } from 'viem' +import { + type ReadContractsErrorType, + type ReadContractsParameters, + type ReadContractsReturnType, + readContracts, +} from '../actions/readContracts.js' +import type { Config } from '../createConfig.js' +import type { + ChainIdParameter, + ScopeKeyParameter, +} from '../types/properties.js' +import type { StrictOmit } from '../types/utils.js' +import type { InfiniteQueryOptions } from './types.js' +import { filterQueryOptions } from './utils.js' + +export type InfiniteReadContractsOptions< + contracts extends readonly unknown[], + allowFailure extends boolean, + pageParam, + config extends Config, +> = { + cacheKey: string + contracts( + pageParam: pageParam, + ): ReadContractsParameters['contracts'] +} & StrictOmit< + ReadContractsParameters, + 'contracts' +> & + ScopeKeyParameter + +export function infiniteReadContractsQueryOptions< + config extends Config, + const contracts extends readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + pageParam = unknown, +>( + config: config, + options: InfiniteReadContractsOptions< + contracts, + allowFailure, + pageParam, + config + > & + ChainIdParameter & + RequiredPageParamsParameters, +) { + return { + ...options.query, + async queryFn({ pageParam, queryKey }) { + const { contracts } = options + const { cacheKey: _, scopeKey: _s, ...parameters } = queryKey[1] + return (await readContracts(config, { + ...parameters, + contracts: contracts(pageParam as any), + })) as ReadContractsReturnType + }, + queryKey: infiniteReadContractsQueryKey(options), + } as const satisfies InfiniteQueryOptions< + InfiniteReadContractsQueryFnData, + ReadContractsErrorType, + InfiniteReadContractsData, + InfiniteReadContractsData, + InfiniteReadContractsQueryKey, + pageParam + > +} + +type RequiredPageParamsParameters< + contracts extends readonly unknown[], + allowFailure extends boolean, + pageParam, +> = { + query: { + initialPageParam: pageParam + getNextPageParam( + lastPage: InfiniteReadContractsQueryFnData, + allPages: InfiniteReadContractsQueryFnData[], + lastPageParam: pageParam, + allPageParams: pageParam[], + ): pageParam | undefined | null + } +} + +export type InfiniteReadContractsQueryFnData< + contracts extends readonly unknown[], + allowFailure extends boolean, +> = ReadContractsReturnType + +export type InfiniteReadContractsData< + contracts extends readonly unknown[], + allowFailure extends boolean, +> = InfiniteReadContractsQueryFnData + +export function infiniteReadContractsQueryKey< + config extends Config, + const contracts extends readonly unknown[], + allowFailure extends boolean, + pageParam, +>( + options: InfiniteReadContractsOptions< + contracts, + allowFailure, + pageParam, + config + > & + ChainIdParameter & + RequiredPageParamsParameters, +) { + const { contracts: _, query: _q, ...parameters } = options + return ['infiniteReadContracts', filterQueryOptions(parameters)] as const +} + +export type InfiniteReadContractsQueryKey< + contracts extends readonly unknown[], + allowFailure extends boolean, + pageParam, + config extends Config, +> = ReturnType< + typeof infiniteReadContractsQueryKey< + config, + contracts, + allowFailure, + pageParam + > +> diff --git a/packages/core/src/query/prepareTransactionRequest.test.ts b/packages/core/src/query/prepareTransactionRequest.test.ts new file mode 100644 index 0000000000..d33c156be4 --- /dev/null +++ b/packages/core/src/query/prepareTransactionRequest.test.ts @@ -0,0 +1,241 @@ +import { accounts, chain, config } from '@wagmi/test' +import { parseEther, parseGwei } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { expect, test } from 'vitest' + +import { prepareTransactionRequestQueryOptions } from './prepareTransactionRequest.js' + +const targetAccount = accounts[0] + +test('default', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: account', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + account: privateKeyToAccount( + '0x0123456789012345678901234567890123456789012345678901234567890123', + ), + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "account": { + "address": "0x14791697260E4c9A71f18484C9f997B308e59325", + "nonceManager": undefined, + "publicKey": "0x046655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a3515217e88dd05e938efdd71b2cce322bf01da96cd42087b236e8f5043157a9c068e", + "sign": [Function], + "signAuthorization": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "privateKey", + "type": "local", + }, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: data', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "data": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + chainId: chain.mainnet2.id, + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "chainId": 456, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: nonce', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + nonce: 5, + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "nonce": 5, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: gasPrice', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + gasPrice: parseGwei('10'), + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "gasPrice": 10000000000n, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: maxFeePerGas', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + maxFeePerGas: parseGwei('100'), + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "maxFeePerGas": 100000000000n, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: maxPriorityFeePerGas', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + maxPriorityFeePerGas: parseGwei('5'), + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "maxPriorityFeePerGas": 5000000000n, + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: type', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + type: 'eip1559', + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "eip1559", + "value": 1000000000000000000n, + }, + ], + } + `) +}) + +test('parameters: parameters', () => { + expect( + prepareTransactionRequestQueryOptions(config, { + parameters: ['gas'], + to: targetAccount, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "prepareTransactionRequest", + { + "parameters": [ + "gas", + ], + "to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "value": 1000000000000000000n, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/prepareTransactionRequest.ts b/packages/core/src/query/prepareTransactionRequest.ts new file mode 100644 index 0000000000..5ed4b419fa --- /dev/null +++ b/packages/core/src/query/prepareTransactionRequest.ts @@ -0,0 +1,101 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import type { PrepareTransactionRequestRequest as viem_PrepareTransactionRequestRequest } from 'viem' + +import { + type PrepareTransactionRequestErrorType, + type PrepareTransactionRequestParameters, + type PrepareTransactionRequestReturnType, + prepareTransactionRequest, +} from '../actions/prepareTransactionRequest.js' +import type { Config } from '../createConfig.js' +import type { SelectChains } from '../types/chain.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { UnionExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type PrepareTransactionRequestOptions< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +> = UnionExactPartial< + PrepareTransactionRequestParameters +> & + ScopeKeyParameter + +export function prepareTransactionRequestQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +>( + config: config, + options: PrepareTransactionRequestOptions< + config, + chainId, + request + > = {} as any, +) { + return { + queryFn({ queryKey }) { + const { scopeKey: _, to, ...parameters } = queryKey[1] + if (!to) throw new Error('to is required') + return prepareTransactionRequest(config, { + to, + ...(parameters as any), + }) as unknown as Promise< + PrepareTransactionRequestQueryFnData + > + }, + queryKey: prepareTransactionRequestQueryKey(options), + } as const satisfies QueryOptions< + PrepareTransactionRequestQueryFnData, + PrepareTransactionRequestErrorType, + PrepareTransactionRequestData, + PrepareTransactionRequestQueryKey + > +} +export type PrepareTransactionRequestQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +> = PrepareTransactionRequestReturnType + +export type PrepareTransactionRequestData< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +> = PrepareTransactionRequestQueryFnData + +export function prepareTransactionRequestQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +>(options: PrepareTransactionRequestOptions) { + return ['prepareTransactionRequest', filterQueryOptions(options)] as const +} + +export type PrepareTransactionRequestQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, +> = ReturnType< + typeof prepareTransactionRequestQueryKey +> diff --git a/packages/core/src/query/readContract.test-d.ts b/packages/core/src/query/readContract.test-d.ts new file mode 100644 index 0000000000..286b8819c7 --- /dev/null +++ b/packages/core/src/query/readContract.test-d.ts @@ -0,0 +1,15 @@ +import { abi, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { readContractQueryOptions } from './readContract.js' + +test('default', async () => { + const options = readContractQueryOptions(config, { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/core/src/query/readContract.test.ts b/packages/core/src/query/readContract.test.ts new file mode 100644 index 0000000000..6c17b849ab --- /dev/null +++ b/packages/core/src/query/readContract.test.ts @@ -0,0 +1,29 @@ +import { abi, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { readContractQueryOptions } from './readContract.js' + +test('default', () => { + expect( + readContractQueryOptions(config, { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "readContract", + { + "address": "0x", + "args": [ + "0x", + ], + "functionName": "balanceOf", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/readContract.ts b/packages/core/src/query/readContract.ts new file mode 100644 index 0000000000..52c586665f --- /dev/null +++ b/packages/core/src/query/readContract.ts @@ -0,0 +1,93 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' + +import { + type ReadContractErrorType, + type ReadContractParameters, + type ReadContractReturnType, + readContract, +} from '../actions/readContract.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { UnionExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type ReadContractOptions< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config, +> = UnionExactPartial> & + ScopeKeyParameter + +export function readContractQueryOptions< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, +>( + config: config, + options: ReadContractOptions = {} as any, +) { + return { + // TODO: Support `signal` once Viem actions allow passthrough + // https://tkdodo.eu/blog/why-you-want-react-query#bonus-cancellation + async queryFn({ queryKey }) { + const abi = options.abi as Abi + if (!abi) throw new Error('abi is required') + + const { functionName, scopeKey: _, ...parameters } = queryKey[1] + const addressOrCodeParams = (() => { + const params = queryKey[1] as unknown as ReadContractParameters + if (params.address) return { address: params.address } + if (params.code) return { code: params.code } + throw new Error('address or code is required') + })() + + if (!functionName) throw new Error('functionName is required') + + return readContract(config, { + abi, + functionName, + args: parameters.args as readonly unknown[], + ...addressOrCodeParams, + ...parameters, + }) as Promise> + }, + queryKey: readContractQueryKey(options as any) as any, + } as const satisfies QueryOptions< + ReadContractQueryFnData, + ReadContractErrorType, + ReadContractData, + ReadContractQueryKey + > +} + +export type ReadContractQueryFnData< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, +> = ReadContractReturnType + +export type ReadContractData< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, +> = ReadContractQueryFnData + +export function readContractQueryKey< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, +>(options: ReadContractOptions = {} as any) { + const { abi: _, ...rest } = options + return ['readContract', filterQueryOptions(rest)] as const +} + +export type ReadContractQueryKey< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config, +> = ReturnType> diff --git a/packages/core/src/query/readContracts.test-d.ts b/packages/core/src/query/readContracts.test-d.ts new file mode 100644 index 0000000000..b6236e191b --- /dev/null +++ b/packages/core/src/query/readContracts.test-d.ts @@ -0,0 +1,58 @@ +import { abi, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { readContractsQueryOptions } from './readContracts.js' + +test('default', async () => { + const options = readContractsQueryOptions(config, { + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf< + [ + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: bigint; status: 'success' } + ), + ( + | { error: Error; result?: undefined; status: 'failure' } + | { error?: undefined; result: string; status: 'success' } + ), + ] + >() +}) + +test('allowFailure: false', async () => { + const options = readContractsQueryOptions(config, { + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + }) + const result = await options.queryFn({} as any) + expectTypeOf(result).toEqualTypeOf<[bigint, string]>() +}) diff --git a/packages/core/src/query/readContracts.test.ts b/packages/core/src/query/readContracts.test.ts new file mode 100644 index 0000000000..755b80c202 --- /dev/null +++ b/packages/core/src/query/readContracts.test.ts @@ -0,0 +1,38 @@ +import { abi, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { readContractsQueryOptions } from './readContracts.js' + +test('default', () => { + expect( + readContractsQueryOptions(config, { + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + ], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "readContracts", + { + "contracts": [ + { + "address": "0x", + "args": [ + "0x", + ], + "chainId": undefined, + "functionName": "balanceOf", + }, + ], + }, + ], + } + `) +}) diff --git a/packages/core/src/query/readContracts.ts b/packages/core/src/query/readContracts.ts new file mode 100644 index 0000000000..96bb2163cd --- /dev/null +++ b/packages/core/src/query/readContracts.ts @@ -0,0 +1,98 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { + ContractFunctionParameters, + MulticallParameters as viem_MulticallParameters, +} from 'viem' + +import { + type ReadContractsErrorType, + type ReadContractsReturnType, + readContracts, +} from '../actions/readContracts.js' +import type { Config } from '../createConfig.js' +import type { ChainIdParameter } from '../types/properties.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type ReadContractsOptions< + contracts extends readonly unknown[], + allowFailure extends boolean, + config extends Config, +> = ExactPartial< + viem_MulticallParameters< + contracts, + allowFailure, + { optional: true; properties: ChainIdParameter } + > +> & + ScopeKeyParameter + +export function readContractsQueryOptions< + config extends Config, + const contracts extends readonly unknown[], + allowFailure extends boolean = true, +>( + config: config, + options: ReadContractsOptions & + ChainIdParameter = {}, +) { + return { + async queryFn({ queryKey }) { + const contracts: ContractFunctionParameters[] = [] + const length = queryKey[1].contracts.length + for (let i = 0; i < length; i++) { + const contract = queryKey[1].contracts[i]! + const abi = (options.contracts?.[i] as ContractFunctionParameters).abi + contracts.push({ ...contract, abi }) + } + const { scopeKey: _, ...parameters } = queryKey[1] + return readContracts(config, { + ...parameters, + contracts, + }) as Promise> + }, + queryKey: readContractsQueryKey(options), + } as const satisfies QueryOptions< + ReadContractsQueryFnData, + ReadContractsErrorType, + ReadContractsData, + ReadContractsQueryKey + > +} + +export type ReadContractsQueryFnData< + contracts extends readonly unknown[], + allowFailure extends boolean, +> = ReadContractsReturnType + +export type ReadContractsData< + contracts extends readonly unknown[], + allowFailure extends boolean, +> = ReadContractsQueryFnData + +export function readContractsQueryKey< + config extends Config, + const contracts extends readonly unknown[], + allowFailure extends boolean, +>( + options: ReadContractsOptions & + ChainIdParameter = {}, +) { + const contracts = [] + for (const contract of (options.contracts ?? + []) as (ContractFunctionParameters & { chainId: number })[]) { + const { abi: _, ...rest } = contract + contracts.push({ ...rest, chainId: rest.chainId ?? options.chainId }) + } + return [ + 'readContracts', + filterQueryOptions({ ...options, contracts }), + ] as const +} + +export type ReadContractsQueryKey< + contracts extends readonly unknown[], + allowFailure extends boolean, + config extends Config, +> = ReturnType> diff --git a/packages/core/src/query/reconnect.test.ts b/packages/core/src/query/reconnect.test.ts new file mode 100644 index 0000000000..fbcb56c3fc --- /dev/null +++ b/packages/core/src/query/reconnect.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { reconnectMutationOptions } from './reconnect.js' + +test('default', () => { + expect(reconnectMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "reconnect", + ], + } + `) +}) diff --git a/packages/core/src/query/reconnect.ts b/packages/core/src/query/reconnect.ts new file mode 100644 index 0000000000..13126cdd34 --- /dev/null +++ b/packages/core/src/query/reconnect.ts @@ -0,0 +1,42 @@ +import type { MutationOptions } from '@tanstack/query-core' + +import { + type ReconnectErrorType, + type ReconnectParameters, + type ReconnectReturnType, + reconnect, +} from '../actions/reconnect.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' +import type { Mutate, MutateAsync } from './types.js' + +export function reconnectMutationOptions(config: Config) { + return { + mutationFn(variables) { + return reconnect(config, variables) + }, + mutationKey: ['reconnect'], + } as const satisfies MutationOptions< + ReconnectData, + ReconnectErrorType, + ReconnectVariables + > +} + +export type ReconnectData = Compute + +export type ReconnectVariables = ReconnectParameters | undefined + +export type ReconnectMutate = Mutate< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context +> + +export type ReconnectMutateAsync = MutateAsync< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context +> diff --git a/packages/core/src/query/sendCalls.test.ts b/packages/core/src/query/sendCalls.test.ts new file mode 100644 index 0000000000..9892aa43e3 --- /dev/null +++ b/packages/core/src/query/sendCalls.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { sendCallsMutationOptions } from './sendCalls.js' + +test('default', () => { + expect(sendCallsMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "sendCalls", + ], + } + `) +}) diff --git a/packages/core/src/query/sendCalls.ts b/packages/core/src/query/sendCalls.ts new file mode 100644 index 0000000000..5d07b89b8f --- /dev/null +++ b/packages/core/src/query/sendCalls.ts @@ -0,0 +1,67 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { + type SendCallsErrorType, + type SendCallsParameters, + type SendCallsReturnType, + sendCalls, +} from '../actions/sendCalls.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function sendCallsMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return sendCalls(config, variables) + }, + mutationKey: ['sendCalls'], + } as const satisfies MutationOptions< + SendCallsData, + SendCallsErrorType, + SendCallsVariables + > +} + +export type SendCallsData = Compute + +export type SendCallsVariables< + config extends Config, + chainId extends config['chains'][number]['id'], + calls extends readonly unknown[] = readonly unknown[], +> = SendCallsParameters + +export type SendCallsMutate = < + const calls extends readonly unknown[], + chainId extends config['chains'][number]['id'], +>( + variables: SendCallsVariables, + options?: + | Compute< + MutateOptions< + SendCallsData, + SendCallsErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type SendCallsMutateAsync = < + const calls extends readonly unknown[], + chainId extends config['chains'][number]['id'], +>( + variables: SendCallsVariables, + options?: + | Compute< + MutateOptions< + SendCallsData, + SendCallsErrorType, + Compute>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/query/sendTransaction.test.ts b/packages/core/src/query/sendTransaction.test.ts new file mode 100644 index 0000000000..4cda333e71 --- /dev/null +++ b/packages/core/src/query/sendTransaction.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { sendTransactionMutationOptions } from './sendTransaction.js' + +test('default', () => { + expect(sendTransactionMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "sendTransaction", + ], + } + `) +}) diff --git a/packages/core/src/query/sendTransaction.ts b/packages/core/src/query/sendTransaction.ts new file mode 100644 index 0000000000..e6df10fef7 --- /dev/null +++ b/packages/core/src/query/sendTransaction.ts @@ -0,0 +1,65 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { + type SendTransactionErrorType, + type SendTransactionParameters, + type SendTransactionReturnType, + sendTransaction, +} from '../actions/sendTransaction.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function sendTransactionMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return sendTransaction(config, variables) + }, + mutationKey: ['sendTransaction'], + } as const satisfies MutationOptions< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables + > +} + +export type SendTransactionData = Compute + +export type SendTransactionVariables< + config extends Config, + chainId extends config['chains'][number]['id'], +> = SendTransactionParameters + +export type SendTransactionMutate = < + chainId extends config['chains'][number]['id'], +>( + variables: SendTransactionVariables, + options?: + | Compute< + MutateOptions< + SendTransactionData, + SendTransactionErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type SendTransactionMutateAsync< + config extends Config, + context = unknown, +> = ( + variables: SendTransactionVariables, + options?: + | Compute< + MutateOptions< + SendTransactionData, + SendTransactionErrorType, + Compute>, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/query/showCallsStatus.test.ts b/packages/core/src/query/showCallsStatus.test.ts new file mode 100644 index 0000000000..ad26ab6171 --- /dev/null +++ b/packages/core/src/query/showCallsStatus.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { showCallsStatusMutationOptions } from './showCallsStatus.js' + +test('default', () => { + expect(showCallsStatusMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "showCallsStatus", + ], + } + `) +}) diff --git a/packages/core/src/query/showCallsStatus.ts b/packages/core/src/query/showCallsStatus.ts new file mode 100644 index 0000000000..86a703cbc8 --- /dev/null +++ b/packages/core/src/query/showCallsStatus.ts @@ -0,0 +1,57 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { + type ShowCallsStatusErrorType, + type ShowCallsStatusParameters, + type ShowCallsStatusReturnType, + showCallsStatus, +} from '../actions/showCallsStatus.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function showCallsStatusMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return showCallsStatus(config, variables) + }, + mutationKey: ['showCallsStatus'], + } as const satisfies MutationOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables + > +} + +export type ShowCallsStatusData = Compute + +export type ShowCallsStatusVariables = ShowCallsStatusParameters + +export type ShowCallsStatusMutate = ( + variables: ShowCallsStatusVariables, + options?: + | Compute< + MutateOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + Compute, + context + > + > + | undefined, +) => void + +export type ShowCallsStatusMutateAsync = ( + variables: ShowCallsStatusVariables, + options?: + | Compute< + MutateOptions< + ShowCallsStatusData, + ShowCallsStatusErrorType, + Compute, + context + > + > + | undefined, +) => Promise diff --git a/packages/core/src/query/signMessage.test.ts b/packages/core/src/query/signMessage.test.ts new file mode 100644 index 0000000000..b8ad84d0af --- /dev/null +++ b/packages/core/src/query/signMessage.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { signMessageMutationOptions } from './signMessage.js' + +test('default', () => { + expect(signMessageMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "signMessage", + ], + } + `) +}) diff --git a/packages/core/src/query/signMessage.ts b/packages/core/src/query/signMessage.ts new file mode 100644 index 0000000000..d2a4d8156a --- /dev/null +++ b/packages/core/src/query/signMessage.ts @@ -0,0 +1,42 @@ +import type { MutationOptions } from '@tanstack/query-core' + +import { + type SignMessageErrorType, + type SignMessageParameters, + type SignMessageReturnType, + signMessage, +} from '../actions/signMessage.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' +import type { Mutate, MutateAsync } from './types.js' + +export function signMessageMutationOptions(config: Config) { + return { + mutationFn(variables) { + return signMessage(config, variables) + }, + mutationKey: ['signMessage'], + } as const satisfies MutationOptions< + SignMessageData, + SignMessageErrorType, + SignMessageVariables + > +} + +export type SignMessageData = SignMessageReturnType + +export type SignMessageVariables = Compute + +export type SignMessageMutate = Mutate< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context +> + +export type SignMessageMutateAsync = MutateAsync< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context +> diff --git a/packages/core/src/query/signTypedData.test.ts b/packages/core/src/query/signTypedData.test.ts new file mode 100644 index 0000000000..02cbb66480 --- /dev/null +++ b/packages/core/src/query/signTypedData.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { signTypedDataMutationOptions } from './signTypedData.js' + +test('default', () => { + expect(signTypedDataMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "signTypedData", + ], + } + `) +}) diff --git a/packages/core/src/query/signTypedData.ts b/packages/core/src/query/signTypedData.ts new file mode 100644 index 0000000000..fe8eeb0268 --- /dev/null +++ b/packages/core/src/query/signTypedData.ts @@ -0,0 +1,75 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import type { TypedData } from 'viem' +import { + type SignTypedDataErrorType, + type SignTypedDataParameters, + type SignTypedDataReturnType, + signTypedData, +} from '../actions/signTypedData.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function signTypedDataMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return signTypedData(config, variables) + }, + mutationKey: ['signTypedData'], + } as const satisfies MutationOptions< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables + > +} + +export type SignTypedDataData = Compute + +export type SignTypedDataVariables< + typedData extends TypedData | Record = TypedData, + primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData, + /// + primaryTypes = typedData extends TypedData ? keyof typedData : string, +> = SignTypedDataParameters + +export type SignTypedDataMutate = < + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + variables: SignTypedDataVariables, + options?: + | MutateOptions< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables< + typedData, + primaryType, + // use `primaryType` to make sure it's not union of all possible primary types + primaryType + >, + context + > + | undefined, +) => void + +export type SignTypedDataMutateAsync = < + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + variables: SignTypedDataVariables, + options?: + | MutateOptions< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables< + typedData, + primaryType, + // use `primaryType` to make sure it's not union of all possible primary types + primaryType + >, + context + > + | undefined, +) => Promise diff --git a/packages/core/src/query/simulateContract.test-d.ts b/packages/core/src/query/simulateContract.test-d.ts new file mode 100644 index 0000000000..11023e7297 --- /dev/null +++ b/packages/core/src/query/simulateContract.test-d.ts @@ -0,0 +1,81 @@ +import { abi } from '@wagmi/test' +import { http, type Address } from 'viem' +import { celo, mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { + type SimulateContractOptions, + simulateContractQueryOptions, +} from './simulateContract.js' + +test('chain formatters', () => { + const config = createConfig({ + chains: [mainnet, celo], + transports: { [celo.id]: http(), [mainnet.id]: http() }, + }) + + type Result = SimulateContractOptions< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + (typeof config)['chains'][number]['id'] + > + expectTypeOf().toMatchTypeOf<{ + chainId?: typeof celo.id | typeof mainnet.id | undefined + feeCurrency?: `0x${string}` | undefined + }>() + simulateContractQueryOptions(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + + type Result2 = SimulateContractOptions< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toMatchTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + feeCurrency?: `0x${string}` | undefined + }>() + simulateContractQueryOptions(config, { + chainId: celo.id, + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + feeCurrency: '0x', + }) + + type Result3 = SimulateContractOptions< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof mainnet.id + > + expectTypeOf().toMatchTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + }>() + expectTypeOf().not.toMatchTypeOf<{ + feeCurrency?: `0x${string}` | undefined + }>() + simulateContractQueryOptions(config, { + chainId: mainnet.id, + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/core/src/query/simulateContract.test.ts b/packages/core/src/query/simulateContract.test.ts new file mode 100644 index 0000000000..354e4b0f02 --- /dev/null +++ b/packages/core/src/query/simulateContract.test.ts @@ -0,0 +1,31 @@ +import { abi, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { simulateContractQueryOptions } from './simulateContract.js' + +test('default', () => { + expect( + simulateContractQueryOptions(config, { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "simulateContract", + { + "address": "0x", + "args": [ + "0x", + "0x", + 123n, + ], + "functionName": "transferFrom", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/simulateContract.ts b/packages/core/src/query/simulateContract.ts new file mode 100644 index 0000000000..c3970999d4 --- /dev/null +++ b/packages/core/src/query/simulateContract.ts @@ -0,0 +1,132 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' + +import { + type SimulateContractErrorType, + type SimulateContractParameters, + type SimulateContractReturnType, + simulateContract, +} from '../actions/simulateContract.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { UnionExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type SimulateContractOptions< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = UnionExactPartial< + SimulateContractParameters +> & + ScopeKeyParameter + +export function simulateContractQueryOptions< + config extends Config, + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'] | undefined, +>( + config: config, + options: SimulateContractOptions< + abi, + functionName, + args, + config, + chainId + > = {} as any, +) { + return { + async queryFn({ queryKey }) { + const { abi, connector } = options + if (!abi) throw new Error('abi is required') + const { scopeKey: _, ...parameters } = queryKey[1] + const { address, functionName } = parameters + if (!address) throw new Error('address is required') + if (!functionName) throw new Error('functionName is required') + return simulateContract(config, { + abi, + connector, + ...(parameters as any), + }) + }, + queryKey: simulateContractQueryKey(options), + } as const satisfies QueryOptions< + SimulateContractQueryFnData, + SimulateContractErrorType, + SimulateContractData, + SimulateContractQueryKey + > +} + +export type SimulateContractQueryFnData< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = SimulateContractReturnType + +export type SimulateContractData< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = SimulateContractQueryFnData + +export function simulateContractQueryKey< + config extends Config, + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'] | undefined, +>( + options: SimulateContractOptions< + abi, + functionName, + args, + config, + chainId + > = {} as any, +) { + const { abi: _, connector: _c, ...rest } = options + return ['simulateContract', filterQueryOptions(rest)] as const +} + +export type SimulateContractQueryKey< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config, + chainId extends config['chains'][number]['id'] | undefined, +> = ReturnType< + typeof simulateContractQueryKey +> diff --git a/packages/core/src/query/switchAccount.test.ts b/packages/core/src/query/switchAccount.test.ts new file mode 100644 index 0000000000..25a7066c61 --- /dev/null +++ b/packages/core/src/query/switchAccount.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { switchAccountMutationOptions } from './switchAccount.js' + +test('default', () => { + expect(switchAccountMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "switchAccount", + ], + } + `) +}) diff --git a/packages/core/src/query/switchAccount.ts b/packages/core/src/query/switchAccount.ts new file mode 100644 index 0000000000..29a10b2ce8 --- /dev/null +++ b/packages/core/src/query/switchAccount.ts @@ -0,0 +1,52 @@ +import type { MutationOptions } from '@tanstack/query-core' + +import { + type SwitchAccountErrorType, + type SwitchAccountParameters, + type SwitchAccountReturnType, + switchAccount, +} from '../actions/switchAccount.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' +import type { Mutate, MutateAsync } from './types.js' + +export function switchAccountMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return switchAccount(config, variables) + }, + mutationKey: ['switchAccount'], + } as const satisfies MutationOptions< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables + > +} + +export type SwitchAccountData = Compute< + SwitchAccountReturnType +> + +export type SwitchAccountVariables = Compute + +export type SwitchAccountMutate< + config extends Config, + context = unknown, +> = Mutate< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context +> + +export type SwitchAccountMutateAsync< + config extends Config, + context = unknown, +> = MutateAsync< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context +> diff --git a/packages/core/src/query/switchChain.test.ts b/packages/core/src/query/switchChain.test.ts new file mode 100644 index 0000000000..09a4277c20 --- /dev/null +++ b/packages/core/src/query/switchChain.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { switchChainMutationOptions } from './switchChain.js' + +test('default', () => { + expect(switchChainMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "switchChain", + ], + } + `) +}) diff --git a/packages/core/src/query/switchChain.ts b/packages/core/src/query/switchChain.ts new file mode 100644 index 0000000000..1924ca8429 --- /dev/null +++ b/packages/core/src/query/switchChain.ts @@ -0,0 +1,67 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' + +import { + type SwitchChainErrorType, + type SwitchChainParameters, + type SwitchChainReturnType, + switchChain, +} from '../actions/switchChain.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function switchChainMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return switchChain(config, variables) + }, + mutationKey: ['switchChain'], + } as const satisfies MutationOptions< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables + > +} + +export type SwitchChainData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute> + +export type SwitchChainVariables< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute> + +export type SwitchChainMutate = < + chainId extends config['chains'][number]['id'], +>( + variables: SwitchChainVariables, + options?: + | Compute< + MutateOptions< + SwitchChainData, + SwitchChainErrorType, + Compute>, + context + > + > + | undefined, +) => void + +export type SwitchChainMutateAsync = < + chainId extends config['chains'][number]['id'], +>( + variables: SwitchChainVariables, + options?: + | Compute< + MutateOptions< + SwitchChainData, + SwitchChainErrorType, + Compute>, + context + > + > + | undefined, +) => Promise> diff --git a/packages/core/src/query/types.ts b/packages/core/src/query/types.ts new file mode 100644 index 0000000000..6a279d52d5 --- /dev/null +++ b/packages/core/src/query/types.ts @@ -0,0 +1,84 @@ +import type { + DefaultError, + InfiniteQueryObserverOptions, + MutateOptions, + QueryFunction, + QueryKey, +} from '@tanstack/query-core' + +import type { Compute, StrictOmit } from '../types/utils.js' + +export type InfiniteQueryOptions< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryData = queryFnData, + queryKey extends QueryKey = QueryKey, + pageParam = unknown, + /// + options extends InfiniteQueryObserverOptions< + queryFnData, + error, + data, + queryData, + queryKey, + pageParam + > = InfiniteQueryObserverOptions< + queryFnData, + error, + data, + queryData, + queryKey, + pageParam + >, +> = Compute< + // `queryFn` doesn't pass through `pageParam` correctly + StrictOmit & { + queryFn?( + context: QueryFunctionContext, + ): options['queryFn'] extends (...args: any) => any + ? ReturnType> + : unknown + } +> + +// `QueryFunctionContext` not exported resulting in TS2742 error so grabbing from `QueryFunction` +type QueryFunctionContext< + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, +> = Parameters>[0] + +export type Mutate< + data = unknown, + error = unknown, + variables = void, + context = unknown, +> = ( + ...args: Parameters, context>> +) => void + +export type MutateAsync< + data = unknown, + error = unknown, + variables = void, + context = unknown, +> = MutateFn, context> + +type MutateFn< + data = unknown, + error = unknown, + variables = void, + context = unknown, +> = undefined extends variables + ? ( + variables?: variables, + options?: + | Compute> + | undefined, + ) => Promise + : ( + variables: variables, + options?: + | Compute> + | undefined, + ) => Promise diff --git a/packages/core/src/query/utils.test.ts b/packages/core/src/query/utils.test.ts new file mode 100644 index 0000000000..920b2e5f37 --- /dev/null +++ b/packages/core/src/query/utils.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from 'vitest' + +import { structuralSharing } from './utils.js' + +test('structuralSharing', () => { + expect( + structuralSharing({ foo: 'bar' }, { foo: 'bar' }), + ).toMatchInlineSnapshot(` + { + "foo": "bar", + } + `) + expect( + structuralSharing({ foo: 'bar' }, { foo: 'baz' }), + ).toMatchInlineSnapshot(` + { + "foo": "baz", + } + `) +}) diff --git a/packages/core/src/query/utils.ts b/packages/core/src/query/utils.ts new file mode 100644 index 0000000000..ab4ea3b989 --- /dev/null +++ b/packages/core/src/query/utils.ts @@ -0,0 +1,73 @@ +import { type QueryKey, replaceEqualDeep } from '@tanstack/query-core' + +export function structuralSharing( + oldData: data | undefined, + newData: data, +): data { + return replaceEqualDeep(oldData, newData) +} + +export function hashFn(queryKey: QueryKey): string { + return JSON.stringify(queryKey, (_, value) => { + if (isPlainObject(value)) + return Object.keys(value) + .sort() + .reduce((result, key) => { + result[key] = value[key] + return result + }, {} as any) + if (typeof value === 'bigint') return value.toString() + return value + }) +} + +// biome-ignore lint/complexity/noBannedTypes: +function isPlainObject(value: any): value is Object { + if (!hasObjectPrototype(value)) { + return false + } + + // If has modified constructor + const ctor = value.constructor + if (typeof ctor === 'undefined') return true + + // If has modified prototype + const prot = ctor.prototype + if (!hasObjectPrototype(prot)) return false + + // If constructor does not have an Object-specific method + // biome-ignore lint/suspicious/noPrototypeBuiltins: + if (!prot.hasOwnProperty('isPrototypeOf')) return false + + // Most likely a plain Object + return true +} + +function hasObjectPrototype(o: any): boolean { + return Object.prototype.toString.call(o) === '[object Object]' +} + +export function filterQueryOptions>( + options: type, +): type { + // destructuring is super fast + // biome-ignore format: no formatting + const { + // import('@tanstack/query-core').QueryOptions + _defaulted, behavior, gcTime, initialData, initialDataUpdatedAt, maxPages, meta, networkMode, queryFn, queryHash, queryKey, queryKeyHashFn, retry, retryDelay, structuralSharing, + + // import('@tanstack/query-core').InfiniteQueryObserverOptions + getPreviousPageParam, getNextPageParam, initialPageParam, + + // import('@tanstack/react-query').UseQueryOptions + _optimisticResults, enabled, notifyOnChangeProps, placeholderData, refetchInterval, refetchIntervalInBackground, refetchOnMount, refetchOnReconnect, refetchOnWindowFocus, retryOnMount, select, staleTime, suspense, throwOnError, + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // wagmi + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + config, connector, query, + ...rest + } = options + + return rest as type +} diff --git a/packages/core/src/query/verifyMessage.test.ts b/packages/core/src/query/verifyMessage.test.ts new file mode 100644 index 0000000000..ae43f6b07f --- /dev/null +++ b/packages/core/src/query/verifyMessage.test.ts @@ -0,0 +1,104 @@ +import { accounts, chain, config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { verifyMessageQueryOptions } from './verifyMessage.js' + +const address = accounts[0] + +test('default', () => { + expect( + verifyMessageQueryOptions(config, { + address, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "message": "This is a test message for viem!", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + verifyMessageQueryOptions(config, { + chainId: chain.mainnet2.id, + address, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "message": "This is a test message for viem!", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + verifyMessageQueryOptions(config, { + blockNumber: 1234567890n, + address, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 1234567890n, + "message": "This is a test message for viem!", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + verifyMessageQueryOptions(config, { + blockTag: 'safe', + address, + message: 'This is a test message for viem!', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "safe", + "message": "This is a test message for viem!", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + }, + ], + } + `) +}) diff --git a/packages/core/src/query/verifyMessage.ts b/packages/core/src/query/verifyMessage.ts new file mode 100644 index 0000000000..d827e64b2b --- /dev/null +++ b/packages/core/src/query/verifyMessage.ts @@ -0,0 +1,56 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type VerifyMessageErrorType, + type VerifyMessageParameters, + type VerifyMessageReturnType, + verifyMessage, +} from '../actions/verifyMessage.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type VerifyMessageOptions = Compute< + ExactPartial> & ScopeKeyParameter +> + +export function verifyMessageQueryOptions( + config: config, + options: VerifyMessageOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { address, message, signature } = queryKey[1] + if (!address || !message || !signature) + throw new Error('address, message, and signature are required') + + const { scopeKey: _, ...parameters } = queryKey[1] + + const verified = await verifyMessage( + config, + parameters as VerifyMessageParameters, + ) + return verified ?? null + }, + queryKey: verifyMessageQueryKey(options), + } as const satisfies QueryOptions< + VerifyMessageQueryFnData, + VerifyMessageErrorType, + VerifyMessageData, + VerifyMessageQueryKey + > +} +export type VerifyMessageQueryFnData = VerifyMessageReturnType + +export type VerifyMessageData = VerifyMessageQueryFnData + +export function verifyMessageQueryKey( + options: VerifyMessageOptions, +) { + return ['verifyMessage', filterQueryOptions(options)] as const +} + +export type VerifyMessageQueryKey = ReturnType< + typeof verifyMessageQueryKey +> diff --git a/packages/core/src/query/verifyTypedData.test.ts b/packages/core/src/query/verifyTypedData.test.ts new file mode 100644 index 0000000000..f4145099ce --- /dev/null +++ b/packages/core/src/query/verifyTypedData.test.ts @@ -0,0 +1,284 @@ +import { accounts, chain, config, typedData } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { verifyTypedDataQueryOptions } from './verifyTypedData.js' + +const address = accounts[0] + +test('default', () => { + expect( + verifyTypedDataQueryOptions(config, { + address, + ...typedData.basic, + primaryType: 'Mail', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyTypedData", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + } + `) +}) + +test('parameters: chainId', () => { + expect( + verifyTypedDataQueryOptions(config, { + ...typedData.basic, + domain: { + ...typedData.basic.domain, + chainId: chain.mainnet2.id, + }, + chainId: chain.mainnet2.id, + address, + primaryType: 'Mail', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyTypedData", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "domain": { + "chainId": 456, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + } + `) +}) + +test('parameters: blockNumber', () => { + expect( + verifyTypedDataQueryOptions(config, { + blockNumber: 1234567890n, + address, + ...typedData.basic, + primaryType: 'Mail', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyTypedData", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 1234567890n, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + } + `) +}) + +test('parameters: blockTag', () => { + expect( + verifyTypedDataQueryOptions(config, { + blockTag: 'pending', + address, + ...typedData.basic, + primaryType: 'Mail', + signature: + '0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "verifyTypedData", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "pending", + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0xefd5fb29a274ea6682673d8b3caa9263e936d48d486e5df68893003e0a76496439594d12245008c6fba1c8e3ef28241cffe1bef27ff6bca487b167f261f329251c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + } + `) +}) diff --git a/packages/core/src/query/verifyTypedData.ts b/packages/core/src/query/verifyTypedData.ts new file mode 100644 index 0000000000..d7d3970b7e --- /dev/null +++ b/packages/core/src/query/verifyTypedData.ts @@ -0,0 +1,82 @@ +import type { QueryOptions } from '@tanstack/query-core' +import type { TypedData } from 'viem' + +import { + type VerifyTypedDataErrorType, + type VerifyTypedDataParameters, + type VerifyTypedDataReturnType, + verifyTypedData, +} from '../actions/verifyTypedData.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type VerifyTypedDataOptions< + typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', + config extends Config, +> = ExactPartial> & + ScopeKeyParameter + +export function verifyTypedDataQueryOptions< + config extends Config, + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + config: config, + options: VerifyTypedDataOptions = {} as any, +) { + return { + async queryFn({ queryKey }) { + const { + address, + message, + primaryType, + signature, + types, + scopeKey: _, + ...parameters + } = queryKey[1] + if (!address) throw new Error('address is required') + if (!message) throw new Error('message is required') + if (!primaryType) throw new Error('primaryType is required') + if (!signature) throw new Error('signature is required') + if (!types) throw new Error('types is required') + + const verified = await verifyTypedData(config, { + ...parameters, + address, + message, + primaryType, + signature, + types, + } as VerifyTypedDataParameters) + return verified ?? null + }, + queryKey: verifyTypedDataQueryKey(options), + } as const satisfies QueryOptions< + VerifyTypedDataQueryFnData, + VerifyTypedDataErrorType, + VerifyTypedDataData, + VerifyTypedDataQueryKey + > +} + +export type VerifyTypedDataQueryFnData = VerifyTypedDataReturnType + +export type VerifyTypedDataData = VerifyTypedDataQueryFnData + +export function verifyTypedDataQueryKey< + config extends Config, + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>(options: VerifyTypedDataOptions) { + return ['verifyTypedData', filterQueryOptions(options)] as const +} + +export type VerifyTypedDataQueryKey< + typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', + config extends Config, +> = ReturnType> diff --git a/packages/core/src/query/waitForCallsStatus.test.ts b/packages/core/src/query/waitForCallsStatus.test.ts new file mode 100644 index 0000000000..215ec72759 --- /dev/null +++ b/packages/core/src/query/waitForCallsStatus.test.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { waitForCallsStatusQueryOptions } from './waitForCallsStatus.js' + +test('default', () => { + expect( + waitForCallsStatusQueryOptions(config, { + id: '0x0000000000000000000000000000000000000000', + }), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "callsStatus", + { + "id": "0x0000000000000000000000000000000000000000", + }, + ], + "retry": [Function], + } + `) +}) diff --git a/packages/core/src/query/waitForCallsStatus.ts b/packages/core/src/query/waitForCallsStatus.ts new file mode 100644 index 0000000000..197c279e93 --- /dev/null +++ b/packages/core/src/query/waitForCallsStatus.ts @@ -0,0 +1,53 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type WaitForCallsStatusErrorType, + type WaitForCallsStatusParameters, + type WaitForCallsStatusReturnType, + waitForCallsStatus, +} from '../actions/waitForCallsStatus.js' +import type { Config } from '../createConfig.js' +import { ConnectorNotConnectedError } from '../errors/config.js' +import { filterQueryOptions } from '../query/utils.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, PartialBy } from '../types/utils.js' + +export type WaitForCallsStatusOptions = Compute< + PartialBy & ScopeKeyParameter +> + +export function waitForCallsStatusQueryOptions( + config: config, + options: WaitForCallsStatusOptions, +) { + return { + async queryFn({ queryKey }) { + const { scopeKey: _, id, ...parameters } = queryKey[1] + if (!id) throw new Error('id is required') + const status = await waitForCallsStatus(config, { ...parameters, id }) + return status + }, + queryKey: waitForCallsStatusQueryKey(options), + retry(failureCount, error) { + if (error instanceof ConnectorNotConnectedError) return false + return failureCount < 3 + }, + } as const satisfies QueryOptions< + WaitForCallsStatusQueryFnData, + WaitForCallsStatusErrorType, + WaitForCallsStatusData, + WaitForCallsStatusQueryKey + > +} + +export type WaitForCallsStatusQueryFnData = WaitForCallsStatusReturnType + +export type WaitForCallsStatusData = WaitForCallsStatusQueryFnData + +export function waitForCallsStatusQueryKey(options: WaitForCallsStatusOptions) { + return ['callsStatus', filterQueryOptions(options)] as const +} + +export type WaitForCallsStatusQueryKey = ReturnType< + typeof waitForCallsStatusQueryKey +> diff --git a/packages/core/src/query/waitForTransactionReceipt.test.ts b/packages/core/src/query/waitForTransactionReceipt.test.ts new file mode 100644 index 0000000000..1877e1bfeb --- /dev/null +++ b/packages/core/src/query/waitForTransactionReceipt.test.ts @@ -0,0 +1,18 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { waitForTransactionReceiptQueryOptions } from './waitForTransactionReceipt.js' + +test('default', () => { + expect( + waitForTransactionReceiptQueryOptions(config, {}), + ).toMatchInlineSnapshot(` + { + "queryFn": [Function], + "queryKey": [ + "waitForTransactionReceipt", + {}, + ], + } + `) +}) diff --git a/packages/core/src/query/waitForTransactionReceipt.ts b/packages/core/src/query/waitForTransactionReceipt.ts new file mode 100644 index 0000000000..3507dabeb8 --- /dev/null +++ b/packages/core/src/query/waitForTransactionReceipt.ts @@ -0,0 +1,71 @@ +import type { QueryOptions } from '@tanstack/query-core' + +import { + type WaitForTransactionReceiptErrorType, + type WaitForTransactionReceiptParameters, + type WaitForTransactionReceiptReturnType, + waitForTransactionReceipt, +} from '../actions/waitForTransactionReceipt.js' +import type { Config } from '../createConfig.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { Compute, ExactPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' + +export type WaitForTransactionReceiptOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +> = Compute< + ExactPartial> & + ScopeKeyParameter +> + +export function waitForTransactionReceiptQueryOptions< + config extends Config, + chainId extends config['chains'][number]['id'], +>( + config: config, + options: WaitForTransactionReceiptOptions = {}, +) { + return { + async queryFn({ queryKey }) { + const { hash, ...parameters } = queryKey[1] + if (!hash) throw new Error('hash is required') + return waitForTransactionReceipt(config, { + ...parameters, + onReplaced: options.onReplaced, + hash, + }) as unknown as Promise< + WaitForTransactionReceiptReturnType + > + }, + queryKey: waitForTransactionReceiptQueryKey(options), + } as const satisfies QueryOptions< + WaitForTransactionReceiptQueryFnData, + WaitForTransactionReceiptErrorType, + WaitForTransactionReceiptData, + WaitForTransactionReceiptQueryKey + > +} + +export type WaitForTransactionReceiptQueryFnData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = WaitForTransactionReceiptReturnType + +export type WaitForTransactionReceiptData< + config extends Config, + chainId extends config['chains'][number]['id'], +> = WaitForTransactionReceiptQueryFnData + +export function waitForTransactionReceiptQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +>(options: WaitForTransactionReceiptOptions = {}) { + const { onReplaced: _, ...rest } = options + return ['waitForTransactionReceipt', filterQueryOptions(rest)] as const +} + +export type WaitForTransactionReceiptQueryKey< + config extends Config, + chainId extends config['chains'][number]['id'], +> = ReturnType> diff --git a/packages/core/src/query/watchAsset.test.ts b/packages/core/src/query/watchAsset.test.ts new file mode 100644 index 0000000000..b580beadba --- /dev/null +++ b/packages/core/src/query/watchAsset.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { watchAssetMutationOptions } from './watchAsset.js' + +test('default', () => { + expect(watchAssetMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "watchAsset", + ], + } + `) +}) diff --git a/packages/core/src/query/watchAsset.ts b/packages/core/src/query/watchAsset.ts new file mode 100644 index 0000000000..52ab82be60 --- /dev/null +++ b/packages/core/src/query/watchAsset.ts @@ -0,0 +1,42 @@ +import type { MutationOptions } from '@tanstack/query-core' + +import { + type WatchAssetErrorType, + type WatchAssetParameters, + type WatchAssetReturnType, + watchAsset, +} from '../actions/watchAsset.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' +import type { Mutate, MutateAsync } from './types.js' + +export function watchAssetMutationOptions(config: Config) { + return { + mutationFn(variables) { + return watchAsset(config, variables) + }, + mutationKey: ['watchAsset'], + } as const satisfies MutationOptions< + WatchAssetData, + WatchAssetErrorType, + WatchAssetVariables + > +} + +export type WatchAssetData = WatchAssetReturnType + +export type WatchAssetVariables = Compute + +export type WatchAssetMutate = Mutate< + WatchAssetData, + WatchAssetErrorType, + WatchAssetVariables, + context +> + +export type WatchAssetMutateAsync = MutateAsync< + WatchAssetData, + WatchAssetErrorType, + WatchAssetVariables, + context +> diff --git a/packages/core/src/query/writeContract.test-d.ts b/packages/core/src/query/writeContract.test-d.ts new file mode 100644 index 0000000000..3c80a83132 --- /dev/null +++ b/packages/core/src/query/writeContract.test-d.ts @@ -0,0 +1,145 @@ +import { http } from 'viem' +import { writeContract } from 'viem/actions' +import { base } from 'viem/chains' +import { test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import type { WriteContractMutate } from './writeContract.js' + +// https://github.com/wevm/wagmi/issues/3981 +test('gh#3981', () => { + const config = createConfig({ + chains: [base], + transports: { + [base.id]: http(), + }, + }) + + const abi = [ + { + type: 'function', + name: 'example1', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'example2', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ] as const + + const write: WriteContractMutate = () => {} + write({ + abi, + address: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) + write({ + abi: [ + { + type: 'function', + name: 'example1', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'example2', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ] as const, + address: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) + + write({ + abi, + address: '0x...', + functionName: 'example2', + args: ['0x...'], + // @ts-expect-error + value: 123n, + }) + write({ + abi: [ + { + type: 'function', + name: 'example1', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'example2', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ], + address: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) + + const client = config.getClient({ chainId: base.id }) + writeContract(client, { + abi, + address: '0x...', + account: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) + writeContract(client, { + abi: [ + { + type: 'function', + name: 'example1', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'example2', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ] as const, + address: '0x...', + account: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) +}) diff --git a/packages/core/src/query/writeContract.test.ts b/packages/core/src/query/writeContract.test.ts new file mode 100644 index 0000000000..04cde53c76 --- /dev/null +++ b/packages/core/src/query/writeContract.test.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expect, test } from 'vitest' + +import { writeContractMutationOptions } from './writeContract.js' + +test('default', () => { + expect(writeContractMutationOptions(config)).toMatchInlineSnapshot(` + { + "mutationFn": [Function], + "mutationKey": [ + "writeContract", + ], + } + `) +}) diff --git a/packages/core/src/query/writeContract.ts b/packages/core/src/query/writeContract.ts new file mode 100644 index 0000000000..0e368fe40b --- /dev/null +++ b/packages/core/src/query/writeContract.ts @@ -0,0 +1,116 @@ +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' + +import { + type WriteContractErrorType, + type WriteContractParameters, + type WriteContractReturnType, + writeContract, +} from '../actions/writeContract.js' +import type { Config } from '../createConfig.js' +import type { Compute } from '../types/utils.js' + +export function writeContractMutationOptions( + config: config, +) { + return { + mutationFn(variables) { + return writeContract(config, variables) + }, + mutationKey: ['writeContract'], + } as const satisfies MutationOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + > + > +} + +export type WriteContractData = Compute + +export type WriteContractVariables< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config, + chainId extends config['chains'][number]['id'], + /// + allFunctionNames = ContractFunctionName, +> = WriteContractParameters< + abi, + functionName, + args, + config, + chainId, + allFunctionNames +> + +export type WriteContractMutate = < + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'], +>( + variables: WriteContractVariables, + options?: + | MutateOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + abi, + functionName, + args, + config, + chainId, + // use `functionName` to make sure it's not union of all possible function names + functionName + >, + context + > + | undefined, +) => void + +export type WriteContractMutateAsync< + config extends Config, + context = unknown, +> = < + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + chainId extends config['chains'][number]['id'], +>( + variables: WriteContractVariables, + options?: + | MutateOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + abi, + functionName, + args, + config, + chainId, + // use `functionName` to make sure it's not union of all possible function names + functionName + >, + context + > + | undefined, +) => Promise diff --git a/packages/core/src/transports/connector.test.ts b/packages/core/src/transports/connector.test.ts new file mode 100644 index 0000000000..4b71c2b46b --- /dev/null +++ b/packages/core/src/transports/connector.test.ts @@ -0,0 +1,97 @@ +import { config } from '@wagmi/test' +import { optimism } from 'viem/chains' +import { expect, test } from 'vitest' +import { createStore } from 'zustand' + +import { injected } from '../connectors/injected.js' +import { mock } from '../connectors/mock.js' +import { unstable_connector } from './connector.js' + +const connector = config.connectors[0]! + +test('setup', () => { + expect(unstable_connector(injected)({})).toMatchInlineSnapshot(` + { + "config": { + "key": "connector", + "methods": undefined, + "name": "Connector", + "request": [Function], + "retryCount": 3, + "retryDelay": 150, + "timeout": undefined, + "type": "connector", + }, + "request": [Function], + "value": undefined, + } + `) +}) + +test('behavior: connector type not found', () => { + const transport = unstable_connector({ type: 'foo' })({}) + expect(() => + transport.request({ method: 'eth_chainId' }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ProviderDisconnectedError: The Provider is disconnected from all chains. + + Details: Could not find connector of type "foo" in \`connectors\` passed to \`createConfig\`. + Version: viem@2.29.2] + `) +}) + +test('behavior: provider is disconnected', () => { + const transport = unstable_connector(mock)({ + connectors: createStore(() => [ + { + ...connector, + async getProvider() { + return undefined + }, + }, + ]), + }) + + expect(() => + transport.request({ method: 'eth_chainId' }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ProviderDisconnectedError: The Provider is disconnected from all chains. + + Details: Provider is disconnected. + Version: viem@2.29.2] + `) +}) + +test('behavior: chainId mismatch', () => { + const transport = unstable_connector(mock)({ + chain: optimism, + connectors: createStore(() => [ + { + ...connector, + async getProvider(options = {}) { + if (options.chainId === optimism.id) return connector.getProvider() + return undefined + }, + }, + ]), + }) + + expect(() => + transport.request({ method: 'eth_chainId' }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [ChainDisconnectedError: The Provider is not connected to the requested chain. + + Details: The current chain of the connector (id: 1) does not match the target chain for the request (id: 10 – OP Mainnet). + Version: viem@2.29.2] + `) +}) + +test('behavior: request', () => { + const transport = unstable_connector(mock)({ + connectors: createStore(() => [connector]), + }) + + expect( + transport.request({ method: 'eth_chainId' }), + ).resolves.toThrowErrorMatchingInlineSnapshot(`"0x1"`) +}) diff --git a/packages/core/src/transports/connector.ts b/packages/core/src/transports/connector.ts new file mode 100644 index 0000000000..0f03d43188 --- /dev/null +++ b/packages/core/src/transports/connector.ts @@ -0,0 +1,87 @@ +import { + ChainDisconnectedError, + type EIP1193Parameters, + type EIP1193Provider, + type EIP1193RequestFn, + ProviderDisconnectedError, + type TransportConfig, + type WalletRpcSchema, + createTransport, + hexToNumber, + withRetry, + withTimeout, +} from 'viem' + +import type { Connector, Transport } from '../createConfig.js' + +export type ConnectorTransportConfig = { + /** The key of the transport. */ + key?: TransportConfig['key'] | undefined + /** The name of the transport. */ + name?: TransportConfig['name'] | undefined + /** The max number of times to retry. */ + retryCount?: TransportConfig['retryCount'] | undefined + /** The base delay (in ms) between retries. */ + retryDelay?: TransportConfig['retryDelay'] | undefined +} + +export type ConnectorTransport = Transport + +export function unstable_connector( + connector: Pick, + config: ConnectorTransportConfig = {}, +): Transport<'connector'> { + const { type } = connector + const { key = 'connector', name = 'Connector', retryDelay } = config + + return (parameters) => { + const { chain, connectors } = parameters + const retryCount = config.retryCount ?? parameters.retryCount + + const request: EIP1193RequestFn = async ({ method, params }) => { + const connector = connectors?.getState().find((c) => c.type === type) + if (!connector) + throw new ProviderDisconnectedError( + new Error( + `Could not find connector of type "${type}" in \`connectors\` passed to \`createConfig\`.`, + ), + ) + + const provider = (await connector.getProvider({ + chainId: chain?.id, + })) as EIP1193Provider | undefined + if (!provider) + throw new ProviderDisconnectedError( + new Error('Provider is disconnected.'), + ) + + // We are applying a retry & timeout strategy here as some injected wallets (e.g. MetaMask) fail to + // immediately resolve a JSON-RPC request on page load. + const chainId = hexToNumber( + await withRetry(() => + withTimeout(() => provider.request({ method: 'eth_chainId' }), { + timeout: 100, + }), + ), + ) + if (chain && chainId !== chain.id) + throw new ChainDisconnectedError( + new Error( + `The current chain of the connector (id: ${chainId}) does not match the target chain for the request (id: ${chain.id} – ${chain.name}).`, + ), + ) + + const body = { method, params } as EIP1193Parameters + return provider.request(body) + } + + return createTransport({ + key, + name, + request, + retryCount, + retryDelay, + type: 'connector', + }) + } +} diff --git a/packages/core/src/transports/fallback.test.ts b/packages/core/src/transports/fallback.test.ts new file mode 100644 index 0000000000..dcae23cb79 --- /dev/null +++ b/packages/core/src/transports/fallback.test.ts @@ -0,0 +1,63 @@ +import { http } from 'viem' +import { expect, test } from 'vitest' +import { unstable_connector } from './connector.js' +import { fallback } from './fallback.js' + +test('setup', () => { + expect( + fallback([ + unstable_connector({ type: 'injected' }), + http('https://example.com'), + ])({}), + ).toMatchInlineSnapshot(` + { + "config": { + "key": "fallback", + "methods": undefined, + "name": "Fallback", + "request": [Function], + "retryCount": 3, + "retryDelay": 150, + "timeout": undefined, + "type": "fallback", + }, + "request": [Function], + "value": { + "onResponse": [Function], + "transports": [ + { + "config": { + "key": "connector", + "methods": undefined, + "name": "Connector", + "request": [Function], + "retryCount": 0, + "retryDelay": 150, + "timeout": undefined, + "type": "connector", + }, + "request": [Function], + "value": undefined, + }, + { + "config": { + "key": "http", + "methods": undefined, + "name": "HTTP JSON-RPC", + "request": [Function], + "retryCount": 0, + "retryDelay": 150, + "timeout": 10000, + "type": "http", + }, + "request": [Function], + "value": { + "fetchOptions": undefined, + "url": "https://example.com", + }, + }, + ], + }, + } + `) +}) diff --git a/packages/core/src/transports/fallback.ts b/packages/core/src/transports/fallback.ts new file mode 100644 index 0000000000..67069f5de1 --- /dev/null +++ b/packages/core/src/transports/fallback.ts @@ -0,0 +1,10 @@ +import { fallback as viem_fallback } from 'viem' + +import type { Transport } from '../createConfig.js' + +export function fallback( + transports: Transport[], + config?: Parameters[1], +) { + return viem_fallback(transports, config) +} diff --git a/packages/core/src/types/chain.test-d.ts b/packages/core/src/types/chain.test-d.ts new file mode 100644 index 0000000000..65632709e1 --- /dev/null +++ b/packages/core/src/types/chain.test-d.ts @@ -0,0 +1,33 @@ +import type { Chain, mainnet, optimism, sepolia } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import type { Config } from '../createConfig.js' +import type { SelectChains } from './chain.js' +import type { Merge } from './utils.js' + +test('not narrowable', () => { + type Result = SelectChains + expectTypeOf().toEqualTypeOf() +}) + +test('chainId', () => { + type Result = SelectChains< + Config, + 1 + > + expectTypeOf().toEqualTypeOf() +}) + +test('at least one chain has formatters', () => { + type Result = SelectChains> + expectTypeOf().toEqualTypeOf< + readonly [typeof mainnet, typeof optimism] + >() +}) + +test('no formatters', () => { + type Result = SelectChains> + expectTypeOf().toEqualTypeOf< + readonly [Merge] + >() +}) diff --git a/packages/core/src/types/chain.ts b/packages/core/src/types/chain.ts new file mode 100644 index 0000000000..5f81783171 --- /dev/null +++ b/packages/core/src/types/chain.ts @@ -0,0 +1,26 @@ +import type { Chain, ChainFormatters } from 'viem' + +import type { Config } from '../createConfig.js' +import type { IsNarrowable, Merge } from './utils.js' + +/** Filters {@link Config} chains by {@link chainId} or simplifies if no `ChainFormatters` are present. */ +export type SelectChains< + config extends Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, +> = Config extends config + ? readonly [Chain] // chains not inferrable, return default + : IsNarrowable extends true + ? readonly [Extract] // select specific chain + : HasFormatter extends true + ? config['chains'] // return all chains since one has formatter + : // return default chain with ID set to union (allows for more simple type since the only thing that is different is the chain ID for each chain) + readonly [Merge] + +type HasFormatter = chains extends readonly [ + infer head extends Chain, + ...infer tail extends readonly Chain[], +] + ? IsNarrowable extends true + ? true + : HasFormatter + : false diff --git a/packages/core/src/types/properties.ts b/packages/core/src/types/properties.ts new file mode 100644 index 0000000000..760cc46bd2 --- /dev/null +++ b/packages/core/src/types/properties.ts @@ -0,0 +1,23 @@ +import type { Config, Connector } from '../createConfig.js' + +export type ChainIdParameter< + config extends Config, + chainId extends + | config['chains'][number]['id'] + | undefined = config['chains'][number]['id'], +> = { + chainId?: + | (chainId extends config['chains'][number]['id'] ? chainId : undefined) + | config['chains'][number]['id'] + | undefined +} + +export type ConnectorParameter = { + connector?: Connector | undefined +} + +export type ScopeKeyParameter = { scopeKey?: string | undefined } + +export type SyncConnectedChainParameter = { + syncConnectedChain?: boolean | undefined +} diff --git a/packages/core/src/types/register.ts b/packages/core/src/types/register.ts new file mode 100644 index 0000000000..db420adfdc --- /dev/null +++ b/packages/core/src/types/register.ts @@ -0,0 +1,9 @@ +import type { Config } from '../createConfig.js' + +// biome-ignore lint/suspicious/noEmptyInterface: +export interface Register {} +export type ResolvedRegister = { + config: Register extends { config: infer config extends Config } + ? config + : Config +} diff --git a/packages/core/src/types/unit.ts b/packages/core/src/types/unit.ts new file mode 100644 index 0000000000..a64f794a87 --- /dev/null +++ b/packages/core/src/types/unit.ts @@ -0,0 +1 @@ +export type Unit = 'ether' | 'gwei' | 'wei' | number diff --git a/packages/core/src/types/utils.test-d.ts b/packages/core/src/types/utils.test-d.ts new file mode 100644 index 0000000000..523f9f2e36 --- /dev/null +++ b/packages/core/src/types/utils.test-d.ts @@ -0,0 +1,40 @@ +import { assertType, expectTypeOf, test } from 'vitest' + +import type { + Compute, + ExactPartial, + IsNever, + Mutable, + OneOf, + PartialBy, +} from './utils.js' + +test('ExactPartial', () => { + expectTypeOf>().toEqualTypeOf<{ + foo?: boolean | undefined + bar?: boolean | undefined + }>() +}) + +test('IsNever', () => { + expectTypeOf>().toEqualTypeOf() +}) + +test('Mutable', () => { + expectTypeOf< + Mutable<{ foo: boolean; readonly bar: boolean }> + >().toEqualTypeOf<{ foo: boolean; bar: boolean }>() +}) + +test('OneOf', () => { + assertType>({ foo: false }) + assertType>({ bar: false }) +}) + +test('PartialBy', () => { + type Result = Compute> + expectTypeOf().toEqualTypeOf<{ + foo?: string | undefined + bar: number + }>() +}) diff --git a/packages/core/src/types/utils.ts b/packages/core/src/types/utils.ts new file mode 100644 index 0000000000..aecd58a142 --- /dev/null +++ b/packages/core/src/types/utils.ts @@ -0,0 +1,101 @@ +/** Combines members of an intersection into a readable type. */ +// https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg +export type Compute = { [key in keyof type]: type[key] } & unknown + +/** + * Makes all properties of an object optional. + * + * Compatible with [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes). + */ +export type ExactPartial = { + [key in keyof type]?: type[key] | undefined +} + +/** Checks if {@link type} can be narrowed further than {@link type2} */ +export type IsNarrowable = IsUnknown extends true + ? false + : undefined extends type + ? false + : IsNever< + (type extends type2 ? true : false) & + (type2 extends type ? false : true) + > extends true + ? false + : true + +/** + * @internal + * Checks if {@link type} is `never` + */ +export type IsNever = [type] extends [never] ? true : false + +/** + * @internal + * Checks if {@link type} is `unknown` + */ +export type IsUnknown = unknown extends type ? true : false + +/** Merges two object types into new type */ +export type Merge = Compute< + LooseOmit & + obj2 +> + +/** Removes `readonly` from all properties of an object. */ +export type Mutable = { + -readonly [key in keyof type]: type[key] +} + +/** Strict version of built-in Omit type */ +export type StrictOmit = Pick< + type, + Exclude +> + +/** Makes objects destructurable. */ +export type OneOf< + union extends object, + /// + keys extends KeyofUnion = KeyofUnion, +> = union extends infer Item + ? Compute]?: undefined }> + : never +type KeyofUnion = type extends type ? keyof type : never + +/** Makes {@link key} optional in {@link type} while preserving type inference. */ +// s/o trpc (https://github.com/trpc/trpc/blob/main/packages/server/src/types.ts#L6) +export type PartialBy = ExactPartial< + Pick +> & + StrictOmit + +/* Removes `undefined` from object property */ +export type RemoveUndefined = { + [key in keyof type]: NonNullable +} + +/////////////////////////////////////////////////////////////////////////// +// Loose types + +/** Loose version of {@link StrictOmit} */ +export type LooseOmit = Pick< + type, + Exclude +> + +/////////////////////////////////////////////////////////////////////////// +// Union types + +export type UnionCompute = type extends object ? Compute : type + +export type UnionLooseOmit = type extends any + ? LooseOmit + : never + +export type UnionStrictOmit = type extends any + ? StrictOmit + : never + +export type UnionExactPartial = type extends object + ? ExactPartial + : type diff --git a/packages/core/src/utils/cookie.test.ts b/packages/core/src/utils/cookie.test.ts new file mode 100644 index 0000000000..f21f5ccee1 --- /dev/null +++ b/packages/core/src/utils/cookie.test.ts @@ -0,0 +1,69 @@ +import { http } from 'viem' +import { mainnet } from 'viem/chains' +import { expect, test } from 'vitest' + +import { createConfig } from '../createConfig.js' +import { createStorage } from '../createStorage.js' +import { cookieStorage, cookieToInitialState, parseCookie } from './cookie.js' + +test('cookieStorage', () => { + expect(cookieStorage.getItem('recentConnectorId')).toMatchInlineSnapshot( + 'null', + ) + cookieStorage.setItem('recentConnectorId', 'foo') + expect(cookieStorage.getItem('recentConnectorId')).toMatchInlineSnapshot( + `"foo"`, + ) + cookieStorage.removeItem('recentConnectorId') + expect(cookieStorage.getItem('recentConnectorId')).toMatchInlineSnapshot( + 'null', + ) +}) + +test('cookieToInitialState', () => { + const config = createConfig({ + chains: [mainnet], + transports: { [mainnet.id]: http() }, + storage: createStorage({ storage: cookieStorage }), + }) + + expect( + cookieToInitialState( + config, + 'wagmi.store={"state":{"connections":{"__type":"Map","value":[]},"chainId":1,"current":null},"version":2}; ', + ), + ).toMatchInlineSnapshot(` + { + "chainId": 1, + "connections": Map {}, + "current": null, + } + `) + + expect(cookieToInitialState(config)).toMatchInlineSnapshot('undefined') + expect(cookieToInitialState(config), 'foo').toMatchInlineSnapshot('undefined') +}) + +test('parseCookie', () => { + expect( + parseCookie( + 'wagmi.store={"state":{"connections":{"__type":"Map","value":[]},"chainId":1,"current":null},"version":2}; ', + 'wagmi.store', + ), + ).toMatchInlineSnapshot( + `"{"state":{"connections":{"__type":"Map","value":[]},"chainId":1,"current":null},"version":2}"`, + ) + + expect( + parseCookie( + 'foo="bar"; wagmi.store={"state":{"connections":{"__type":"Map","value":[]},"chainId":1,"current":null},"version":2}; ', + 'wagmi.store', + ), + ).toMatchInlineSnapshot( + `"{"state":{"connections":{"__type":"Map","value":[]},"chainId":1,"current":null},"version":2}"`, + ) + + expect(parseCookie('foo="bar"; ', 'wagmi.store')).toMatchInlineSnapshot( + 'undefined', + ) +}) diff --git a/packages/core/src/utils/cookie.ts b/packages/core/src/utils/cookie.ts new file mode 100644 index 0000000000..ccd07bc7b7 --- /dev/null +++ b/packages/core/src/utils/cookie.ts @@ -0,0 +1,33 @@ +import type { Config, State } from '../createConfig.js' +import type { BaseStorage } from '../createStorage.js' +import { deserialize } from './deserialize.js' + +export const cookieStorage = { + getItem(key) { + if (typeof window === 'undefined') return null + const value = parseCookie(document.cookie, key) + return value ?? null + }, + setItem(key, value) { + if (typeof window === 'undefined') return + document.cookie = `${key}=${value};path=/;samesite=Lax` + }, + removeItem(key) { + if (typeof window === 'undefined') return + document.cookie = `${key}=;max-age=-1;path=/` + }, +} satisfies BaseStorage + +export function cookieToInitialState(config: Config, cookie?: string | null) { + if (!cookie) return undefined + const key = `${config.storage?.key}.store` + const parsed = parseCookie(cookie, key) + if (!parsed) return undefined + return deserialize<{ state: State }>(parsed).state +} + +export function parseCookie(cookie: string, key: string) { + const keyValue = cookie.split('; ').find((x) => x.startsWith(`${key}=`)) + if (!keyValue) return undefined + return keyValue.substring(key.length + 1) +} diff --git a/packages/core/src/utils/deepEqual.test.ts b/packages/core/src/utils/deepEqual.test.ts new file mode 100644 index 0000000000..6105fe496f --- /dev/null +++ b/packages/core/src/utils/deepEqual.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'vitest' + +import { deepEqual } from './deepEqual.js' + +test('compares primitive values', () => { + expect(deepEqual(true, true)).toBe(true) + expect(deepEqual(true, false)).toBe(false) + + expect(deepEqual(1, 1)).toBe(true) + expect(deepEqual(1, 2)).toBe(false) + + expect(deepEqual('zustand', 'zustand')).toBe(true) + expect(deepEqual('zustand', 'redux')).toBe(false) +}) + +test('compares objects', () => { + expect(deepEqual({ foo: 'bar', asd: 123 }, { foo: 'bar', asd: 123 })).toBe( + true, + ) + + expect( + deepEqual({ foo: 'bar', asd: 123 }, { foo: 'bar', foobar: true }), + ).toBe(false) + + expect( + deepEqual({ foo: 'bar', asd: 123 }, { foo: 'bar', asd: 123, foobar: true }), + ).toBe(false) +}) + +test('compares arrays', () => { + expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true) + + expect(deepEqual([1, 2, 3], [2, 3, 4])).toBe(false) + + expect( + deepEqual([{ foo: 'bar' }, { asd: 123 }], [{ foo: 'bar' }, { asd: 123 }]), + ).toBe(true) + + expect(deepEqual([{ foo: 'bar' }], [{ foo: 'bar', asd: 123 }])).toBe(false) +}) diff --git a/packages/core/src/utils/deepEqual.ts b/packages/core/src/utils/deepEqual.ts new file mode 100644 index 0000000000..89b47a6bdd --- /dev/null +++ b/packages/core/src/utils/deepEqual.ts @@ -0,0 +1,43 @@ +/** Forked from https://github.com/epoberezkin/fast-deep-equal */ + +export function deepEqual(a: any, b: any) { + if (a === b) return true + + if (a && b && typeof a === 'object' && typeof b === 'object') { + if (a.constructor !== b.constructor) return false + + let length: number + let i: number + + if (Array.isArray(a) && Array.isArray(b)) { + length = a.length + if (length !== b.length) return false + for (i = length; i-- !== 0; ) if (!deepEqual(a[i], b[i])) return false + return true + } + + if (a.valueOf !== Object.prototype.valueOf) + return a.valueOf() === b.valueOf() + if (a.toString !== Object.prototype.toString) + return a.toString() === b.toString() + + const keys = Object.keys(a) + length = keys.length + if (length !== Object.keys(b).length) return false + + for (i = length; i-- !== 0; ) + if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false + + for (i = length; i-- !== 0; ) { + const key = keys[i] + + if (key && !deepEqual(a[key], b[key])) return false + } + + return true + } + + // true if both NaN, false otherwise + // biome-ignore lint/suspicious/noSelfCompare: + return a !== a && b !== b +} diff --git a/packages/core/src/utils/deserialize.test.ts b/packages/core/src/utils/deserialize.test.ts new file mode 100644 index 0000000000..11f73ff79b --- /dev/null +++ b/packages/core/src/utils/deserialize.test.ts @@ -0,0 +1,114 @@ +import { expect, test } from 'vitest' + +import { deserialize } from './deserialize.js' +import { serialize } from './serialize.js' + +test('deserializes', () => { + const deserializedCache = deserialize( + serialize({ + some: 'complex', + object: { + that: 'has', + many: [ + { many: 'many', manymany: 'many' }, + { many: 'many' }, + { many: 'many' }, + { + many: { + properties: { + ones: { + that: { + have: { + functions: () => null, + }, + }, + }, + }, + }, + }, + ], + }, + and: { + ones: { + that: { + have: { + bigints: 123456789012345678901234567890n, + }, + }, + }, + }, + also: { + ones: { + that: { + have: { + proxies: new Proxy({ lol: 'nice' }, {}), + }, + }, + }, + }, + }), + ) + expect(deserializedCache).toMatchInlineSnapshot(` + { + "also": { + "ones": { + "that": { + "have": { + "proxies": { + "lol": "nice", + }, + }, + }, + }, + }, + "and": { + "ones": { + "that": { + "have": { + "bigints": 123456789012345678901234567890n, + }, + }, + }, + }, + "object": { + "many": [ + { + "many": "many", + "manymany": "many", + }, + { + "many": "many", + }, + { + "many": "many", + }, + { + "many": { + "properties": { + "ones": { + "that": { + "have": {}, + }, + }, + }, + }, + }, + ], + "that": "has", + }, + "some": "complex", + } + `) +}) + +test('Map', () => { + const map = new Map().set('foo', { bar: 'baz' }) + const deserializedCache = deserialize(serialize({ map })) + expect(deserializedCache).toEqual({ map }) +}) + +test('bigint', () => { + const bigint = 123n + const deserializedCache = deserialize(serialize({ bigint })) + expect(deserializedCache).toEqual({ bigint }) +}) diff --git a/packages/core/src/utils/deserialize.ts b/packages/core/src/utils/deserialize.ts new file mode 100644 index 0000000000..36fe30dab8 --- /dev/null +++ b/packages/core/src/utils/deserialize.ts @@ -0,0 +1,10 @@ +type Reviver = (key: string, value: any) => any + +export function deserialize(value: string, reviver?: Reviver): type { + return JSON.parse(value, (key, value_) => { + let value = value_ + if (value?.__type === 'bigint') value = BigInt(value.value) + if (value?.__type === 'Map') value = new Map(value.value) + return reviver?.(key, value) ?? value + }) +} diff --git a/packages/core/src/utils/extractRpcUrls.test.ts b/packages/core/src/utils/extractRpcUrls.test.ts new file mode 100644 index 0000000000..6d2c21e26e --- /dev/null +++ b/packages/core/src/utils/extractRpcUrls.test.ts @@ -0,0 +1,92 @@ +import { http } from 'viem' +import { mainnet, optimism, sepolia } from 'viem/chains' +import { expect, test } from 'vitest' + +import { injected } from '../connectors/injected.js' +import { unstable_connector } from '../transports/connector.js' +import { fallback } from '../transports/fallback.js' +import { extractRpcUrls } from './extractRpcUrls.js' + +test('default', () => { + expect( + extractRpcUrls({ + chain: mainnet, + transports: { + [mainnet.id]: fallback([ + http('https://wagmi.com'), + http('https://lol.com'), + ]), + [sepolia.id]: http('https://sepoliarocks.com'), + [optimism.id]: http(), + }, + }), + ).toMatchInlineSnapshot(` + [ + "https://wagmi.com", + "https://lol.com", + ] + `) + + expect( + extractRpcUrls({ + chain: sepolia, + transports: { + [mainnet.id]: fallback([ + http('https://wagmi.com'), + http('https://lol.com'), + ]), + [sepolia.id]: http('https://sepoliarocks.com'), + [optimism.id]: http(), + }, + }), + ).toMatchInlineSnapshot(` + [ + "https://sepoliarocks.com", + ] + `) + + expect( + extractRpcUrls({ + chain: optimism, + transports: { + [mainnet.id]: fallback([ + http('https://wagmi.com'), + http('https://lol.com'), + ]), + [sepolia.id]: http('https://sepoliarocks.com'), + [optimism.id]: http(), + }, + }), + ).toMatchInlineSnapshot(` + [ + "https://mainnet.optimism.io", + ] + `) + + expect( + extractRpcUrls({ + chain: mainnet, + }), + ).toMatchInlineSnapshot(` + [ + "https://eth.merkle.io", + ] + `) + + expect( + extractRpcUrls({ + chain: mainnet, + transports: { + [mainnet.id]: fallback([ + unstable_connector(injected), + http('https://lol.com'), + ]), + }, + }), + ).toMatchInlineSnapshot(` + [ + "https://eth.merkle.io", + "https://lol.com", + ] + `) +}) diff --git a/packages/core/src/utils/extractRpcUrls.ts b/packages/core/src/utils/extractRpcUrls.ts new file mode 100644 index 0000000000..a617eac13c --- /dev/null +++ b/packages/core/src/utils/extractRpcUrls.ts @@ -0,0 +1,19 @@ +import type { Chain, Transport } from 'viem' + +type ExtractRpcUrlsParameters = { + transports?: Record | undefined + chain: Chain +} + +export function extractRpcUrls(parameters: ExtractRpcUrlsParameters) { + const { chain } = parameters + const fallbackUrl = chain.rpcUrls.default.http[0] + + if (!parameters.transports) return [fallbackUrl] + + const transport = parameters.transports?.[chain.id]?.({ chain }) + const transports = (transport?.value?.transports as NonNullable< + typeof transport + >[]) || [transport] + return transports.map(({ value }) => value?.url || fallbackUrl) +} diff --git a/packages/core/src/utils/getAction.test.ts b/packages/core/src/utils/getAction.test.ts new file mode 100644 index 0000000000..7173852947 --- /dev/null +++ b/packages/core/src/utils/getAction.test.ts @@ -0,0 +1,49 @@ +import { abi, address, config } from '@wagmi/test' +import * as viem_actions from 'viem/actions' +import { expect, test, vi } from 'vitest' + +import { getAction } from './getAction.js' + +test('uses tree-shakable action', async () => { + const client = config.getClient({ chainId: 1 }) + + const name = 'getBlockNumber' + const actions = { ...viem_actions } + const spy = vi.spyOn(actions, name) + const action = getAction(client, actions[name], name) + + await action({}) + expect(spy).toBeCalledWith(client, {}) +}) + +test('uses client action', async () => { + const client = config + .getClient({ chainId: 1 }) + .extend(() => ({ getBlockNumber: async () => 69n })) + + const name = 'getBlockNumber' + const spy = vi.spyOn(client, name) + const action = getAction(client, client[name], name) + + await action({}) + expect(spy).toBeCalledWith({}) +}) + +test('internal call', async () => { + const client = config.getClient({ chainId: 1 }).extend(() => ({ + async call() { + return { + data: '0x0000000000000000000000000000000000000000000000000000000000000045', + } + }, + })) + + await expect( + viem_actions.readContract(client, { + address: address.wagmiMintExample, + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ).resolves.toEqual(69n) +}) diff --git a/packages/core/src/utils/getAction.ts b/packages/core/src/utils/getAction.ts new file mode 100644 index 0000000000..514ceb5d50 --- /dev/null +++ b/packages/core/src/utils/getAction.ts @@ -0,0 +1,44 @@ +import type { + Account, + Chain, + Client, + PublicActions, + RpcSchema, + Transport, + WalletActions, +} from 'viem' + +/** + * Retrieves and returns an action from the client (if exists), and falls + * back to the tree-shakable action. + * + * Useful for extracting overridden actions from a client (ie. if a consumer + * wants to override the `sendTransaction` implementation). + */ +export function getAction< + transport extends Transport, + chain extends Chain | undefined, + account extends Account | undefined, + rpcSchema extends RpcSchema | undefined, + extended extends { [key: string]: unknown }, + client extends Client, + parameters, + returnType, +>( + client: client, + actionFn: (_: any, parameters: parameters) => returnType, + // Some minifiers drop `Function.prototype.name`, or replace it with short letters, + // meaning that `actionFn.name` will not always work. For that case, the consumer + // needs to pass the name explicitly. + name: keyof PublicActions | keyof WalletActions, +): (parameters: parameters) => returnType { + const action_implicit = client[actionFn.name] + if (typeof action_implicit === 'function') + return action_implicit as (params: parameters) => returnType + + const action_explicit = client[name] + if (typeof action_explicit === 'function') + return action_explicit as (params: parameters) => returnType + + return (params) => actionFn(client, params) +} diff --git a/packages/core/src/utils/getUnit.test.ts b/packages/core/src/utils/getUnit.test.ts new file mode 100644 index 0000000000..1054303def --- /dev/null +++ b/packages/core/src/utils/getUnit.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from 'vitest' + +import { getUnit } from './getUnit.js' + +test('default', () => { + expect(getUnit(1)).toMatchInlineSnapshot('1') + expect(getUnit('wei')).toMatchInlineSnapshot('0') + expect(getUnit('ether')).toMatchInlineSnapshot('18') +}) diff --git a/packages/core/src/utils/getUnit.ts b/packages/core/src/utils/getUnit.ts new file mode 100644 index 0000000000..084f45b622 --- /dev/null +++ b/packages/core/src/utils/getUnit.ts @@ -0,0 +1,9 @@ +import { weiUnits } from 'viem' + +import type { Unit } from '../types/unit.js' + +export function getUnit(unit: Unit) { + if (typeof unit === 'number') return unit + if (unit === 'wei') return 0 + return Math.abs(weiUnits[unit]) +} diff --git a/packages/core/src/utils/getVersion.test.ts b/packages/core/src/utils/getVersion.test.ts new file mode 100644 index 0000000000..29fa256178 --- /dev/null +++ b/packages/core/src/utils/getVersion.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +import { getVersion } from './getVersion.js' + +test('default', () => { + expect(getVersion()).toMatchInlineSnapshot(`"@wagmi/core@x.y.z"`) +}) diff --git a/packages/core/src/utils/getVersion.ts b/packages/core/src/utils/getVersion.ts new file mode 100644 index 0000000000..3506365267 --- /dev/null +++ b/packages/core/src/utils/getVersion.ts @@ -0,0 +1,3 @@ +import { version } from '../version.js' + +export const getVersion = () => `@wagmi/core@${version}` diff --git a/packages/core/src/utils/normalizeChainId.test.ts b/packages/core/src/utils/normalizeChainId.test.ts new file mode 100644 index 0000000000..de9d1882e3 --- /dev/null +++ b/packages/core/src/utils/normalizeChainId.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from 'vitest' + +import { normalizeChainId } from './normalizeChainId.js' + +test.each([ + { chainId: 1, expected: 1 }, + { chainId: '1', expected: 1 }, + { chainId: '0x1', expected: 1 }, + { chainId: '0x4', expected: 4 }, + { chainId: 42, expected: 42 }, + { chainId: '42', expected: 42 }, + { chainId: '0x2a', expected: 42 }, + { chainId: ' 0x2a', expected: 42 }, + { chainId: BigInt(1), expected: 1 }, + { chainId: BigInt(10), expected: 10 }, +])('normalizeChainId($chainId)', ({ chainId, expected }) => { + expect(normalizeChainId(chainId)).toEqual(expected) +}) + +test('unknown type', () => { + expect(() => normalizeChainId({})).toThrow( + 'Cannot normalize chainId "[object Object]" of type "object"', + ) +}) diff --git a/packages/core/src/utils/normalizeChainId.ts b/packages/core/src/utils/normalizeChainId.ts new file mode 100644 index 0000000000..a1017c0953 --- /dev/null +++ b/packages/core/src/utils/normalizeChainId.ts @@ -0,0 +1,13 @@ +/** @deprecated use `Number` instead */ +export function normalizeChainId(chainId: bigint | number | string | unknown) { + if (typeof chainId === 'string') + return Number.parseInt( + chainId, + chainId.trim().substring(0, 2) === '0x' ? 16 : 10, + ) + if (typeof chainId === 'bigint') return Number(chainId) + if (typeof chainId === 'number') return chainId + throw new Error( + `Cannot normalize chainId "${chainId}" of type "${typeof chainId}"`, + ) +} diff --git a/packages/core/src/utils/serialize.test.ts b/packages/core/src/utils/serialize.test.ts new file mode 100644 index 0000000000..e182101130 --- /dev/null +++ b/packages/core/src/utils/serialize.test.ts @@ -0,0 +1,241 @@ +import { describe, expect, it } from 'vitest' + +import { serialize } from './serialize.js' + +class Foo { + value: string + + constructor(value: string) { + this.value = value + } +} + +const simpleObject = { + boolean: true, + fn() { + return 'foo' + }, + nan: Number.NaN, + nil: null, + number: 123, + string: 'foo', + undef: undefined, + [Symbol('key')]: 'value', +} + +const bigintObject = Object.assign({}, simpleObject, { + bigint: 123n, + nested: { + bigint: { + value: 69n, + }, + }, +}) + +const bufferString = 'this is a test buffer' +const complexObject = Object.assign({}, simpleObject, { + array: ['foo', { bar: 'baz' }], + buffer: Buffer.alloc(bufferString.length, bufferString), + error: new Error('boom'), + foo: new Foo('value'), + map: new Map().set('foo', { bar: 'baz' }), + object: { foo: { bar: 'baz' } }, + promise: Promise.resolve('foo'), + regexp: /foo/, + set: new Set().add('foo').add({ bar: 'baz' }), + weakmap: new WeakMap([ + [{}, 'foo'], + [{}, 'bar'], + ]), + weakset: new WeakSet([{}, {}]), +}) + +const circularObject = Object.assign( + {}, + complexObject, + { + map: { __type: 'Map', value: [['foo', { bar: 'baz' }]] }, + }, + { + deeply: { + nested: { + reference: {}, + }, + }, + }, +) + +circularObject.deeply.nested.reference = circularObject + +describe('stringify', () => { + describe('handling of object types', () => { + it('should handle simple objects', () => { + const result = serialize(simpleObject) + + expect(result).toMatchInlineSnapshot( + `"{"boolean":true,"nan":null,"nil":null,"number":123,"string":"foo"}"`, + ) + }) + + it('should handle objects with bigints', () => { + const result = serialize(bigintObject) + + expect(result).toMatchInlineSnapshot( + `"{"boolean":true,"nan":null,"nil":null,"number":123,"string":"foo","bigint":{"__type":"bigint","value":"123"},"nested":{"bigint":{"value":{"__type":"bigint","value":"69"}}}}"`, + ) + }) + + it('should handle simple objects with a custom replacer', () => { + const replacer = (_key: string, value: any) => + value && typeof value === 'object' ? value : `primitive-${value}` + + const result = serialize(simpleObject, replacer) + + expect(result).toMatchInlineSnapshot( + `"{"boolean":"primitive-true","fn":"primitive-fn() {\\n return \\"foo\\";\\n }","nan":"primitive-NaN","nil":"primitive-null","number":"primitive-123","string":"primitive-foo","undef":"primitive-undefined"}"`, + ) + }) + + it('should handle simple objects with indentation', () => { + const result = serialize(simpleObject, null, 2) + + expect(result).toMatchInlineSnapshot(` + "{ + "boolean": true, + "nan": null, + "nil": null, + "number": 123, + "string": "foo" + }" + `) + }) + + it('should handle bigint objects with indentation', () => { + const result = serialize(bigintObject, null, 2) + + expect(result).toMatchInlineSnapshot(` + "{ + "boolean": true, + "nan": null, + "nil": null, + "number": 123, + "string": "foo", + "bigint": { + "__type": "bigint", + "value": "123" + }, + "nested": { + "bigint": { + "value": { + "__type": "bigint", + "value": "69" + } + } + } + }" + `) + }) + + it('should handle complex objects', () => { + const result = serialize(complexObject) + + expect(result).toMatchInlineSnapshot( + `"{"boolean":true,"nan":null,"nil":null,"number":123,"string":"foo","array":["foo",{"bar":"baz"}],"buffer":{"type":"Buffer","data":[116,104,105,115,32,105,115,32,97,32,116,101,115,116,32,98,117,102,102,101,114]},"error":{},"foo":{"value":"value"},"map":{"__type":"Map","value":[["foo",{"bar":"baz"}]]},"object":{"foo":{"bar":"baz"}},"promise":{},"regexp":{},"set":{},"weakmap":{},"weakset":{}}"`, + ) + }) + + it('should handle complex objects with a custom replacer', () => { + const replacer = (_key: string, value: any) => + value && typeof value === 'object' ? value : `primitive-${value}` + + const result = serialize(complexObject, replacer) + + expect(result).toMatchInlineSnapshot( + `"{"boolean":"primitive-true","fn":"primitive-fn() {\\n return \\"foo\\";\\n }","nan":"primitive-NaN","nil":"primitive-null","number":"primitive-123","string":"primitive-foo","undef":"primitive-undefined","array":["primitive-foo",{"bar":"primitive-baz"}],"buffer":{"type":"primitive-Buffer","data":["primitive-116","primitive-104","primitive-105","primitive-115","primitive-32","primitive-105","primitive-115","primitive-32","primitive-97","primitive-32","primitive-116","primitive-101","primitive-115","primitive-116","primitive-32","primitive-98","primitive-117","primitive-102","primitive-102","primitive-101","primitive-114"]},"error":{},"foo":{"value":"primitive-value"},"map":{"__type":"primitive-Map","value":[["primitive-foo",{"bar":"primitive-baz"}]]},"object":{"foo":{"bar":"primitive-baz"}},"promise":{},"regexp":{},"set":{},"weakmap":{},"weakset":{}}"`, + ) + }) + + it('should handle circular objects', () => { + const result = serialize(circularObject) + + expect(result).toEqual( + JSON.stringify( + circularObject, + (() => { + const cache: any[] = [] + + return (_key, value) => { + if (value && typeof value === 'object' && ~cache.indexOf(value)) { + return '[ref=.]' + } + + cache.push(value) + + return value + } + })(), + ), + ) + }) + + it('should handle circular objects with a custom circular replacer', () => { + const result = serialize( + circularObject, + null, + null, + (_key: string, _value: string, referenceKey: string) => referenceKey, + ) + const circularReplacer = (() => { + const cache: any[] = [] + + return (_key: string, value: any) => { + if (value && typeof value === 'object' && ~cache.indexOf(value)) { + return '.' + } + + cache.push(value) + + return value + } + })() + + expect(result).toEqual(JSON.stringify(circularObject, circularReplacer)) + }) + }) + + describe('key references', () => { + it('should point to the top level object when it is referenced', () => { + const object = { + foo: 'bar', + deeply: { + recursive: { + object: {}, + }, + }, + } + + object.deeply.recursive.object = object + + expect(serialize(object)).toEqual( + `{"foo":"bar","deeply":{"recursive":{"object":"[ref=.]"}}}`, + ) + }) + + it('should point to the nested object when it is referenced', () => { + const object = { + foo: 'bar', + deeply: { + recursive: { + object: {}, + }, + }, + } + + object.deeply.recursive.object = object.deeply.recursive + + expect(serialize(object)).toEqual( + `{"foo":"bar","deeply":{"recursive":{"object":"[ref=.deeply.recursive]"}}}`, + ) + }) + }) +}) diff --git a/packages/core/src/utils/serialize.ts b/packages/core/src/utils/serialize.ts new file mode 100644 index 0000000000..3f538c7b87 --- /dev/null +++ b/packages/core/src/utils/serialize.ts @@ -0,0 +1,116 @@ +/** + * Get the reference key for the circular value + * + * @param keys the keys to build the reference key from + * @param cutoff the maximum number of keys to include + * @returns the reference key + */ +function getReferenceKey(keys: string[], cutoff: number) { + return keys.slice(0, cutoff).join('.') || '.' +} + +/** + * Faster `Array.prototype.indexOf` implementation build for slicing / splicing + * + * @param array the array to match the value in + * @param value the value to match + * @returns the matching index, or -1 + */ +function getCutoff(array: any[], value: any) { + const { length } = array + + for (let index = 0; index < length; ++index) { + if (array[index] === value) { + return index + 1 + } + } + + return 0 +} + +type StandardReplacer = (key: string, value: any) => any +type CircularReplacer = (key: string, value: any, referenceKey: string) => any + +/** + * Create a replacer method that handles circular values + * + * @param [replacer] a custom replacer to use for non-circular values + * @param [circularReplacer] a custom replacer to use for circular methods + * @returns the value to stringify + */ +function createReplacer( + replacer?: StandardReplacer | null | undefined, + circularReplacer?: CircularReplacer | null | undefined, +): StandardReplacer { + const hasReplacer = typeof replacer === 'function' + const hasCircularReplacer = typeof circularReplacer === 'function' + + const cache: any[] = [] + const keys: string[] = [] + + return function replace(this: any, key: string, value: any) { + if (typeof value === 'object') { + if (cache.length) { + const thisCutoff = getCutoff(cache, this) + + if (thisCutoff === 0) { + cache[cache.length] = this + } else { + cache.splice(thisCutoff) + keys.splice(thisCutoff) + } + + keys[keys.length] = key + + const valueCutoff = getCutoff(cache, value) + + if (valueCutoff !== 0) { + return hasCircularReplacer + ? circularReplacer.call( + this, + key, + value, + getReferenceKey(keys, valueCutoff), + ) + : `[ref=${getReferenceKey(keys, valueCutoff)}]` + } + } else { + cache[0] = value + keys[0] = key + } + } + + return hasReplacer ? replacer.call(this, key, value) : value + } +} + +/** + * Stringifier that handles circular values + * + * Forked from https://github.com/planttheidea/fast-stringify + * + * @param value to stringify + * @param [replacer] a custom replacer function for handling standard values + * @param [indent] the number of spaces to indent the output by + * @param [circularReplacer] a custom replacer function for handling circular values + * @returns the stringified output + */ +export function serialize( + value: any, + replacer?: StandardReplacer | null | undefined, + indent?: number | null | undefined, + circularReplacer?: CircularReplacer | null | undefined, +) { + return JSON.stringify( + value, + createReplacer((key, value_) => { + let value = value_ + if (typeof value === 'bigint') + value = { __type: 'bigint', value: value_.toString() } + if (value instanceof Map) + value = { __type: 'Map', value: Array.from(value_.entries()) } + return replacer?.(key, value) ?? value + }, circularReplacer), + indent ?? undefined, + ) +} diff --git a/packages/core/src/utils/uid.ts b/packages/core/src/utils/uid.ts new file mode 100644 index 0000000000..4adc3e4e2b --- /dev/null +++ b/packages/core/src/utils/uid.ts @@ -0,0 +1,49 @@ +const size = 256 +let index = size +let buffer: string + +function getRandomBytes(byteLength: number): Uint8Array { + if ( + typeof globalThis !== 'undefined' && + globalThis.crypto && + typeof globalThis.crypto.getRandomValues === 'function' + ) { + const array = new Uint8Array(byteLength) + globalThis.crypto.getRandomValues(array) + return array + } + + // Fallback for Node.js environments that expose `require('crypto')`. + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const nodeCrypto = require('crypto') as { + randomBytes: (size: number) => { readonly [n: number]: number; length: number } + } + const buf = nodeCrypto.randomBytes(byteLength) + const array = new Uint8Array(byteLength) + for (let i = 0; i < byteLength; i++) array[i] = buf[i] + return array + } catch { + // ignore and fall through to non-cryptographic fallback + } + + // Last-resort, non-cryptographic fallback (used only if no crypto APIs are available). + const array = new Uint8Array(byteLength) + for (let i = 0; i < byteLength; i++) { + array[i] = (Math.random() * 256) | 0 + } + return array +} + +export function uid(length = 11) { + if (!buffer || index + length > size * 2) { + buffer = '' + index = 0 + const randomBytes = getRandomBytes(size) + for (let i = 0; i < size; i++) { + const byte = randomBytes[i] + buffer += byte.toString(16).padStart(2, '0') + } + } + return buffer.substring(index, index++ + length) +} diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts new file mode 100644 index 0000000000..e291f4b23c --- /dev/null +++ b/packages/core/src/version.ts @@ -0,0 +1 @@ +export const version = '2.17.2' diff --git a/packages/core/test/setup.ts b/packages/core/test/setup.ts new file mode 100644 index 0000000000..f59a0488fe --- /dev/null +++ b/packages/core/test/setup.ts @@ -0,0 +1,5 @@ +import { vi } from 'vitest' + +vi.mock('../src/version.ts', () => { + return { version: 'x.y.z' } +}) diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json new file mode 100644 index 0000000000..fbed2b1036 --- /dev/null +++ b/packages/core/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000000..bacbc9228c --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts", "test/**/*.ts"], + "exclude": [] +} diff --git a/packages/create-wagmi/CHANGELOG.md b/packages/create-wagmi/CHANGELOG.md new file mode 100644 index 0000000000..7ab58fe7e1 --- /dev/null +++ b/packages/create-wagmi/CHANGELOG.md @@ -0,0 +1,278 @@ +# create-wagmi + +## 2.0.14 + +### Patch Changes + +- [#4450](https://github.com/wevm/wagmi/pull/4450) [`7b9a6bb35881b657a00bdd7ccd7edea32660f5bf`](https://github.com/wevm/wagmi/commit/7b9a6bb35881b657a00bdd7ccd7edea32660f5bf) Thanks [@tmm](https://github.com/tmm)! - Removed internal usage of `fs-extra`. + +## 2.0.13 + +### Patch Changes + +- [#4060](https://github.com/wevm/wagmi/pull/4060) [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad) Thanks [@dalechyn](https://github.com/dalechyn)! - Bumped Tanstack Query dependencies to fix typing issues between exported Wagmi query options and TanStack Query suspense query methods (due to [`direction` property in `QueryFunctionContext` being deprecated](https://github.com/TanStack/query/pull/7410)). + +## 2.0.12 + +### Patch Changes + +- [`c00303d0c5909680b3124f92c0a2d2f31ea30405`](https://github.com/wevm/wagmi/commit/c00303d0c5909680b3124f92c0a2d2f31ea30405) Thanks [@tmm](https://github.com/tmm)! - Bumped Next.js version + +## 2.0.11 + +### Patch Changes + +- [#3871](https://github.com/wevm/wagmi/pull/3871) [`0e6bd286`](https://github.com/wevm/wagmi/commit/0e6bd286ca2572d2bfbe206db460528b7c2ebc63) Thanks [@jxom](https://github.com/jxom)! - Added `.npmrc` with `legacy-peer-deps = true` to templates. + +## 2.0.10 + +### Patch Changes + +- [`3f8203bd`](https://github.com/wevm/wagmi/commit/3f8203bd77fcf6b6756640b5971d09741ae3853d) Thanks [@tmm](https://github.com/tmm)! - Set Wagmi-related package versions to latest. + +## 2.0.9 + +### Patch Changes + +- [#3518](https://github.com/wevm/wagmi/pull/3518) [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +## 2.0.8 + +### Patch Changes + +- [#3510](https://github.com/wevm/wagmi/pull/3510) [`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where connectors returning multiple addresses didn't checksum correctly. + +## 2.0.7 + +### Patch Changes + +- [#3496](https://github.com/wevm/wagmi/pull/3496) [`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +## 2.0.6 + +### Patch Changes + +- [#3427](https://github.com/wevm/wagmi/pull/3427) [`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Bumped dependencies. + +## 2.0.5 + +### Patch Changes + +- [#3459](https://github.com/wevm/wagmi/pull/3459) [`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Bumped dependencies + +## 2.0.4 + +### Patch Changes + +- [#3443](https://github.com/wevm/wagmi/pull/3443) [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb) Thanks [@jmrossy](https://github.com/jmrossy)! - Bumped dependencies. + +- [#3447](https://github.com/wevm/wagmi/pull/3447) [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +## 2.0.3 + +### Patch Changes + +- [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37) Thanks [@tmm](https://github.com/tmm)! - Added note to `metaMask` connector. + +## 2.0.2 + +### Patch Changes + +- [#3384](https://github.com/wevm/wagmi/pull/3384) [`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844) Thanks [@tmm](https://github.com/tmm)! - Fixed connectors not bubbling error when connecting with `chainId` and subsequent user rejection. + +## 2.0.1 + +### Major Changes + +- [#3333](https://github.com/wevm/wagmi/pull/3333) [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a) Thanks [@tmm](https://github.com/tmm)! - Added support for Wagmi 2.0. + +## 1.0.5 + +### Patch Changes + +- 6ba996f: Fixed optional WalletConnect Cloud Project ID prompt + +## 1.0.4 + +### Patch Changes + +- eb8dd9d: Updated wagmi & viem. Added ConnectKit template back in. + +## 1.0.3 + +### Patch Changes + +- 00d9080: Added RainbowKit & Web3Modal wagmi v1 templates + +## 1.0.2 + +### Patch Changes + +- 3c65f77: Fixed Next.js default template title + +## 1.0.0 + +### Major Changes + +- affc13e: Updated to wagmi v1. + +## 0.1.19 + +### Patch Changes + +- 1282f0e: Updated templates to use WalletConnect v2 by default + +## 0.1.18 + +### Patch Changes + +- 12dcfe0: Updated wagmi to 0.12 in ConnectKit templates. + +## 0.1.17 + +### Patch Changes + +- 6eba01a: Updated wagmi to 0.12. +- 3a0ab8c: Added .env to .gitignore in templates. + +## 0.1.16 + +### Patch Changes + +- 7ad50f1: Upgraded `@wagmi/cli` + +## 0.1.15 + +### Patch Changes + +- 6e30599: Added `test` config to `foundry.toml` on the Foundry templates. + +## 0.1.14 + +### Patch Changes + +- 19f3675: Bumped `wagmi` to `~0.11.0`. + +## 0.1.13 + +### Patch Changes + +- c1ab75c: Bump @wagmi/cli + +## 0.1.12 + +### Patch Changes + +- af6e551: Added templates for `@wagmi/cli`: + + - `@wagmi/cli (Mint NFT Example)` + - `@wagmi/cli + ERC20` + - `@wagmi/cli + Etherscan (Mint NFT Example)` + - `@wagmi/cli + Foundry (Counter.sol Example)` + +## 0.1.11 + +### Patch Changes + +- cc638dd: Fixed an issue where Vite projects used `process.env` instead of `import.meta.env`. +- 75d1c2d: Updated `wagmi` to `~0.10.0`. + +## 0.1.10 + +### Patch Changes + +- 9cd7140: Amend gitignore files for Vite templates + +## 0.1.9 + +### Patch Changes + +- 904a2e1: Fixed import env variables in Vite (React) templates + +## 0.1.8 + +### Patch Changes + +- 65cc841: Update RainbowKit & ConnectKit templates to `wagmi@~0.9.0` + +## 0.1.7 + +### Patch Changes + +- a59d9c5: Upgrade `default` & `web3modal` templates to `wagmi@0.9` + +## 0.1.6 + +### Patch Changes + +- b544457: Updated `connectkit` to `1.1.0` in the ConnectKit templates + +## 0.1.5 + +### Patch Changes + +- 6bd6c74: Updated repo link in package.json + +## 0.1.4 + +### Patch Changes + +- c39666b: Added ability to select providers +- 37708ed: **Added ability to select frameworks.** + + Each directory in `templates/` now mirrors a "framework", where its child directories mirror a "template" for that framework. + + Example: + + ``` + templates/ + next/ + connectkit/ + default/ + rainbowkit/ + web3modal + vite-react/ + connectkit/ + default/ + rainbowkit/ + web3modal/ + ``` + +- 399d2b9: Moved template configuration to the template level + added hooks + +## 0.1.3 + +### Patch Changes + +- 353332a: Added Web3Modal template +- dd95b14: **next-with-connectkit**: Update `connectkit` to `^1.0.0` + +## 0.1.2 + +### Patch Changes + +- 34f666b: Fixed issue where package manager install process would not log error + +## 0.1.1 + +### Patch Changes + +- 900cbdc: Updated `@rainbow-me/rainbowkit` dependency in Next + RainbowKit template + +## 0.1.0 + +### Minor Changes + +- 23993d2: ## 🎉 Initial release 🎉 + + Get up and running quickly with wagmi by using the `create-wagmi` CLI. This tool interactively scaffolds a new wagmi project for you so you can start building instantly without the hassle of setting up `git`, installing packages, worrying about TypeScript configuration, etc. + + To get started, `create-wagmi` can be instantiated with one of your favorite package managers: + + ```bash + npm init wagmi + # or + pnpm create wagmi + # or + yarn create wagmi + ``` diff --git a/packages/create-wagmi/README.md b/packages/create-wagmi/README.md new file mode 100644 index 0000000000..f923127ca2 --- /dev/null +++ b/packages/create-wagmi/README.md @@ -0,0 +1,17 @@ +# create-wagmi + +Get up and running quickly with [Wagmi](https://wagmi.sh) by using the `create-wagmi` CLI. + +## Installation + +```bash +npm create wagmi +# or +pnpm create wagmi +# or +yarn create wagmi +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh/cli/create-wagmi). diff --git a/packages/create-wagmi/package.json b/packages/create-wagmi/package.json new file mode 100644 index 0000000000..7b6798c628 --- /dev/null +++ b/packages/create-wagmi/package.json @@ -0,0 +1,49 @@ +{ + "name": "create-wagmi", + "description": "Create Wagmi apps with one command", + "version": "2.0.14", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/create-wagmi" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "dev": "bun src/cli.ts", + "test:build": "publint --strict" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "templates/**" + ], + "bin": { + "wagmi": "./dist/esm/cli.js" + }, + "sideEffects": false, + "type": "module", + "exports": { + "./package.json": "./package.json" + }, + "dependencies": { + "cac": "^6.7.14", + "cross-spawn": "^7.0.5", + "picocolors": "^1.0.0", + "prompts": "^2.4.2" + }, + "devDependencies": { + "@types/cross-spawn": "^6.0.6", + "@types/node": "^20.12.10", + "@types/prompts": "^2.4.9" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": ["wagmi", "eth", "ethereum", "dapps", "wallet", "web3", "cli"] +} diff --git a/packages/create-wagmi/src/cli.test.ts b/packages/create-wagmi/src/cli.test.ts new file mode 100644 index 0000000000..4d6915a6f3 --- /dev/null +++ b/packages/create-wagmi/src/cli.test.ts @@ -0,0 +1,151 @@ +import { + type ExecSyncOptionsWithStringEncoding, + execSync, +} from 'node:child_process' +import { mkdirSync, readdirSync, writeFileSync } from 'node:fs' +import { rm } from 'node:fs/promises' +import { join } from 'node:path' +import pc from 'picocolors' +import { afterEach, beforeAll, expect, test } from 'vitest' + +import { version } from './version.js' + +const cliPath = join(__dirname, '../dist/esm/cli.js') + +const projectName = 'test-app' +const genPath = join(__dirname, projectName) + +function run( + args: string[], + options: ExecSyncOptionsWithStringEncoding = { encoding: 'utf8' }, +): string { + return execSync(`node ${cliPath} ${args.join(' ')}`, options) +} + +function createNonEmptyDir() { + mkdirSync(genPath, { recursive: true }) + const file = join(genPath, 'foo') + writeFileSync(file, 'bar') +} + +beforeAll(async () => { + execSync('pnpm --filter create-wagmi build') + await rm(genPath, { recursive: true, force: true }) +}) +afterEach(async () => { + await rm(genPath, { recursive: true, force: true }) +}) + +test('prompts for the project name if none supplied', () => { + const stdout = run([]) + expect(stdout).toContain('Project name:') +}) + +test('prompts for the framework if none supplied when target dir is current directory', () => { + mkdirSync(genPath) + const stdout = run(['.'], { cwd: genPath, encoding: 'utf8' }) + expect(stdout).toContain('Select a framework:') +}) + +test('prompts for the framework if none supplied', () => { + const stdout = run([projectName]) + expect(stdout).toContain('Select a framework:') +}) + +test('prompts for the framework on not supplying a value for --template', () => { + const stdout = run([projectName, '--template']) + expect(stdout).toContain('Select a framework:') +}) + +test('prompts for the framework on supplying an invalid template', () => { + const stdout = run([projectName, '--template', 'unknown']) + expect(stdout).toContain( + `"unknown" isn't a valid template. Please choose from below:`, + ) +}) + +test('asks to overwrite non-empty target directory', () => { + createNonEmptyDir() + const stdout = run([projectName], { cwd: __dirname, encoding: 'utf8' }) + expect(stdout).toContain(`Target directory "${projectName}" is not empty.`) +}) + +test('asks to overwrite non-empty current directory', () => { + createNonEmptyDir() + const stdout = run(['.'], { cwd: genPath, encoding: 'utf8' }) + expect(stdout).toContain('Current directory is not empty.') +}) + +const templateFiles = readdirSync( + join(cliPath, '../../../templates/vite-react'), +) + .map((filePath) => { + if (filePath === '_gitignore') return '.gitignore' + if (filePath === '_env.local') return '.env.local' + if (filePath === '_npmrc') return '.npmrc' + return filePath + }) + .sort() + +test('successfully scaffolds a project based on vite-react starter template', () => { + mkdirSync(genPath, { recursive: true }) + const stdout = run([projectName, '--template', 'vite-react'], { + cwd: __dirname, + encoding: 'utf8', + }) + const generatedFiles = readdirSync(genPath).sort() + + expect(stdout).toContain(`Scaffolding project in ${genPath}`) + expect(templateFiles).toEqual(generatedFiles) +}) + +test('works with the -t alias', () => { + mkdirSync(genPath, { recursive: true }) + const stdout = run([projectName, '-t', 'vite-react'], { + cwd: __dirname, + encoding: 'utf8', + }) + const generatedFiles = readdirSync(genPath).sort() + + expect(stdout).toContain(`Scaffolding project in ${genPath}`) + expect(templateFiles).toEqual(generatedFiles) +}) + +test('uses different package manager', () => { + mkdirSync(genPath, { recursive: true }) + const stdout = run([projectName, '--bun', '-t', 'vite-react'], { + cwd: __dirname, + encoding: 'utf8', + }) + + expect(stdout).toContain('bun install') +}) + +test('shows help', () => { + const stdout = run(['--help']) + expect( + stdout + .replace(version, 'x.y.z') + .replace(pc.green(''), ''), + ).toMatchInlineSnapshot(` + "create-wagmi/x.y.z + + Usage: + $ create-wagmi [options] + + Options: + -t, --template [name] Template to bootstrap with. Available: vite-react, next, vite-vue, nuxt, vite-vanilla + --bun Use bun as your package manager + --npm Use npm as your package manager + --pnpm Use pnpm as your package manager + --yarn Use yarn as your package manager + -h, --help Display this message + -v, --version Display version number + " + `) +}) + +test('shows version', () => { + const stdout = run(['--version']) + expect(stdout).toContain(`create-wagmi/${version} `) +}) diff --git a/packages/create-wagmi/src/cli.ts b/packages/create-wagmi/src/cli.ts new file mode 100644 index 0000000000..ecb6a25a4c --- /dev/null +++ b/packages/create-wagmi/src/cli.ts @@ -0,0 +1,284 @@ +#!/usr/bin/env node +import * as fs from 'node:fs' +import * as path from 'node:path' +import { fileURLToPath } from 'node:url' +import { cac } from 'cac' +import spawn from 'cross-spawn' +import pc from 'picocolors' +import prompts from 'prompts' + +import { type Framework, frameworks } from './frameworks.js' +import { + copy, + emptyDir, + formatTargetDir, + isEmpty, + isValidPackageName, + pkgFromUserAgent, + toValidPackageName, +} from './utils.js' +import { version } from './version.js' + +const templates = frameworks + .map((f) => f.variants?.map((v) => v.name) || [f.name]) + .reduce((a, b) => a.concat(b), []) + +const cli = cac('create-wagmi') + +cli + .usage(`${pc.green('')} [options]`) + .option( + '-t, --template [name]', + `Template to bootstrap with. Available: ${templates + .sort((a, b) => (a && !b ? -1 : 1)) + .join(', ')}`, + ) + .option('--bun', 'Use bun as your package manager') + .option('--npm', 'Use npm as your package manager') + .option('--pnpm', 'Use pnpm as your package manager') + .option('--yarn', 'Use yarn as your package manager') + +cli.help() +cli.version(version) + +const cwd = process.cwd() + +const renameFiles: Record = { + '_env.local': '.env.local', + // https://github.com/npm/npm/issues/1862 + _gitignore: '.gitignore', + _npmrc: '.npmrc', +} + +const defaultTargetDir = 'wagmi-project' + +async function init() { + const { args, options } = cli.parse(process.argv) + if (options.help) return + if (options.version) return + + const argTargetDir = formatTargetDir(args[0]) + const argTemplate = options.template || options.t + + let targetDir = argTargetDir || defaultTargetDir + function getProjectName() { + return targetDir === '.' ? path.basename(path.resolve()) : targetDir + } + + let result: prompts.Answers< + 'framework' | 'overwrite' | 'packageName' | 'projectName' | 'variant' + > + try { + result = await prompts( + [ + { + type: argTargetDir ? null : 'text', + name: 'projectName', + message: pc.reset('Project name:'), + initial: defaultTargetDir, + onState(state) { + targetDir = formatTargetDir(state.value) || defaultTargetDir + }, + }, + { + type() { + return !fs.existsSync(targetDir) || isEmpty(targetDir) + ? null + : 'confirm' + }, + name: 'overwrite', + message() { + return `${ + targetDir === '.' + ? 'Current directory' + : `Target directory "${targetDir}"` + } is not empty. Remove existing files and continue?` + }, + }, + { + type(_, { overwrite }: { overwrite?: boolean }) { + if (overwrite === false) + throw new Error(`${pc.red('✖')} Operation cancelled`) + return null + }, + name: 'overwriteChecker', + }, + { + type() { + return isValidPackageName(getProjectName()) ? null : 'text' + }, + name: 'packageName', + message: pc.reset('Package name:'), + initial() { + return toValidPackageName(getProjectName()) + }, + validate(dir) { + return isValidPackageName(dir) || 'Invalid package.json name' + }, + }, + { + type: + argTemplate && templates.includes(argTemplate) ? null : 'select', + name: 'framework', + message: + typeof argTemplate === 'string' && !templates.includes(argTemplate) + ? pc.reset( + `"${argTemplate}" isn't a valid template. Please choose from below: `, + ) + : pc.reset('Select a framework:'), + initial: 0, + choices: frameworks.map((framework) => { + const frameworkColor = framework.color + return { + title: frameworkColor(framework.display || framework.name), + value: framework, + } + }), + }, + { + type(framework: Framework) { + return framework?.variants?.length > 1 ? 'select' : null + }, + name: 'variant', + message: pc.reset('Select a variant:'), + choices(framework: Framework) { + return framework.variants.map((variant) => { + const variantColor = variant.color + return { + title: variantColor(variant.display || variant.name), + value: variant.name, + } + }) + }, + }, + ], + { + onCancel() { + throw new Error(`${pc.red('✖')} Operation cancelled`) + }, + }, + ) + } catch (error) { + // biome-ignore lint/suspicious/noConsoleLog: + console.log((error as Error).message) + return + } + + // user choice associated with prompts + const { + framework, + overwrite, + packageName, + variant = framework?.variants[0]?.name, + } = result + + const root = path.join(cwd, targetDir) + + if (overwrite) emptyDir(root) + else if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true }) + + // determine template + const template: string = variant || framework?.name || argTemplate + + const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent) + type PkgManager = 'bun' | 'npm' | 'pnpm' | 'yarn' + let pkgManager: PkgManager + if (options.bun) pkgManager = 'bun' + else if (options.npm) pkgManager = 'npm' + else if (options.pnpm) pkgManager = 'pnpm' + else if (options.yarn) pkgManager = 'yarn' + else pkgManager = pkgInfo ? (pkgInfo.name as PkgManager) : 'npm' + const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version?.startsWith('1.') + + const { customCommand } = + frameworks.flatMap((f) => f.variants).find((v) => v.name === template) ?? {} + + if (customCommand) { + const fullCustomCommand = customCommand + .replace(/^npm create /, () => { + // `bun create` uses it's own set of templates, + // the closest alternative is using `bun x` directly on the package + if (pkgManager === 'bun') return 'bun x create-' + return `${pkgManager} create ` + }) + // Only Yarn 1.x doesn't support `@version` in the `create` command + .replace('@latest', () => (isYarn1 ? '' : '@latest')) + .replace(/^npm exec/, () => { + // Prefer `pnpm dlx`, `yarn dlx`, or `bun x` + if (pkgManager === 'pnpm') return 'pnpm dlx' + if (pkgManager === 'yarn' && !isYarn1) return 'yarn dlx' + if (pkgManager === 'bun') return 'bun x' + // Use `npm exec` in all other cases, + // including Yarn 1.x and other custom npm clients. + return 'npm exec' + }) + + const [command, ...args] = fullCustomCommand.split(' ') + // we replace TARGET_DIR here because targetDir may include a space + const replacedArgs = args.map((arg) => arg.replace('TARGET_DIR', targetDir)) + const { status } = spawn.sync(command!, replacedArgs, { + stdio: 'inherit', + }) + process.exit(status ?? 0) + } + + // biome-ignore lint/suspicious/noConsoleLog: + console.log(`\nScaffolding project in ${root}...`) + + const templateDir = path.resolve( + fileURLToPath(import.meta.url), + '../../../templates', + template, + ) + + function write(file: string, content?: string) { + const targetPath = path.join(root, renameFiles[file] ?? file) + if (content) fs.writeFileSync(targetPath, content) + else copy(path.join(templateDir, file), targetPath) + } + + const files = fs.readdirSync(templateDir) + for (const file of files.filter((f) => f !== 'package.json')) { + write(file) + } + + const pkg = JSON.parse( + fs.readFileSync(path.join(templateDir, 'package.json'), 'utf-8'), + ) + + pkg.name = packageName || getProjectName() + + write('package.json', `${JSON.stringify(pkg, null, 2)}\n`) + + const cdProjectName = path.relative(cwd, root) + // biome-ignore lint/suspicious/noConsoleLog: + console.log('\nDone. Now run:\n') + if (root !== cwd) + // biome-ignore lint/suspicious/noConsoleLog: + console.log( + ` cd ${ + cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName + }`, + ) + + switch (pkgManager) { + case 'yarn': + // biome-ignore lint/suspicious/noConsoleLog: + console.log(' yarn') + // biome-ignore lint/suspicious/noConsoleLog: + console.log(' yarn dev') + break + default: + // biome-ignore lint/suspicious/noConsoleLog: + console.log(` ${pkgManager} install`) + // biome-ignore lint/suspicious/noConsoleLog: + console.log(` ${pkgManager} run dev`) + break + } + // biome-ignore lint/suspicious/noConsoleLog: + console.log() +} + +init().catch((e) => { + console.error(e) +}) diff --git a/packages/create-wagmi/src/frameworks.ts b/packages/create-wagmi/src/frameworks.ts new file mode 100644 index 0000000000..1b414bafc8 --- /dev/null +++ b/packages/create-wagmi/src/frameworks.ts @@ -0,0 +1,66 @@ +import pc from 'picocolors' + +type ColorFunc = (str: string | number) => string + +type FrameworkVariant = { + name: string + display: string + color: ColorFunc + customCommand?: string +} + +export type Framework = { + name: string + display: string + color: ColorFunc + variants: readonly FrameworkVariant[] +} + +export const frameworks: readonly Framework[] = [ + { + name: 'react', + display: 'React', + color: pc.cyan, + variants: [ + { + name: 'vite-react', + display: 'Vite', + color: pc.blue, + }, + { + name: 'next', + display: 'Next', + color: pc.yellow, + }, + ], + }, + { + name: 'vue', + display: 'Vue', + color: pc.green, + variants: [ + { + name: 'vite-vue', + display: 'Vite', + color: pc.blue, + }, + { + name: 'nuxt', + display: 'Nuxt', + color: pc.yellow, + }, + ], + }, + { + name: 'vanilla', + display: 'Vanilla', + color: pc.magenta, + variants: [ + { + name: 'vite-vanilla', + display: 'Vite', + color: pc.blue, + }, + ], + }, +] diff --git a/packages/create-wagmi/src/index.test-d.ts b/packages/create-wagmi/src/index.test-d.ts new file mode 100644 index 0000000000..b056d56358 --- /dev/null +++ b/packages/create-wagmi/src/index.test-d.ts @@ -0,0 +1,4 @@ +import { expectTypeOf } from 'vitest' + +// noop test because vitest typecheck fails unless each workspace project has type test +expectTypeOf(1).toEqualTypeOf() diff --git a/packages/create-wagmi/src/utils.ts b/packages/create-wagmi/src/utils.ts new file mode 100644 index 0000000000..d04f60ed93 --- /dev/null +++ b/packages/create-wagmi/src/utils.ts @@ -0,0 +1,79 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' + +export function formatTargetDir(targetDir: string | undefined) { + return targetDir?.trim().replace(/\/+$/g, '') +} + +export function copy(src: string, dest: string) { + const stat = fs.statSync(src) + if (stat.isDirectory()) copyDir(src, dest) + else fs.copyFileSync(src, dest) +} + +function copyDir(srcDir: string, destDir: string) { + fs.mkdirSync(destDir, { recursive: true }) + for (const file of fs.readdirSync(srcDir)) { + const srcFile = path.resolve(srcDir, file) + const destFile = path.resolve(destDir, file) + copy(srcFile, destFile) + } +} + +export function isValidPackageName(projectName: string) { + return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test( + projectName, + ) +} + +export function toValidPackageName(projectName: string) { + return projectName + .trim() + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/^[._]/, '') + .replace(/[^a-z\d\-~]+/g, '-') +} + +export function isEmpty(path: string) { + const files = fs.readdirSync(path) + return files.length === 0 || (files.length === 1 && files[0] === '.git') +} + +export function emptyDir(dir: string) { + if (!fs.existsSync(dir)) return + for (const file of fs.readdirSync(dir)) { + if (file === '.git') continue + fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) + } +} + +export function pkgFromUserAgent(userAgent: string | undefined) { + if (!userAgent) return undefined + const pkgSpec = userAgent.split(' ')[0]! + const pkgSpecArr = pkgSpec.split('/') + return { + name: pkgSpecArr[0], + version: pkgSpecArr[1], + } +} + +// function setupReactSwc(root: string, isTs: boolean) { +// editFile(path.resolve(root, 'package.json'), (content) => { +// return content.replace( +// /"@vitejs\/plugin-react": ".+?"/, +// `"@vitejs/plugin-react-swc": "^3.3.2"`, +// ) +// }) +// editFile( +// path.resolve(root, `vite.config.${isTs ? 'ts' : 'js'}`), +// (content) => { +// return content.replace('@vitejs/plugin-react', '@vitejs/plugin-react-swc') +// }, +// ) +// } + +// function editFile(file: string, callback: (content: string) => string) { +// const content = fs.readFileSync(file, 'utf-8') +// fs.writeFileSync(file, callback(content), 'utf-8') +// } diff --git a/packages/create-wagmi/src/version.ts b/packages/create-wagmi/src/version.ts new file mode 100644 index 0000000000..5556800c0d --- /dev/null +++ b/packages/create-wagmi/src/version.ts @@ -0,0 +1 @@ +export const version = '2.0.14' diff --git a/packages/create-wagmi/templates/next/README.md b/packages/create-wagmi/templates/next/README.md new file mode 100644 index 0000000000..bd3aa7b7f0 --- /dev/null +++ b/packages/create-wagmi/templates/next/README.md @@ -0,0 +1 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi). diff --git a/packages/create-wagmi/templates/next/_env.local b/packages/create-wagmi/templates/next/_env.local new file mode 100644 index 0000000000..9a11dba16a --- /dev/null +++ b/packages/create-wagmi/templates/next/_env.local @@ -0,0 +1,2 @@ +NEXT_PUBLIC_WC_PROJECT_ID= +NEXT_TELEMETRY_DISABLED=1 \ No newline at end of file diff --git a/packages/create-wagmi/templates/next/_gitignore b/packages/create-wagmi/templates/next/_gitignore new file mode 100644 index 0000000000..8f322f0d8f --- /dev/null +++ b/packages/create-wagmi/templates/next/_gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# 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 diff --git a/packages/create-wagmi/templates/next/_npmrc b/packages/create-wagmi/templates/next/_npmrc new file mode 100644 index 0000000000..ca1e9d98b5 --- /dev/null +++ b/packages/create-wagmi/templates/next/_npmrc @@ -0,0 +1 @@ +legacy-peer-deps = true \ No newline at end of file diff --git a/packages/create-wagmi/templates/next/next-env.d.ts b/packages/create-wagmi/templates/next/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/packages/create-wagmi/templates/next/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/packages/create-wagmi/templates/next/next.config.js b/packages/create-wagmi/templates/next/next.config.js new file mode 100644 index 0000000000..767719fc4f --- /dev/null +++ b/packages/create-wagmi/templates/next/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/packages/create-wagmi/templates/next/package.json b/packages/create-wagmi/templates/next/package.json new file mode 100644 index 0000000000..55ac1dac6d --- /dev/null +++ b/packages/create-wagmi/templates/next/package.json @@ -0,0 +1,32 @@ +{ + "name": "wagmi-next-starter", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@tanstack/react-query": "5.45.1", + "next": "15.4.10", + "react": "^19.1.0", + "react-dom": "^18.3.1", + "viem": "latest", + "wagmi": "latest" + }, + "devDependencies": { + "@types/node": "^20.12.10", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@wagmi/cli": "latest", + "bufferutil": "^4.0.8", + "encoding": "^0.1.13", + "lokijs": "^1.5.12", + "pino-pretty": "^10.3.1", + "supports-color": "^9.4.0", + "typescript": "^5.4.5", + "utf-8-validate": "^5.0.2" + } +} diff --git a/packages/create-wagmi/templates/next/src/app/globals.css b/packages/create-wagmi/templates/next/src/app/globals.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/packages/create-wagmi/templates/next/src/app/globals.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/packages/create-wagmi/templates/next/src/app/layout.tsx b/packages/create-wagmi/templates/next/src/app/layout.tsx new file mode 100644 index 0000000000..da5b293bf0 --- /dev/null +++ b/packages/create-wagmi/templates/next/src/app/layout.tsx @@ -0,0 +1,30 @@ +import './globals.css' +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import { headers } from 'next/headers' +import { type ReactNode } from 'react' +import { cookieToInitialState } from 'wagmi' + +import { getConfig } from '../wagmi' +import { Providers } from './providers' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'Create Wagmi', + description: 'Generated by create-wagmi', +} + +export default async function RootLayout(props: { children: ReactNode }) { + const initialState = cookieToInitialState( + getConfig(), + (await headers()).get('cookie'), + ) + return ( + + + {props.children} + + + ) +} diff --git a/packages/create-wagmi/templates/next/src/app/page.tsx b/packages/create-wagmi/templates/next/src/app/page.tsx new file mode 100644 index 0000000000..f5dcbdf812 --- /dev/null +++ b/packages/create-wagmi/templates/next/src/app/page.tsx @@ -0,0 +1,48 @@ +'use client' + +import { useAccount, useConnect, useDisconnect } from 'wagmi' + +function App() { + const account = useAccount() + const { connectors, connect, status, error } = useConnect() + const { disconnect } = useDisconnect() + + return ( + <> +
+

Account

+ +
+ status: {account.status} +
+ addresses: {JSON.stringify(account.addresses)} +
+ chainId: {account.chainId} +
+ + {account.status === 'connected' && ( + + )} +
+ +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ + ) +} + +export default App diff --git a/packages/create-wagmi/templates/next/src/app/providers.tsx b/packages/create-wagmi/templates/next/src/app/providers.tsx new file mode 100644 index 0000000000..b4086cf531 --- /dev/null +++ b/packages/create-wagmi/templates/next/src/app/providers.tsx @@ -0,0 +1,23 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { type ReactNode, useState } from 'react' +import { type State, WagmiProvider } from 'wagmi' + +import { getConfig } from '@/wagmi' + +export function Providers(props: { + children: ReactNode + initialState?: State +}) { + const [config] = useState(() => getConfig()) + const [queryClient] = useState(() => new QueryClient()) + + return ( + + + {props.children} + + + ) +} diff --git a/packages/create-wagmi/templates/next/src/wagmi.ts b/packages/create-wagmi/templates/next/src/wagmi.ts new file mode 100644 index 0000000000..b34f4c0079 --- /dev/null +++ b/packages/create-wagmi/templates/next/src/wagmi.ts @@ -0,0 +1,28 @@ +import { http, cookieStorage, createConfig, createStorage } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' +import { coinbaseWallet, injected, walletConnect } from 'wagmi/connectors' + +export function getConfig() { + return createConfig({ + chains: [mainnet, sepolia], + connectors: [ + injected(), + coinbaseWallet(), + walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID }), + ], + storage: createStorage({ + storage: cookieStorage, + }), + ssr: true, + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, + }) +} + +declare module 'wagmi' { + interface Register { + config: ReturnType + } +} diff --git a/packages/create-wagmi/templates/next/tsconfig.json b/packages/create-wagmi/templates/next/tsconfig.json new file mode 100644 index 0000000000..e59724b283 --- /dev/null +++ b/packages/create-wagmi/templates/next/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/create-wagmi/templates/nuxt/_env.local b/packages/create-wagmi/templates/nuxt/_env.local new file mode 100644 index 0000000000..437e9e3e7b --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/_env.local @@ -0,0 +1,3 @@ +NUXT_PUBLIC_WC_PROJECT_ID= +NUXT_TELEMETRY_DISABLED=1 + diff --git a/packages/create-wagmi/templates/nuxt/_gitignore b/packages/create-wagmi/templates/nuxt/_gitignore new file mode 100644 index 0000000000..4a7f73a2ed --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/_gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/packages/create-wagmi/templates/nuxt/_npmrc b/packages/create-wagmi/templates/nuxt/_npmrc new file mode 100644 index 0000000000..057cc841f2 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/_npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps = true + diff --git a/packages/create-wagmi/templates/nuxt/app.vue b/packages/create-wagmi/templates/nuxt/app.vue new file mode 100644 index 0000000000..98b46bf528 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/app.vue @@ -0,0 +1,28 @@ + + + diff --git a/packages/create-wagmi/templates/nuxt/components/Account.vue b/packages/create-wagmi/templates/nuxt/components/Account.vue new file mode 100644 index 0000000000..33f1491dac --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/components/Account.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/create-wagmi/templates/nuxt/components/Connect.vue b/packages/create-wagmi/templates/nuxt/components/Connect.vue new file mode 100644 index 0000000000..93320448c0 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/components/Connect.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/create-wagmi/templates/nuxt/nuxt.config.ts b/packages/create-wagmi/templates/nuxt/nuxt.config.ts new file mode 100644 index 0000000000..adfe7fd2d8 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/nuxt.config.ts @@ -0,0 +1,7 @@ +import { defineNuxtConfig } from 'nuxt/config' + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + devtools: { enabled: true }, + modules: ['@wagmi/vue/nuxt'], +}) diff --git a/packages/create-wagmi/templates/nuxt/package.json b/packages/create-wagmi/templates/nuxt/package.json new file mode 100644 index 0000000000..65ff657528 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/package.json @@ -0,0 +1,21 @@ +{ + "name": "nuxt-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare" + }, + "dependencies": { + "@tanstack/vue-query": ">=5.45.0", + "@wagmi/vue": "latest", + "nuxt": "^3.11.2", + "viem": "latest", + "vue": ">=3.4.21", + "vue-router": "^4.3.2" + } +} diff --git a/packages/create-wagmi/templates/nuxt/plugins/wagmi.ts b/packages/create-wagmi/templates/nuxt/plugins/wagmi.ts new file mode 100644 index 0000000000..b6abe5bcd2 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/plugins/wagmi.ts @@ -0,0 +1,10 @@ +import { VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { defineNuxtPlugin } from 'nuxt/app' + +import { config } from '../wagmi' + +// TODO: Move to @wagmi/vue/nuxt nitro plugin +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.vueApp.use(WagmiPlugin, { config }).use(VueQueryPlugin, {}) +}) diff --git a/packages/create-wagmi/templates/nuxt/server/tsconfig.json b/packages/create-wagmi/templates/nuxt/server/tsconfig.json new file mode 100644 index 0000000000..b9ed69c19e --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +} diff --git a/packages/create-wagmi/templates/nuxt/tsconfig.json b/packages/create-wagmi/templates/nuxt/tsconfig.json new file mode 100644 index 0000000000..a746f2a70c --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/tsconfig.json @@ -0,0 +1,4 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "extends": "./.nuxt/tsconfig.json" +} diff --git a/packages/create-wagmi/templates/nuxt/wagmi.ts b/packages/create-wagmi/templates/nuxt/wagmi.ts new file mode 100644 index 0000000000..83e8569ea8 --- /dev/null +++ b/packages/create-wagmi/templates/nuxt/wagmi.ts @@ -0,0 +1,29 @@ +import { http, cookieStorage, createConfig, createStorage } from '@wagmi/vue' +import { mainnet, optimism, sepolia } from '@wagmi/vue/chains' +import { injected, metaMask, walletConnect } from '@wagmi/vue/connectors' + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + injected(), + walletConnect({ + projectId: process.env.NUXT_PUBLIC_WC_PROJECT_ID!, + }), + metaMask(), + ], + storage: createStorage({ + storage: cookieStorage, + }), + ssr: true, + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + }, +}) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/packages/create-wagmi/templates/vite-react/README.md b/packages/create-wagmi/templates/vite-react/README.md new file mode 100644 index 0000000000..15f6f79541 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/README.md @@ -0,0 +1 @@ +This is a [Vite](https://vitejs.dev) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi). diff --git a/packages/create-wagmi/templates/vite-react/_env.local b/packages/create-wagmi/templates/vite-react/_env.local new file mode 100644 index 0000000000..936f763762 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/_env.local @@ -0,0 +1 @@ +VITE_WC_PROJECT_ID= \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-react/_gitignore b/packages/create-wagmi/templates/vite-react/_gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/_gitignore @@ -0,0 +1,24 @@ +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/create-wagmi/templates/vite-react/_npmrc b/packages/create-wagmi/templates/vite-react/_npmrc new file mode 100644 index 0000000000..ca1e9d98b5 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/_npmrc @@ -0,0 +1 @@ +legacy-peer-deps = true \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-react/biome.json b/packages/create-wagmi/templates/vite-react/biome.json new file mode 100644 index 0000000000..08eb8a26ab --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/biome.json @@ -0,0 +1,13 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 120 + }, + "linter": { + "enabled": true + }, + "organizeImports": { + "enabled": true + } +} diff --git a/packages/create-wagmi/templates/vite-react/index.html b/packages/create-wagmi/templates/vite-react/index.html new file mode 100644 index 0000000000..f519ce85a7 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/index.html @@ -0,0 +1,12 @@ + + + + + + Create Wagmi + + +
+ + + diff --git a/packages/create-wagmi/templates/vite-react/package.json b/packages/create-wagmi/templates/vite-react/package.json new file mode 100644 index 0000000000..0eeaab9a10 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/package.json @@ -0,0 +1,29 @@ +{ + "name": "wagmi-vite-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "biome check .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "5.45.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "latest", + "wagmi": "latest" + }, + "devDependencies": { + "@biomejs/biome": "^1.8.0", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.2.1", + "@wagmi/cli": "latest", + "buffer": "^6.0.3", + "typescript": "^5.8.3", + "vite": "^5.2.11" + } +} diff --git a/packages/create-wagmi/templates/vite-react/src/App.tsx b/packages/create-wagmi/templates/vite-react/src/App.tsx new file mode 100644 index 0000000000..faa8ce1c79 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/src/App.tsx @@ -0,0 +1,46 @@ +import { useAccount, useConnect, useDisconnect } from 'wagmi' + +function App() { + const account = useAccount() + const { connectors, connect, status, error } = useConnect() + const { disconnect } = useDisconnect() + + return ( + <> +
+

Account

+ +
+ status: {account.status} +
+ addresses: {JSON.stringify(account.addresses)} +
+ chainId: {account.chainId} +
+ + {account.status === 'connected' && ( + + )} +
+ +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ + ) +} + +export default App diff --git a/packages/create-wagmi/templates/vite-react/src/index.css b/packages/create-wagmi/templates/vite-react/src/index.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/src/index.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/packages/create-wagmi/templates/vite-react/src/main.tsx b/packages/create-wagmi/templates/vite-react/src/main.tsx new file mode 100644 index 0000000000..d999e1a932 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/src/main.tsx @@ -0,0 +1,24 @@ +import { Buffer } from 'buffer' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import React from 'react' +import ReactDOM from 'react-dom/client' +import { WagmiProvider } from 'wagmi' + +import App from './App.tsx' +import { config } from './wagmi.ts' + +import './index.css' + +globalThis.Buffer = Buffer + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + , +) diff --git a/packages/create-wagmi/templates/vite-react/src/vite-env.d.ts b/packages/create-wagmi/templates/vite-react/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/create-wagmi/templates/vite-react/src/wagmi.ts b/packages/create-wagmi/templates/vite-react/src/wagmi.ts new file mode 100644 index 0000000000..43cf231934 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/src/wagmi.ts @@ -0,0 +1,22 @@ +import { http, createConfig } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' +import { coinbaseWallet, injected, walletConnect } from 'wagmi/connectors' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [ + injected(), + coinbaseWallet(), + walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }), + ], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} diff --git a/packages/create-wagmi/templates/vite-react/tsconfig.json b/packages/create-wagmi/templates/vite-react/tsconfig.json new file mode 100644 index 0000000000..a7fc6fbf23 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/create-wagmi/templates/vite-react/tsconfig.node.json b/packages/create-wagmi/templates/vite-react/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/create-wagmi/templates/vite-react/vite.config.ts b/packages/create-wagmi/templates/vite-react/vite.config.ts new file mode 100644 index 0000000000..36f7f4e1bc --- /dev/null +++ b/packages/create-wagmi/templates/vite-react/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/packages/create-wagmi/templates/vite-vanilla/_env.local b/packages/create-wagmi/templates/vite-vanilla/_env.local new file mode 100644 index 0000000000..936f763762 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/_env.local @@ -0,0 +1 @@ +VITE_WC_PROJECT_ID= \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-vanilla/_gitignore b/packages/create-wagmi/templates/vite-vanilla/_gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/_gitignore @@ -0,0 +1,24 @@ +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/create-wagmi/templates/vite-vanilla/_npmrc b/packages/create-wagmi/templates/vite-vanilla/_npmrc new file mode 100644 index 0000000000..ca1e9d98b5 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/_npmrc @@ -0,0 +1 @@ +legacy-peer-deps = true \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-vanilla/index.html b/packages/create-wagmi/templates/vite-vanilla/index.html new file mode 100644 index 0000000000..e2028cc129 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/index.html @@ -0,0 +1,12 @@ + + + + + + Create Wagmi + + +
+ + + diff --git a/packages/create-wagmi/templates/vite-vanilla/package.json b/packages/create-wagmi/templates/vite-vanilla/package.json new file mode 100644 index 0000000000..e48954313c --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/package.json @@ -0,0 +1,24 @@ +{ + "name": "wagmi-core-vanilla-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@wagmi/connectors": "latest", + "@wagmi/core": "latest", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "viem": "latest" + }, + "devDependencies": { + "@wagmi/cli": "latest", + "buffer": "^6.0.3", + "typescript": "^5.8.3", + "vite": "^5.2.11" + } +} diff --git a/packages/create-wagmi/templates/vite-vanilla/src/main.ts b/packages/create-wagmi/templates/vite-vanilla/src/main.ts new file mode 100644 index 0000000000..f2b80f3459 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/src/main.ts @@ -0,0 +1,89 @@ +import { Buffer } from 'buffer' +import { connect, disconnect, reconnect, watchAccount } from '@wagmi/core' + +import './style.css' +import { config } from './wagmi' + +globalThis.Buffer = Buffer + +document.querySelector('#app')!.innerHTML = ` +
+
+

Account

+ +
+ status: +
+ addresses: +
+ chainId: +
+
+ +
+

Connect

+ ${config.connectors + .map( + (connector) => + ``, + ) + .join('')} +
+
+` + +setupApp(document.querySelector('#app')!) + +function setupApp(element: HTMLDivElement) { + const connectElement = element.querySelector('#connect') + const buttons = element.querySelectorAll('.connect') + for (const button of buttons) { + const connector = config.connectors.find( + (connector) => connector.uid === button.id, + )! + button.addEventListener('click', async () => { + try { + const errorElement = element.querySelector('#error') + if (errorElement) errorElement.remove() + await connect(config, { connector }) + } catch (error) { + const errorElement = document.createElement('div') + errorElement.id = 'error' + errorElement.innerText = (error as Error).message + connectElement?.appendChild(errorElement) + } + }) + } + + watchAccount(config, { + onChange(account) { + const accountElement = element.querySelector('#account')! + accountElement.innerHTML = ` +

Account

+
+ status: ${account.status} +
+ addresses: ${ + account.addresses ? JSON.stringify(account.addresses) : '' + } +
+ chainId: ${account.chainId ?? ''} +
+ ${ + account.status === 'connected' + ? `` + : '' + } + ` + + const disconnectButton = + element.querySelector('#disconnect') + if (disconnectButton) + disconnectButton.addEventListener('click', () => disconnect(config)) + }, + }) + + reconnect(config) + .then(() => {}) + .catch(() => {}) +} diff --git a/packages/create-wagmi/templates/vite-vanilla/src/style.css b/packages/create-wagmi/templates/vite-vanilla/src/style.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/src/style.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/packages/create-wagmi/templates/vite-vanilla/src/vite-env.d.ts b/packages/create-wagmi/templates/vite-vanilla/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/create-wagmi/templates/vite-vanilla/src/wagmi.ts b/packages/create-wagmi/templates/vite-vanilla/src/wagmi.ts new file mode 100644 index 0000000000..6baeff5293 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/src/wagmi.ts @@ -0,0 +1,16 @@ +import { coinbaseWallet, injected, walletConnect } from '@wagmi/connectors' +import { http, createConfig } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [ + injected(), + coinbaseWallet(), + walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }), + ], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/packages/create-wagmi/templates/vite-vanilla/tsconfig.json b/packages/create-wagmi/templates/vite-vanilla/tsconfig.json new file mode 100644 index 0000000000..75abdef265 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vanilla/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/packages/create-wagmi/templates/vite-vue/README.md b/packages/create-wagmi/templates/vite-vue/README.md new file mode 100644 index 0000000000..15f6f79541 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/README.md @@ -0,0 +1 @@ +This is a [Vite](https://vitejs.dev) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi). diff --git a/packages/create-wagmi/templates/vite-vue/_env.local b/packages/create-wagmi/templates/vite-vue/_env.local new file mode 100644 index 0000000000..936f763762 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/_env.local @@ -0,0 +1 @@ +VITE_WC_PROJECT_ID= \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-vue/_gitignore b/packages/create-wagmi/templates/vite-vue/_gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/_gitignore @@ -0,0 +1,24 @@ +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/create-wagmi/templates/vite-vue/_npmrc b/packages/create-wagmi/templates/vite-vue/_npmrc new file mode 100644 index 0000000000..ca1e9d98b5 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/_npmrc @@ -0,0 +1 @@ +legacy-peer-deps = true \ No newline at end of file diff --git a/packages/create-wagmi/templates/vite-vue/biome.json b/packages/create-wagmi/templates/vite-vue/biome.json new file mode 100644 index 0000000000..08eb8a26ab --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/biome.json @@ -0,0 +1,13 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 120 + }, + "linter": { + "enabled": true + }, + "organizeImports": { + "enabled": true + } +} diff --git a/packages/create-wagmi/templates/vite-vue/index.html b/packages/create-wagmi/templates/vite-vue/index.html new file mode 100644 index 0000000000..e2028cc129 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/index.html @@ -0,0 +1,12 @@ + + + + + + Create Wagmi + + +
+ + + diff --git a/packages/create-wagmi/templates/vite-vue/package.json b/packages/create-wagmi/templates/vite-vue/package.json new file mode 100644 index 0000000000..8d306e620c --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/package.json @@ -0,0 +1,24 @@ +{ + "name": "wagmi-vue-vite-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "lint": "biome check .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/vue-query": ">=5.45.0", + "@wagmi/vue": "latest", + "viem": "latest", + "vue": ">=3.4.21" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "buffer": "^6.0.3", + "vite": "^5.2.11", + "vue-tsc": "^2.0.6" + } +} diff --git a/packages/create-wagmi/templates/vite-vue/src/App.vue b/packages/create-wagmi/templates/vite-vue/src/App.vue new file mode 100644 index 0000000000..421aeb9a7b --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/App.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/create-wagmi/templates/vite-vue/src/components/Account.vue b/packages/create-wagmi/templates/vite-vue/src/components/Account.vue new file mode 100644 index 0000000000..33f1491dac --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/components/Account.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/create-wagmi/templates/vite-vue/src/components/Connect.vue b/packages/create-wagmi/templates/vite-vue/src/components/Connect.vue new file mode 100644 index 0000000000..93320448c0 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/components/Connect.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/create-wagmi/templates/vite-vue/src/main.ts b/packages/create-wagmi/templates/vite-vue/src/main.ts new file mode 100644 index 0000000000..820eed3722 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/main.ts @@ -0,0 +1,17 @@ +import { Buffer } from 'buffer' +import { VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { createApp } from 'vue' + +// `@coinbase-wallet/sdk` uses `Buffer` +globalThis.Buffer = Buffer + +import App from './App.vue' +import './style.css' +import { config } from './wagmi' + +const app = createApp(App) + +app.use(WagmiPlugin, { config }).use(VueQueryPlugin, {}) + +app.mount('#app') diff --git a/packages/create-wagmi/templates/vite-vue/src/style.css b/packages/create-wagmi/templates/vite-vue/src/style.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/style.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/packages/create-wagmi/templates/vite-vue/src/vite-env.d.ts b/packages/create-wagmi/templates/vite-vue/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/create-wagmi/templates/vite-vue/src/wagmi.ts b/packages/create-wagmi/templates/vite-vue/src/wagmi.ts new file mode 100644 index 0000000000..f0282e9490 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/src/wagmi.ts @@ -0,0 +1,25 @@ +import { http, createConfig, createStorage } from '@wagmi/vue' +import { mainnet, optimism, sepolia } from '@wagmi/vue/chains' +import { coinbaseWallet, walletConnect } from '@wagmi/vue/connectors' + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + walletConnect({ + projectId: import.meta.env.VITE_WC_PROJECT_ID, + }), + coinbaseWallet({ appName: 'Vite Vue Playground', darkMode: true }), + ], + storage: createStorage({ storage: localStorage, key: 'vite-vue' }), + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + }, +}) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/packages/create-wagmi/templates/vite-vue/tsconfig.json b/packages/create-wagmi/templates/vite-vue/tsconfig.json new file mode 100644 index 0000000000..9e03e60496 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/create-wagmi/templates/vite-vue/tsconfig.node.json b/packages/create-wagmi/templates/vite-vue/tsconfig.node.json new file mode 100644 index 0000000000..97ede7ee6f --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/create-wagmi/templates/vite-vue/vite.config.ts b/packages/create-wagmi/templates/vite-vue/vite.config.ts new file mode 100644 index 0000000000..c3fa62d865 --- /dev/null +++ b/packages/create-wagmi/templates/vite-vue/vite.config.ts @@ -0,0 +1,7 @@ +import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/packages/create-wagmi/tsconfig.build.json b/packages/create-wagmi/tsconfig.build.json new file mode 100644 index 0000000000..45ae2069ef --- /dev/null +++ b/packages/create-wagmi/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/create-wagmi/tsconfig.json b/packages/create-wagmi/tsconfig.json new file mode 100644 index 0000000000..bd33919ac3 --- /dev/null +++ b/packages/create-wagmi/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md new file mode 100644 index 0000000000..7dfc14098a --- /dev/null +++ b/packages/react/CHANGELOG.md @@ -0,0 +1,5037 @@ +# wagmi + +## 2.15.4 + +### Patch Changes + +- Updated dependencies [[`42b1fed58e9ac09da0f8ebf3e9271f98a707aaac`](https://github.com/wevm/wagmi/commit/42b1fed58e9ac09da0f8ebf3e9271f98a707aaac)]: + - @wagmi/connectors@5.8.3 + +## 2.15.3 + +### Patch Changes + +- Updated dependencies [[`29297a48af72b537173d948ccd2fe37d39914c66`](https://github.com/wevm/wagmi/commit/29297a48af72b537173d948ccd2fe37d39914c66), [`07370106d5fb6b8fe300992d93abf25b3d0eaf57`](https://github.com/wevm/wagmi/commit/07370106d5fb6b8fe300992d93abf25b3d0eaf57)]: + - @wagmi/core@2.17.2 + - @wagmi/connectors@5.8.2 + +## 2.15.2 + +### Patch Changes + +- [#4649](https://github.com/wevm/wagmi/pull/4649) [`01f64e64fa4f85cdd30023903f972f4f9023681f`](https://github.com/wevm/wagmi/commit/01f64e64fa4f85cdd30023903f972f4f9023681f) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` parameter to `getCapabilities`/`useCapabilities`. + +- Updated dependencies [[`01f64e64fa4f85cdd30023903f972f4f9023681f`](https://github.com/wevm/wagmi/commit/01f64e64fa4f85cdd30023903f972f4f9023681f)]: + - @wagmi/core@2.17.1 + - @wagmi/connectors@5.8.1 + +## 2.15.1 + +### Patch Changes + +- Updated dependencies [[`cc5517ff6880bb630f1b201930acc20dd1a0b451`](https://github.com/wevm/wagmi/commit/cc5517ff6880bb630f1b201930acc20dd1a0b451)]: + - @wagmi/connectors@5.8.0 + +## 2.15.0 + +### Minor Changes + +- [#4638](https://github.com/wevm/wagmi/pull/4638) [`799ee4d4b23c2ecd64e3f3668e67634e81939719`](https://github.com/wevm/wagmi/commit/799ee4d4b23c2ecd64e3f3668e67634e81939719) Thanks [@jxom](https://github.com/jxom)! - Stabilized EIP-5792 Actions & Hooks. + +### Patch Changes + +- Updated dependencies [[`88427b2bcd13ec375ef519e9ad1ccffef9f02a7b`](https://github.com/wevm/wagmi/commit/88427b2bcd13ec375ef519e9ad1ccffef9f02a7b), [`799ee4d4b23c2ecd64e3f3668e67634e81939719`](https://github.com/wevm/wagmi/commit/799ee4d4b23c2ecd64e3f3668e67634e81939719), [`3f8b2edc4f237cccff1009bcef03d51ca27a7324`](https://github.com/wevm/wagmi/commit/3f8b2edc4f237cccff1009bcef03d51ca27a7324)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.17.0 + +## 2.14.16 + +### Patch Changes + +- Updated dependencies [[`b59c024b23c69f5459b17390531207cfdf126ce4`](https://github.com/wevm/wagmi/commit/b59c024b23c69f5459b17390531207cfdf126ce4)]: + - @wagmi/connectors@5.7.12 + +## 2.14.15 + +### Patch Changes + +- [`a4bd0623eed28e3761a27295831a60ad835f0ee0`](https://github.com/wevm/wagmi/commit/a4bd0623eed28e3761a27295831a60ad835f0ee0) Thanks [@jxom](https://github.com/jxom)! - **Experimental (EIP-5792):** Updated `id` parameter to be optional on `useWaitForCallsStatus`. + +- Updated dependencies [[`a4bd0623eed28e3761a27295831a60ad835f0ee0`](https://github.com/wevm/wagmi/commit/a4bd0623eed28e3761a27295831a60ad835f0ee0)]: + - @wagmi/core@2.16.7 + - @wagmi/connectors@5.7.11 + +## 2.14.14 + +### Patch Changes + +- [#4586](https://github.com/wevm/wagmi/pull/4586) [`edf47477b2f6385a1c3ae01d36a8498c47f30a0b`](https://github.com/wevm/wagmi/commit/edf47477b2f6385a1c3ae01d36a8498c47f30a0b) Thanks [@jxom](https://github.com/jxom)! - **Experimental (EIP-5792):** Added `waitForCallsStatus` + `useWaitForCallsStatus`. + +- Updated dependencies [[`edf47477b2f6385a1c3ae01d36a8498c47f30a0b`](https://github.com/wevm/wagmi/commit/edf47477b2f6385a1c3ae01d36a8498c47f30a0b), [`e944812ebc234a72c1417b77cff341166f5e0fef`](https://github.com/wevm/wagmi/commit/e944812ebc234a72c1417b77cff341166f5e0fef)]: + - @wagmi/core@2.16.6 + - @wagmi/connectors@5.7.10 + +## 2.14.13 + +### Patch Changes + +- Updated dependencies [[`5b7101fddb61df56e34b2e02b46bc409e496eaf9`](https://github.com/wevm/wagmi/commit/5b7101fddb61df56e34b2e02b46bc409e496eaf9)]: + - @wagmi/connectors@5.7.9 + +## 2.14.12 + +### Patch Changes + +- [`d0c9a86921a4e939373cc6e763284e53f2a2e93c`](https://github.com/wevm/wagmi/commit/d0c9a86921a4e939373cc6e763284e53f2a2e93c) Thanks [@jxom](https://github.com/jxom)! - **Experimental (ERC-5792)**: Added support for `account: null` in `sendCalls` to cater for sending calls without a connected account (account will be filled by the wallet). + +- Updated dependencies [[`d0c9a86921a4e939373cc6e763284e53f2a2e93c`](https://github.com/wevm/wagmi/commit/d0c9a86921a4e939373cc6e763284e53f2a2e93c)]: + - @wagmi/core@2.16.5 + - @wagmi/connectors@5.7.8 + +## 2.14.11 + +### Patch Changes + +- [`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec) Thanks [@jxom](https://github.com/jxom)! - Updated `@coinbase/wallet-sdk`. + +- Updated dependencies [[`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec)]: + - @wagmi/connectors@5.7.7 + - @wagmi/core@2.16.4 + +## 2.14.10 + +### Patch Changes + +- Updated dependencies [[`639952c97f0fe3927106f42d3c9f7f366cdf7f7a`](https://github.com/wevm/wagmi/commit/639952c97f0fe3927106f42d3c9f7f366cdf7f7a), [`5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766`](https://github.com/wevm/wagmi/commit/5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766)]: + - @wagmi/connectors@5.7.6 + +## 2.14.9 + +### Patch Changes + +- Updated dependencies [[`a257e8d4f97431a4af872cda1817b4ae17c7bbed`](https://github.com/wevm/wagmi/commit/a257e8d4f97431a4af872cda1817b4ae17c7bbed)]: + - @wagmi/connectors@5.7.5 + +## 2.14.8 + +### Patch Changes + +- Updated dependencies [[`c8a257e0f6d2ece013b873895c35769a8a804fdc`](https://github.com/wevm/wagmi/commit/c8a257e0f6d2ece013b873895c35769a8a804fdc)]: + - @wagmi/connectors@5.7.4 + +## 2.14.7 + +### Patch Changes + +- [#4497](https://github.com/wevm/wagmi/pull/4497) [`00c144b21bac3f0797b683d8a4a81f7399c6e042`](https://github.com/wevm/wagmi/commit/00c144b21bac3f0797b683d8a4a81f7399c6e042) Thanks [@tmm](https://github.com/tmm)! - Bumped `use-sync-external-store` version for React 19. + +## 2.14.6 + +### Patch Changes + +- [#4480](https://github.com/wevm/wagmi/pull/4480) [`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8) Thanks [@RodeRickIsWatching](https://github.com/RodeRickIsWatching)! - Fixed invocation of default storage. + +- Updated dependencies [[`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8)]: + - @wagmi/core@2.16.3 + - @wagmi/connectors@5.7.3 + +## 2.14.5 + +### Patch Changes + +- [`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6) Thanks [@jxom](https://github.com/jxom)! - Fixed assignment in `getDefaultStorage`. + +- Updated dependencies [[`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6)]: + - @wagmi/core@2.16.2 + - @wagmi/connectors@5.7.2 + +## 2.14.4 + +### Patch Changes + +- Updated dependencies [[`9c8c35a3b829f2c58edcd3a29e2dcd99974d7470`](https://github.com/wevm/wagmi/commit/9c8c35a3b829f2c58edcd3a29e2dcd99974d7470), [`3892ebd21c06beef4b28ece4e70d2a38807bce6f`](https://github.com/wevm/wagmi/commit/3892ebd21c06beef4b28ece4e70d2a38807bce6f)]: + - @wagmi/connectors@5.7.1 + - @wagmi/core@2.16.1 + +## 2.14.3 + +### Patch Changes + +- Updated dependencies [[`e3f63a02c1f7d80481804584f262bc98dab0400d`](https://github.com/wevm/wagmi/commit/e3f63a02c1f7d80481804584f262bc98dab0400d)]: + - @wagmi/connectors@5.7.0 + +## 2.14.2 + +### Patch Changes + +- Updated dependencies [[`adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85`](https://github.com/wevm/wagmi/commit/adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85)]: + - @wagmi/connectors@5.6.2 + +## 2.14.1 + +### Patch Changes + +- Updated dependencies [[`987404f590c1d29ebb3cb68928f5e54aa032793d`](https://github.com/wevm/wagmi/commit/987404f590c1d29ebb3cb68928f5e54aa032793d)]: + - @wagmi/connectors@5.6.1 + +## 2.14.0 + +### Minor Changes + +- [#4453](https://github.com/wevm/wagmi/pull/4453) [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227) Thanks [@tmm](https://github.com/tmm)! - Added support to `useConnect` for custom `connector.connect` parameters. + +### Patch Changes + +- Updated dependencies [[`afea6b67822a7a2b96901ec851441d27ee0f7a52`](https://github.com/wevm/wagmi/commit/afea6b67822a7a2b96901ec851441d27ee0f7a52), [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227), [`8b0726c1106fce88b782e676498eabf0718b2619`](https://github.com/wevm/wagmi/commit/8b0726c1106fce88b782e676498eabf0718b2619)]: + - @wagmi/connectors@5.6.0 + - @wagmi/core@2.16.0 + +## 2.13.5 + +### Patch Changes + +- [#4447](https://github.com/wevm/wagmi/pull/4447) [`244f7777d9d227b3134d4cb9b9dda64f50cbddd3`](https://github.com/wevm/wagmi/commit/244f7777d9d227b3134d4cb9b9dda64f50cbddd3) Thanks [@Aerilym](https://github.com/Aerilym)! - Fixed config parameter passing in useSimulateContract and useEstimateGas + +## 2.13.4 + +### Patch Changes + +- [`2f79a3da4872d6158569017b1927a07a1ff5e7ba`](https://github.com/wevm/wagmi/commit/2f79a3da4872d6158569017b1927a07a1ff5e7ba) Thanks [@tmm](https://github.com/tmm)! - Exported `injected` and `mock`. + +## 2.13.3 + +### Patch Changes + +- [#4433](https://github.com/wevm/wagmi/pull/4433) [`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc) Thanks [@Aerilym](https://github.com/Aerilym)! - Bumped Metamask SDK version to `0.31.1`. + +- Updated dependencies [[`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc)]: + - @wagmi/connectors@5.5.3 + - @wagmi/core@2.15.2 + +## 2.13.2 + +### Patch Changes + +- Updated dependencies [[`e563ef69130a511fd6f3f72ed4cd4fbe1390541f`](https://github.com/wevm/wagmi/commit/e563ef69130a511fd6f3f72ed4cd4fbe1390541f)]: + - @wagmi/connectors@5.5.2 + +## 2.13.1 + +### Patch Changes + +- [`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `null` gas would accidentally pass through. + +- Updated dependencies [[`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693)]: + - @wagmi/core@2.15.1 + - @wagmi/connectors@5.5.1 + +## 2.13.0 + +### Minor Changes + +- [#4417](https://github.com/wevm/wagmi/pull/4417) [`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141) Thanks [@jxom](https://github.com/jxom)! - Removed simulation in `writeContract` & `sendTransaction`. + +### Patch Changes + +- Updated dependencies [[`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.15.0 + +## 2.12.33 + +### Patch Changes + +- Updated dependencies [[`7ca62b44cd997d48f92c2b81343726a5908aa00b`](https://github.com/wevm/wagmi/commit/7ca62b44cd997d48f92c2b81343726a5908aa00b)]: + - @wagmi/connectors@5.4.0 + +## 2.12.32 + +### Patch Changes + +- Updated dependencies [[`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3), [`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3)]: + - @wagmi/core@2.14.6 + - @wagmi/connectors@5.3.10 + +## 2.12.31 + +### Patch Changes + +- Updated dependencies [[`b12a04eeec985c48d2feac94b011d41fb29ca23e`](https://github.com/wevm/wagmi/commit/b12a04eeec985c48d2feac94b011d41fb29ca23e)]: + - @wagmi/connectors@5.3.9 + +## 2.12.30 + +### Patch Changes + +- Updated dependencies [[`6b9bbacdc7bffd44fc2165362a5e65fd434e7646`](https://github.com/wevm/wagmi/commit/6b9bbacdc7bffd44fc2165362a5e65fd434e7646), [`dac62dc99a0679fa632a0fae49873d6053d06b35`](https://github.com/wevm/wagmi/commit/dac62dc99a0679fa632a0fae49873d6053d06b35)]: + - @wagmi/core@2.14.5 + - @wagmi/connectors@5.3.8 + +## 2.12.29 + +### Patch Changes + +- Updated dependencies [[`e08681c81fbdf475213e2d0f4c5517d0abf4e743`](https://github.com/wevm/wagmi/commit/e08681c81fbdf475213e2d0f4c5517d0abf4e743)]: + - @wagmi/core@2.14.4 + - @wagmi/connectors@5.3.7 + +## 2.12.28 + +### Patch Changes + +- Updated dependencies [[`7558ff3133c11bc4c49473d08ee9a47eaa12df5b`](https://github.com/wevm/wagmi/commit/7558ff3133c11bc4c49473d08ee9a47eaa12df5b)]: + - @wagmi/connectors@5.3.6 + +## 2.12.27 + +### Patch Changes + +- Updated dependencies [[`cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7`](https://github.com/wevm/wagmi/commit/cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7), [`7fe78f2d09778fc01fd0cffe85ba198e64999275`](https://github.com/wevm/wagmi/commit/7fe78f2d09778fc01fd0cffe85ba198e64999275)]: + - @wagmi/core@2.14.3 + - @wagmi/connectors@5.3.5 + +## 2.12.26 + +### Patch Changes + +- Updated dependencies [[`b6861a4c378dab78d8751ae0ac2aa425f3c24b8f`](https://github.com/wevm/wagmi/commit/b6861a4c378dab78d8751ae0ac2aa425f3c24b8f), [`d0d0963bb5904a15cf0355862d62dd141ce0c31c`](https://github.com/wevm/wagmi/commit/d0d0963bb5904a15cf0355862d62dd141ce0c31c), [`ecac0ba36243d94c9199d0bd21937104c835d9a0`](https://github.com/wevm/wagmi/commit/ecac0ba36243d94c9199d0bd21937104c835d9a0)]: + - @wagmi/connectors@5.3.4 + - @wagmi/core@2.14.2 + +## 2.12.25 + +### Patch Changes + +- Updated dependencies [[`83c6d16b7d6dddfa6bda036e04f00ec313c6248c`](https://github.com/wevm/wagmi/commit/83c6d16b7d6dddfa6bda036e04f00ec313c6248c)]: + - @wagmi/connectors@5.3.3 + +## 2.12.24 + +### Patch Changes + +- [`a4c5389c1a299bd7acf9df4a0d607e2ced709280`](https://github.com/wevm/wagmi/commit/a4c5389c1a299bd7acf9df4a0d607e2ced709280) Thanks [@jxom](https://github.com/jxom)! - Exported `Transport` type (for inference). + +## 2.12.23 + +### Patch Changes + +- Updated dependencies [[`8970cc51398e1ac713435533096215c6d31ffdf9`](https://github.com/wevm/wagmi/commit/8970cc51398e1ac713435533096215c6d31ffdf9)]: + - @wagmi/connectors@5.3.2 + +## 2.12.22 + +### Patch Changes + +- Updated dependencies [[`052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702`](https://github.com/wevm/wagmi/commit/052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702), [`b250fc21ee577b2a75c5a34ff684f62fb4ad771a`](https://github.com/wevm/wagmi/commit/b250fc21ee577b2a75c5a34ff684f62fb4ad771a)]: + - @wagmi/core@2.14.1 + - @wagmi/connectors@5.3.1 + +## 2.12.21 + +### Patch Changes + +- Updated dependencies [[`f43e074f473820b208a6295d7c97f847332f1a1d`](https://github.com/wevm/wagmi/commit/f43e074f473820b208a6295d7c97f847332f1a1d)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.14.0 + +## 2.12.20 + +### Patch Changes + +- Updated dependencies [[`c05caabc20c3ced9682cfc7ba1f3f7dcfece0703`](https://github.com/wevm/wagmi/commit/c05caabc20c3ced9682cfc7ba1f3f7dcfece0703), [`5ae49af590ff168426c9c283d54c34ae5148fcd9`](https://github.com/wevm/wagmi/commit/5ae49af590ff168426c9c283d54c34ae5148fcd9), [`f3182b22e6e454d9bd74f1b940ef34431fd9555d`](https://github.com/wevm/wagmi/commit/f3182b22e6e454d9bd74f1b940ef34431fd9555d)]: + - @wagmi/core@2.13.9 + - @wagmi/connectors@5.2.2 + +## 2.12.19 + +### Patch Changes + +- Updated dependencies [[`91a40f2db08e3a91db421b8732a5511a1e6c88fd`](https://github.com/wevm/wagmi/commit/91a40f2db08e3a91db421b8732a5511a1e6c88fd)]: + - @wagmi/connectors@5.2.1 + +## 2.12.18 + +### Patch Changes + +- Updated dependencies [[`34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4`](https://github.com/wevm/wagmi/commit/34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4)]: + - @wagmi/connectors@5.2.0 + +## 2.12.17 + +### Patch Changes + +- Updated dependencies [[`3b2123664b7ac66848390739e855c3b9702ab60c`](https://github.com/wevm/wagmi/commit/3b2123664b7ac66848390739e855c3b9702ab60c)]: + - @wagmi/connectors@5.1.15 + +## 2.12.16 + +### Patch Changes + +- Updated dependencies [[`56f2482508f2ba71bd6b0295c70c6abca7101e57`](https://github.com/wevm/wagmi/commit/56f2482508f2ba71bd6b0295c70c6abca7101e57)]: + - @wagmi/connectors@5.1.14 + - @wagmi/core@2.13.8 + +## 2.12.15 + +### Patch Changes + +- [#4229](https://github.com/wevm/wagmi/pull/4229) [`c6b8efd26254c8e692b2b67286ed538fc183b992`](https://github.com/wevm/wagmi/commit/c6b8efd26254c8e692b2b67286ed538fc183b992) Thanks [@weilliao05621](https://github.com/weilliao05621)! - Stabilized `useAccount` return type object reference. + +## 2.12.14 + +### Patch Changes + +- Updated dependencies [[`be75c2d4ef636d7362420ab0a106bfdf63f5d1e6`](https://github.com/wevm/wagmi/commit/be75c2d4ef636d7362420ab0a106bfdf63f5d1e6)]: + - @wagmi/core@2.13.7 + - @wagmi/connectors@5.1.13 + +## 2.12.13 + +### Patch Changes + +- Updated dependencies [[`edcbf5d6fbe92f639bead800502edda9e0aa39f1`](https://github.com/wevm/wagmi/commit/edcbf5d6fbe92f639bead800502edda9e0aa39f1)]: + - @wagmi/core@2.13.6 + - @wagmi/connectors@5.1.12 + +## 2.12.12 + +### Patch Changes + +- Updated dependencies [[`82404c960e04c83e0bae6e1e12459ef9debf9554`](https://github.com/wevm/wagmi/commit/82404c960e04c83e0bae6e1e12459ef9debf9554), [`d07ad7f63a018256908a673d078aaf79e47ac703`](https://github.com/wevm/wagmi/commit/d07ad7f63a018256908a673d078aaf79e47ac703)]: + - @wagmi/connectors@5.1.11 + +## 2.12.11 + +### Patch Changes + +- [#4262](https://github.com/wevm/wagmi/pull/4262) [`8531f83db3a1fbb8202c3e426b7f85679f587a52`](https://github.com/wevm/wagmi/commit/8531f83db3a1fbb8202c3e426b7f85679f587a52) Thanks [@nezouse](https://github.com/nezouse)! - Added experimental actions entrypoint. + +## 2.12.10 + +### Patch Changes + +- [#4260](https://github.com/wevm/wagmi/pull/4260) [`969a208a110b760a13fd7263360320f52440a9b6`](https://github.com/wevm/wagmi/commit/969a208a110b760a13fd7263360320f52440a9b6) Thanks [@tmm](https://github.com/tmm)! - Fixed `useReadContract` deployless reads support. + +- [#4259](https://github.com/wevm/wagmi/pull/4259) [`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb) Thanks [@tmm](https://github.com/tmm)! - Disabled `useConnectorClient` and `useWalletClient` during reconnection if connector is not fully restored. + +- Updated dependencies [[`81de006e66121a18c61945c1f9b8426c83a5713c`](https://github.com/wevm/wagmi/commit/81de006e66121a18c61945c1f9b8426c83a5713c), [`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb)]: + - @wagmi/connectors@5.1.10 + - @wagmi/core@2.13.5 + +## 2.12.9 + +### Patch Changes + +- Updated dependencies [[`21bd0e473d374cbbd7a01bececa6022d529026ba`](https://github.com/wevm/wagmi/commit/21bd0e473d374cbbd7a01bececa6022d529026ba), [`5c89c6853e616437a3be2b019db895451fecfb3c`](https://github.com/wevm/wagmi/commit/5c89c6853e616437a3be2b019db895451fecfb3c)]: + - @wagmi/connectors@5.1.9 + +## 2.12.8 + +### Patch Changes + +- Updated dependencies [[`b580ad4edff1721e0b9d138cf5ae2ec74d2374c7`](https://github.com/wevm/wagmi/commit/b580ad4edff1721e0b9d138cf5ae2ec74d2374c7)]: + - @wagmi/connectors@5.1.8 + +## 2.12.7 + +### Patch Changes + +- Updated dependencies [[`91fd81a068789c5020e891f539bcad8f54a7a52f`](https://github.com/wevm/wagmi/commit/91fd81a068789c5020e891f539bcad8f54a7a52f)]: + - @wagmi/connectors@5.1.7 + +## 2.12.6 + +### Patch Changes + +- Updated dependencies [[`3168616298cbb6135d0ffda771cba4126e83eba8`](https://github.com/wevm/wagmi/commit/3168616298cbb6135d0ffda771cba4126e83eba8), [`d7608ef9a79459465dc8c06a2ab740465c881907`](https://github.com/wevm/wagmi/commit/d7608ef9a79459465dc8c06a2ab740465c881907)]: + - @wagmi/connectors@5.1.6 + +## 2.12.5 + +### Patch Changes + +- Updated dependencies [[`b4c8971788c70b09479946ecfa998cff2f1b3953`](https://github.com/wevm/wagmi/commit/b4c8971788c70b09479946ecfa998cff2f1b3953)]: + - @wagmi/core@2.13.4 + - @wagmi/connectors@5.1.5 + +## 2.12.4 + +### Patch Changes + +- Updated dependencies [[`871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4`](https://github.com/wevm/wagmi/commit/871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4)]: + - @wagmi/core@2.13.3 + - @wagmi/connectors@5.1.4 + +## 2.12.3 + +### Patch Changes + +- Updated dependencies [[`1b9b523fa9b9dfe839aecdf4b40caa9547d7e594`](https://github.com/wevm/wagmi/commit/1b9b523fa9b9dfe839aecdf4b40caa9547d7e594)]: + - @wagmi/core@2.13.2 + - @wagmi/connectors@5.1.3 + +## 2.12.2 + +### Patch Changes + +- Updated dependencies [[`abb490dac4f0f02f46cb0878e7ca9a0db6aada56`](https://github.com/wevm/wagmi/commit/abb490dac4f0f02f46cb0878e7ca9a0db6aada56), [`28e0e5c9a4f856583f9d36a807502bd51a0c6ec2`](https://github.com/wevm/wagmi/commit/28e0e5c9a4f856583f9d36a807502bd51a0c6ec2)]: + - @wagmi/connectors@5.1.2 + +## 2.12.1 + +### Patch Changes + +- Updated dependencies [[`07c1227f306d0efb9421d4bb77a774f92f5fcf45`](https://github.com/wevm/wagmi/commit/07c1227f306d0efb9421d4bb77a774f92f5fcf45)]: + - @wagmi/core@2.13.1 + - @wagmi/connectors@5.1.1 + +## 2.12.0 + +### Minor Changes + +- [#4162](https://github.com/wevm/wagmi/pull/4162) [`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0) Thanks [@jxom](https://github.com/jxom)! - Added functionality for consumer-defined RPC URLs (`config.transports`) to be propagated to the WalletConnect & MetaMask Connectors. + +### Patch Changes + +- Updated dependencies [[`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.13.0 + +## 2.11.3 + +### Patch Changes + +- [#4124](https://github.com/wevm/wagmi/pull/4124) [`26616462db2e0140025f22c505c4541cfecb9308`](https://github.com/wevm/wagmi/commit/26616462db2e0140025f22c505c4541cfecb9308) Thanks [@t0rbik](https://github.com/t0rbik)! - Updated `useConnectorClient` to be enabled when status is `'reconnecting'` or `'connected'` (previously was also enabled when status was `'connecting'`). + +## 2.11.2 + +### Patch Changes + +- Updated dependencies [[`5bc8c8877810b2eec24a829df87dce40a51e6f20`](https://github.com/wevm/wagmi/commit/5bc8c8877810b2eec24a829df87dce40a51e6f20), [`8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5`](https://github.com/wevm/wagmi/commit/8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5)]: + - @wagmi/core@2.12.2 + - @wagmi/connectors@5.0.26 + +## 2.11.1 + +### Patch Changes + +- [#4146](https://github.com/wevm/wagmi/pull/4146) [`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b) Thanks [@jxom](https://github.com/jxom)! - Updated `@safe-global/safe-apps-sdk` + `@safe-global/safe-apps-provider` dependencies. + +- Updated dependencies [[`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b)]: + - @wagmi/connectors@5.0.25 + - @wagmi/core@2.12.1 + +## 2.11.0 + +### Minor Changes + +- [#4128](https://github.com/wevm/wagmi/pull/4128) [`5581a810ef70308e99c6f8b630cd4bca59f64afc`](https://github.com/wevm/wagmi/commit/5581a810ef70308e99c6f8b630cd4bca59f64afc) Thanks [@dalechyn](https://github.com/dalechyn)! - Added `useWatchAsset` hook. + +### Patch Changes + +- Updated dependencies [[`5581a810ef70308e99c6f8b630cd4bca59f64afc`](https://github.com/wevm/wagmi/commit/5581a810ef70308e99c6f8b630cd4bca59f64afc)]: + - @wagmi/core@2.12.0 + - @wagmi/connectors@6.0.0 + +## 2.10.11 + +### Patch Changes + +- [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d) Thanks [@jxom](https://github.com/jxom)! - Updated `mipd` dependency. + +- Updated dependencies [[`b08013eaa9ce97c02f8a7128ea400e3da7ef74bb`](https://github.com/wevm/wagmi/commit/b08013eaa9ce97c02f8a7128ea400e3da7ef74bb), [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d)]: + - @wagmi/core@2.11.8 + - @wagmi/connectors@5.0.23 + +## 2.10.10 + +### Patch Changes + +- Updated dependencies [[`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e)]: + - @wagmi/connectors@5.0.22 + - @wagmi/core@2.11.7 + +## 2.10.9 + +### Patch Changes + +- [#4060](https://github.com/wevm/wagmi/pull/4060) [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad) Thanks [@dalechyn](https://github.com/dalechyn)! - Bumped Tanstack Query dependencies to fix typing issues between exported Wagmi query options and TanStack Query suspense query methods (due to [`direction` property in `QueryFunctionContext` being deprecated](https://github.com/TanStack/query/pull/7410)). + +- Updated dependencies [[`ff0760b5900114bcfdf420a9fba3cc278ac95afe`](https://github.com/wevm/wagmi/commit/ff0760b5900114bcfdf420a9fba3cc278ac95afe), [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad)]: + - @wagmi/connectors@5.0.21 + - @wagmi/core@2.11.6 + +## 2.10.8 + +### Patch Changes + +- Updated dependencies [[`43fa971d34cac57fa5a2898ad4d839b95d7af37c`](https://github.com/wevm/wagmi/commit/43fa971d34cac57fa5a2898ad4d839b95d7af37c)]: + - @wagmi/connectors@5.0.20 + +## 2.10.7 + +### Patch Changes + +- Updated dependencies [[`b7ad208030d9f2e3f89912ff76b16cdbd848feda`](https://github.com/wevm/wagmi/commit/b7ad208030d9f2e3f89912ff76b16cdbd848feda)]: + - @wagmi/connectors@5.0.19 + +## 2.10.6 + +### Patch Changes + +- Updated dependencies [[`44d24620c9e3957f3245d14d6a042736371df70b`](https://github.com/wevm/wagmi/commit/44d24620c9e3957f3245d14d6a042736371df70b)]: + - @wagmi/connectors@5.0.18 + +## 2.10.5 + +### Patch Changes + +- Updated dependencies [[`04f2b846b113f3d300d82c9fa75212f1805817c5`](https://github.com/wevm/wagmi/commit/04f2b846b113f3d300d82c9fa75212f1805817c5)]: + - @wagmi/core@2.11.5 + - @wagmi/connectors@5.0.17 + +## 2.10.4 + +### Patch Changes + +- Updated dependencies [[`9e8345cd56186b997b5e56deaa2cfc69b30d15f6`](https://github.com/wevm/wagmi/commit/9e8345cd56186b997b5e56deaa2cfc69b30d15f6), [`02c38c28d1aa0ad7a61c33775de603ed974c5c1b`](https://github.com/wevm/wagmi/commit/02c38c28d1aa0ad7a61c33775de603ed974c5c1b)]: + - @wagmi/core@2.11.4 + - @wagmi/connectors@5.0.16 + +## 2.10.3 + +### Patch Changes + +- Updated dependencies [[`8974e6269bb5d7bfaa90db0246bc7d13e8bff798`](https://github.com/wevm/wagmi/commit/8974e6269bb5d7bfaa90db0246bc7d13e8bff798)]: + - @wagmi/core@2.11.3 + - @wagmi/connectors@5.0.15 + +## 2.10.2 + +### Patch Changes + +- Updated dependencies [[`b4d9ef79deb554ee20fed6666a474be5e7cdd522`](https://github.com/wevm/wagmi/commit/b4d9ef79deb554ee20fed6666a474be5e7cdd522)]: + - @wagmi/core@2.11.2 + - @wagmi/connectors@5.0.14 + +## 2.10.1 + +### Patch Changes + +- Updated dependencies [[`9c862d8d63e3d692a22cef2a90782b74a9103f17`](https://github.com/wevm/wagmi/commit/9c862d8d63e3d692a22cef2a90782b74a9103f17)]: + - @wagmi/connectors@5.0.13 + - @wagmi/core@2.11.1 + +## 2.10.0 + +### Minor Changes + +- [#3816](https://github.com/wevm/wagmi/pull/3816) [`06bb598a7f04c7b167f5b7ff6d46bd15886a6a14`](https://github.com/wevm/wagmi/commit/06bb598a7f04c7b167f5b7ff6d46bd15886a6a14) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useDeployContract` hook. + +### Patch Changes + +- Updated dependencies [[`06bb598a7f04c7b167f5b7ff6d46bd15886a6a14`](https://github.com/wevm/wagmi/commit/06bb598a7f04c7b167f5b7ff6d46bd15886a6a14), [`24a45b269bd0214a29d6f82a84ac66ef8c3f3822`](https://github.com/wevm/wagmi/commit/24a45b269bd0214a29d6f82a84ac66ef8c3f3822)]: + - @wagmi/core@2.11.0 + - @wagmi/connectors@6.0.0 + +## 2.9.12 + +### Patch Changes + +- Updated dependencies [[`f2a7cefab96691ebed8b8e45ffde071c47b58dbe`](https://github.com/wevm/wagmi/commit/f2a7cefab96691ebed8b8e45ffde071c47b58dbe), [`f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5`](https://github.com/wevm/wagmi/commit/f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5), [`e3b124ce414b8fd1b2214e2c5a28dc72158a13d1`](https://github.com/wevm/wagmi/commit/e3b124ce414b8fd1b2214e2c5a28dc72158a13d1)]: + - @wagmi/core@2.10.6 + - @wagmi/connectors@5.0.11 + +## 2.9.11 + +### Patch Changes + +- Updated dependencies [[`560952acd4bfe33db6c7c07b35c613cef278677c`](https://github.com/wevm/wagmi/commit/560952acd4bfe33db6c7c07b35c613cef278677c)]: + - @wagmi/connectors@5.0.10 + +## 2.9.10 + +### Patch Changes + +- Updated dependencies [[`32cdd7b7dc5aff916c040628519562c3a99d418d`](https://github.com/wevm/wagmi/commit/32cdd7b7dc5aff916c040628519562c3a99d418d)]: + - @wagmi/connectors@5.0.9 + +## 2.9.9 + +### Patch Changes + +- Updated dependencies [[`c1952d1ff7f0a491dc88595a49159451b07b5621`](https://github.com/wevm/wagmi/commit/c1952d1ff7f0a491dc88595a49159451b07b5621)]: + - @wagmi/connectors@5.0.8 + +## 2.9.8 + +### Patch Changes + +- Updated dependencies [[`030c7c2cb380dfd67a2182f62e2aa7a6e1601898`](https://github.com/wevm/wagmi/commit/030c7c2cb380dfd67a2182f62e2aa7a6e1601898)]: + - @wagmi/core@2.10.5 + - @wagmi/connectors@5.0.7 + +## 2.9.7 + +### Patch Changes + +- Updated dependencies [[`51fde8a0433b4fff357c1a8d7e08b41b4c86c968`](https://github.com/wevm/wagmi/commit/51fde8a0433b4fff357c1a8d7e08b41b4c86c968)]: + - @wagmi/core@2.10.4 + - @wagmi/connectors@5.0.6 + +## 2.9.6 + +### Patch Changes + +- Updated dependencies [[`70dd28669dd8d2ce08217cd02e29a8fbba7a08d4`](https://github.com/wevm/wagmi/commit/70dd28669dd8d2ce08217cd02e29a8fbba7a08d4)]: + - @wagmi/connectors@5.0.5 + +## 2.9.5 + +### Patch Changes + +- Updated dependencies [[`be9e1b8a9818b92eb0654a20d9471e9e39329e7e`](https://github.com/wevm/wagmi/commit/be9e1b8a9818b92eb0654a20d9471e9e39329e7e)]: + - @wagmi/connectors@5.0.4 + +## 2.9.4 + +### Patch Changes + +- Updated dependencies [[`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c), [`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c)]: + - @wagmi/connectors@5.0.3 + - @wagmi/core@2.10.3 + +## 2.9.3 + +### Patch Changes + +- [`ec2f63f106fd468f28b43d3b88ab3e89aaf5e81a`](https://github.com/wevm/wagmi/commit/ec2f63f106fd468f28b43d3b88ab3e89aaf5e81a) Thanks [@tmm](https://github.com/tmm)! - Fixed `useSwitchChain` `chains` typing. + +## 2.9.2 + +### Patch Changes + +- [#3940](https://github.com/wevm/wagmi/pull/3940) [`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a) Thanks [@jxom](https://github.com/jxom)! - Fixed usage of `metaMask` connector in Vite environments. + +- Updated dependencies [[`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a)]: + - @wagmi/connectors@5.0.2 + - @wagmi/core@2.10.2 + +## 2.9.1 + +### Patch Changes + +- Bumped versions. + +- Updated dependencies []: + - @wagmi/connectors@5.0.1 + - @wagmi/core@2.10.1 + +## 2.9.0 + +### Minor Changes + +- [#3928](https://github.com/wevm/wagmi/pull/3928) [`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1) Thanks [@tmm](https://github.com/tmm)! - Updated the default Coinbase SDK in `coinbaseWallet` Connector to v4.x. + +### Patch Changes + +- Updated dependencies [[`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1), [`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1)]: + - @wagmi/connectors@5.0.0 + - @wagmi/core@2.10.0 + +## 2.8.8 + +### Patch Changes + +- [#3906](https://github.com/wevm/wagmi/pull/3906) [`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67) Thanks [@tmm](https://github.com/tmm)! - Added support for Vue. + +- Updated dependencies [[`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67)]: + - @wagmi/connectors@4.3.10 + - @wagmi/core@2.9.8 + +## 2.8.7 + +### Patch Changes + +- [#3924](https://github.com/wevm/wagmi/pull/3924) [`1f58734f88458e0f6adb05c99f0c90f36ab286b8`](https://github.com/wevm/wagmi/commit/1f58734f88458e0f6adb05c99f0c90f36ab286b8) Thanks [@jxom](https://github.com/jxom)! - Refactored `isChainsStale` logic in `walletConnect` connector. + +- Updated dependencies [[`1f58734f88458e0f6adb05c99f0c90f36ab286b8`](https://github.com/wevm/wagmi/commit/1f58734f88458e0f6adb05c99f0c90f36ab286b8)]: + - @wagmi/connectors@4.3.9 + - @wagmi/core@2.9.7 + +## 2.8.6 + +### Patch Changes + +- [#3917](https://github.com/wevm/wagmi/pull/3917) [`05948fdad5bb4a56b08916d45b3dec2cb1e5f55b`](https://github.com/wevm/wagmi/commit/05948fdad5bb4a56b08916d45b3dec2cb1e5f55b) Thanks [@jxom](https://github.com/jxom)! - Updated `@metamask/sdk`. + +- Updated dependencies [[`05948fdad5bb4a56b08916d45b3dec2cb1e5f55b`](https://github.com/wevm/wagmi/commit/05948fdad5bb4a56b08916d45b3dec2cb1e5f55b)]: + - @wagmi/connectors@4.3.8 + - @wagmi/core@2.9.6 + +## 2.8.5 + +### Patch Changes + +- [`4fecbbb66d0aacd03b8c62a6455d11a33cde8f85`](https://github.com/wevm/wagmi/commit/4fecbbb66d0aacd03b8c62a6455d11a33cde8f85) Thanks [@jxom](https://github.com/jxom)! - Fixed address comparison in `getConnectorClient`. + +- Updated dependencies [[`4fecbbb66d0aacd03b8c62a6455d11a33cde8f85`](https://github.com/wevm/wagmi/commit/4fecbbb66d0aacd03b8c62a6455d11a33cde8f85)]: + - @wagmi/core@2.9.5 + - @wagmi/connectors@4.3.7 + +## 2.8.4 + +### Patch Changes + +- Updated dependencies [[`e6139a97c4b8804d734b1547b5e3921ce01fbe24`](https://github.com/wevm/wagmi/commit/e6139a97c4b8804d734b1547b5e3921ce01fbe24)]: + - @wagmi/core@2.9.4 + - @wagmi/connectors@4.3.6 + +## 2.8.3 + +### Patch Changes + +- [#3904](https://github.com/wevm/wagmi/pull/3904) [`addca28ebc20f1a4367c35fe9ef786decff9c87e`](https://github.com/wevm/wagmi/commit/addca28ebc20f1a4367c35fe9ef786decff9c87e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +- Updated dependencies [[`addca28ebc20f1a4367c35fe9ef786decff9c87e`](https://github.com/wevm/wagmi/commit/addca28ebc20f1a4367c35fe9ef786decff9c87e)]: + - @wagmi/connectors@4.3.5 + - @wagmi/core@2.9.3 + +## 2.8.2 + +### Patch Changes + +- [#3902](https://github.com/wevm/wagmi/pull/3902) [`204b7b624612405500ec098fb9e35facd3f74ca4`](https://github.com/wevm/wagmi/commit/204b7b624612405500ec098fb9e35facd3f74ca4) Thanks [@jxom](https://github.com/jxom)! - Made third-party SDK imports type-only. + +- Updated dependencies [[`204b7b624612405500ec098fb9e35facd3f74ca4`](https://github.com/wevm/wagmi/commit/204b7b624612405500ec098fb9e35facd3f74ca4)]: + - @wagmi/connectors@4.3.4 + - @wagmi/core@2.9.2 + +## 2.8.1 + +### Patch Changes + +- [`cda6a5d5`](https://github.com/wevm/wagmi/commit/cda6a5d56328330fbde050b4ef40b01c58d2519a) Thanks [@jxom](https://github.com/jxom)! - Updated packages. + +- Updated dependencies [[`cda6a5d5`](https://github.com/wevm/wagmi/commit/cda6a5d56328330fbde050b4ef40b01c58d2519a)]: + - @wagmi/core@2.9.1 + - @wagmi/connectors@4.3.3 + +## 2.8.0 + +### Minor Changes + +- [#3878](https://github.com/wevm/wagmi/pull/3878) [`017828fc`](https://github.com/wevm/wagmi/commit/017828fc027c7a84b54ea9d627e9389f4d60d6c2) Thanks [@jxom](https://github.com/jxom)! - Added experimental EIP-5792 Actions & Hooks. + +### Patch Changes + +- Updated dependencies [[`017828fc`](https://github.com/wevm/wagmi/commit/017828fc027c7a84b54ea9d627e9389f4d60d6c2)]: + - @wagmi/core@2.9.0 + - @wagmi/connectors@4.3.2 + +## 2.7.1 + +### Patch Changes + +- [#3869](https://github.com/wevm/wagmi/pull/3869) [`d4a78eb0`](https://github.com/wevm/wagmi/commit/d4a78eb07119d2e5617e52481ac7d6c6d1583ddc) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `prepareTransactionRequest` would internally call unsupported wallet RPC methods. + +- Updated dependencies [[`d4a78eb0`](https://github.com/wevm/wagmi/commit/d4a78eb07119d2e5617e52481ac7d6c6d1583ddc)]: + - @wagmi/core@2.8.1 + - @wagmi/connectors@4.3.1 + +## 2.7.0 + +### Minor Changes + +- [#3868](https://github.com/wevm/wagmi/pull/3868) [`c2af20b8`](https://github.com/wevm/wagmi/commit/c2af20b88cf16970d087faaec10b463357a5836e) Thanks [@jxom](https://github.com/jxom)! - Added `supportsSimulation` property to connectors that indicates if the connector's wallet supports contract simulation. + +### Patch Changes + +- [#3858](https://github.com/wevm/wagmi/pull/3858) [`0d141f17`](https://github.com/wevm/wagmi/commit/0d141f171d6ec44bcbfc9c876565b5e2fb8af6de) Thanks [@yulafezmesi](https://github.com/yulafezmesi)! - Fixed accessing reverted reason property inside `waitForTransactionReceipt`. + +- Updated dependencies [[`0d141f17`](https://github.com/wevm/wagmi/commit/0d141f171d6ec44bcbfc9c876565b5e2fb8af6de), [`c2af20b8`](https://github.com/wevm/wagmi/commit/c2af20b88cf16970d087faaec10b463357a5836e)]: + - @wagmi/core@2.8.0 + - @wagmi/connectors@5.0.0 + +## 2.6.0 + +### Minor Changes + +- [#3857](https://github.com/wevm/wagmi/pull/3857) [`d4274c03`](https://github.com/wevm/wagmi/commit/d4274c03a6af5f2d26d31432016ebc14950a330e) Thanks [@tmm](https://github.com/tmm)! - Added `addEthereumChainParameter` to `switchChain`-related methods. + +### Patch Changes + +- Updated dependencies [[`d4274c03`](https://github.com/wevm/wagmi/commit/d4274c03a6af5f2d26d31432016ebc14950a330e), [`4781a405`](https://github.com/wevm/wagmi/commit/4781a4056d4ffc2c74f96a75429e9b2cd2417ad8), [`400c960b`](https://github.com/wevm/wagmi/commit/400c960b30d701c134850c695ae903a382c29b5b)]: + - @wagmi/connectors@5.0.0 + - @wagmi/core@2.7.0 + +## 2.5.22 + +### Patch Changes + +- [`e3c832a1`](https://github.com/wevm/wagmi/commit/e3c832a12c301f9b0ee129d877b3101d220ba8b2) Thanks [@jxom](https://github.com/jxom)! - Fixed undefined `navigator` issue in MetaMask connector. + +- Updated dependencies [[`e3c832a1`](https://github.com/wevm/wagmi/commit/e3c832a12c301f9b0ee129d877b3101d220ba8b2)]: + - @wagmi/connectors@4.1.28 + - @wagmi/core@2.6.19 + +## 2.5.21 + +### Patch Changes + +- [#3848](https://github.com/wevm/wagmi/pull/3848) [`dd40a41c`](https://github.com/wevm/wagmi/commit/dd40a41c526ab60a288aff2250ed8dba92a27b16) Thanks [@jxom](https://github.com/jxom)! - Updated MetaMask SDK. + +- Updated dependencies [[`dd40a41c`](https://github.com/wevm/wagmi/commit/dd40a41c526ab60a288aff2250ed8dba92a27b16)]: + - @wagmi/connectors@4.1.27 + - @wagmi/core@2.6.18 + +## 2.5.20 + +### Patch Changes + +- [#3822](https://github.com/wevm/wagmi/pull/3822) [`a97bfbae`](https://github.com/wevm/wagmi/commit/a97bfbaeb615cfef04665e5e7348d85d17f960f0) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where Wagmi would not correctly rehydrate the active chain when a persisted store was being used. + +- Updated dependencies [[`a97bfbae`](https://github.com/wevm/wagmi/commit/a97bfbaeb615cfef04665e5e7348d85d17f960f0)]: + - @wagmi/core@2.6.17 + - @wagmi/connectors@4.1.26 + +## 2.5.19 + +### Patch Changes + +- [#3793](https://github.com/wevm/wagmi/pull/3793) [`f85b83ae`](https://github.com/wevm/wagmi/commit/f85b83ae95dd0bb73ffbdb49afa174e7c68298e1) Thanks [@tmm](https://github.com/tmm)! - Wired up `config` inside hooks so you can pass it explicitly and not use the `WagmiProvider`. + +- Updated dependencies [[`42ad380d`](https://github.com/wevm/wagmi/commit/42ad380d9a5d8bc0f61d73612142dea9d098de5e)]: + - @wagmi/connectors@4.1.25 + - @wagmi/core@2.6.16 + +## 2.5.18 + +### Patch Changes + +- Updated dependencies [[`b907d5ac`](https://github.com/wevm/wagmi/commit/b907d5ac3a746bcbccc06d1fe78c5bd8f9a7d685)]: + - @wagmi/core@2.6.15 + - @wagmi/connectors@4.1.24 + +## 2.5.17 + +### Patch Changes + +- [#3779](https://github.com/wevm/wagmi/pull/3779) [`3da20bb8`](https://github.com/wevm/wagmi/commit/3da20bb80e7c3efeef8227ced66ad615370fc242) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `eth_requestAccounts` would be called upon reconnect instead of `eth_accounts`. + +- [`a3d1858f`](https://github.com/wevm/wagmi/commit/a3d1858fce448d2b70e36ee692ef1589b74e9d3f) Thanks [@jxom](https://github.com/jxom)! - Fixed hydration conditional in `createConfig`. + +- Updated dependencies [[`b3b54ef1`](https://github.com/wevm/wagmi/commit/b3b54ef179c5fa0d1694d38d4b808549a0550409), [`3da20bb8`](https://github.com/wevm/wagmi/commit/3da20bb80e7c3efeef8227ced66ad615370fc242), [`a3d1858f`](https://github.com/wevm/wagmi/commit/a3d1858fce448d2b70e36ee692ef1589b74e9d3f)]: + - @wagmi/core@2.6.14 + - @wagmi/connectors@4.1.23 + +## 2.5.16 + +### Patch Changes + +- [`b80236dc`](https://github.com/wevm/wagmi/commit/b80236dc623095fe8f1e1d10957d7776fb6ab48b) Thanks [@jxom](https://github.com/jxom)! - Removed unneeded `uniqueBy` check on connectors state. + +- Updated dependencies [[`b80236dc`](https://github.com/wevm/wagmi/commit/b80236dc623095fe8f1e1d10957d7776fb6ab48b)]: + - @wagmi/core@2.6.13 + - @wagmi/connectors@4.1.22 + +## 2.5.15 + +### Patch Changes + +- [#3740](https://github.com/wevm/wagmi/pull/3740) [`3373c644`](https://github.com/wevm/wagmi/commit/3373c6444c38ef16532d18cfc351b3fe2bf2d351) Thanks [@BrickheadJohnny](https://github.com/BrickheadJohnny)! - Removed unnecessary re-renders from `useConnectorClient` and `useWalletClient`. + +- Updated dependencies [[`a59069e9`](https://github.com/wevm/wagmi/commit/a59069e9fab45dd606bb89a7f829fe94c51a5494), [`0acd3132`](https://github.com/wevm/wagmi/commit/0acd31320f534993af566be5490c2978b6184f66)]: + - @wagmi/core@2.6.12 + - @wagmi/connectors@4.1.21 + +## 2.5.14 + +### Patch Changes + +- [`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d) Thanks [@tmm](https://github.com/tmm)! - Deprecated `normalizeChainId`. Use `Number` instead. + +- Updated dependencies [[`e1ca4e63`](https://github.com/wevm/wagmi/commit/e1ca4e637ae6cec7f5902b0a2c0e0efc3b751a1d)]: + - @wagmi/connectors@4.1.20 + - @wagmi/core@2.6.11 + +## 2.5.13 + +### Patch Changes + +- [`dbdca8fd`](https://github.com/wevm/wagmi/commit/dbdca8fd14b90c166222a66a373c1b33c06ce019) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where duplicate connectors could be instantiated if injected after page mount. + +- Updated dependencies [[`dbdca8fd`](https://github.com/wevm/wagmi/commit/dbdca8fd14b90c166222a66a373c1b33c06ce019)]: + - @wagmi/core@2.6.10 + - @wagmi/connectors@4.1.19 + +## 2.5.12 + +### Patch Changes + +- [#3612](https://github.com/wevm/wagmi/pull/3612) [`97237bb0`](https://github.com/wevm/wagmi/commit/97237bb05c30860b9b12c094e82a38ce59d9bedf) Thanks [@m1heng](https://github.com/m1heng)! - Added missing `functionName` parameter to `useWriteContract` codegen helper. + +## 2.5.11 + +### Patch Changes + +- [#3714](https://github.com/wevm/wagmi/pull/3714) [`f1628d65`](https://github.com/wevm/wagmi/commit/f1628d65f06e9ef18e6c4e2eb4f6e3ab2e700924) Thanks [@dalechyn](https://github.com/dalechyn)! - Replaced `Omit` with `UnionOmit` for `UseMutationReturnType`. + +- [#3715](https://github.com/wevm/wagmi/pull/3715) [`d56edf4f`](https://github.com/wevm/wagmi/commit/d56edf4f27c52acc7a0f57114454b0d3e22cacd6) Thanks [@jxom](https://github.com/jxom)! - Fixed SSR hydration issues. + +- Updated dependencies [[`d56edf4f`](https://github.com/wevm/wagmi/commit/d56edf4f27c52acc7a0f57114454b0d3e22cacd6)]: + - @wagmi/core@2.6.9 + - @wagmi/connectors@4.1.18 + +## 2.5.10 + +### Patch Changes + +- [#3643](https://github.com/wevm/wagmi/pull/3643) [`e46bcd47`](https://github.com/wevm/wagmi/commit/e46bcd4738a18da15b53f6612b614379c1985374) Thanks [@TateB](https://github.com/TateB)! - Fixed race condition arising from `reconnect`. + +- Updated dependencies [[`e46bcd47`](https://github.com/wevm/wagmi/commit/e46bcd4738a18da15b53f6612b614379c1985374)]: + - @wagmi/core@2.6.8 + - @wagmi/connectors@4.1.17 + +## 2.5.9 + +### Patch Changes + +- [`f5648dd2`](https://github.com/wevm/wagmi/commit/f5648dd28b3576b628f57732b89287f55acbb1c1) Thanks [@jxom](https://github.com/jxom)! - Updated `prepareTransactionRequest` types for `viem@2.8.0`. + +- [`1c1fee6a`](https://github.com/wevm/wagmi/commit/1c1fee6ab8f01f7734ac6ce05093fa8e388beb3e) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/ethereum-provider`. + +- Updated dependencies [[`b479b5e8`](https://github.com/wevm/wagmi/commit/b479b5e8a5866cba792862f22e6352c4fb566137), [`f5648dd2`](https://github.com/wevm/wagmi/commit/f5648dd28b3576b628f57732b89287f55acbb1c1), [`1c1fee6a`](https://github.com/wevm/wagmi/commit/1c1fee6ab8f01f7734ac6ce05093fa8e388beb3e), [`88a2d744`](https://github.com/wevm/wagmi/commit/88a2d744a1315908c9e54156026df3ad2435ad44)]: + - @wagmi/core@2.6.7 + - @wagmi/connectors@4.1.16 + +## 2.5.8 + +### Patch Changes + +- Updated dependencies [[`a91c0b64`](https://github.com/wevm/wagmi/commit/a91c0b64ba8b3e6537a560e69724eb601f26af27)]: + - @wagmi/core@2.6.6 + - @wagmi/connectors@4.1.15 + +## 2.5.7 + +### Patch Changes + +- [#3580](https://github.com/wevm/wagmi/pull/3580) [`c677dcd2`](https://github.com/wevm/wagmi/commit/c677dcd245dccdf69289a3d66dded237b09570a2) Thanks [@tmm](https://github.com/tmm)! - Made `useSwitchChain().chains` reactive. + +- Updated dependencies [[`ca5decdb`](https://github.com/wevm/wagmi/commit/ca5decdb712f81e3f5dab933a94b967bca5b6af4), [`c677dcd2`](https://github.com/wevm/wagmi/commit/c677dcd245dccdf69289a3d66dded237b09570a2)]: + - @wagmi/connectors@4.1.14 + - @wagmi/core@2.6.5 + +## 2.5.6 + +### Patch Changes + +- Updated dependencies [[`7c6618e6`](https://github.com/wevm/wagmi/commit/7c6618e6a0eb1ff39cf8f66b34d3ddc14be538fe), [`fa25b448`](https://github.com/wevm/wagmi/commit/fa25b4482504b4d9729a5687ea6d6dc959265bc0), [`895f28e8`](https://github.com/wevm/wagmi/commit/895f28e873af7c8eda5ca85734ff67c8979fd950)]: + - @wagmi/core@2.6.4 + - @wagmi/connectors@4.1.13 + +## 2.5.5 + +### Patch Changes + +- Updated dependencies [[`9c3b85dd`](https://github.com/wevm/wagmi/commit/9c3b85dd0a9a4a593e1d7e029345275735330e32), [`2a72214a`](https://github.com/wevm/wagmi/commit/2a72214a2901d6b6ddd39f80238aa0bd4db670a7)]: + - @wagmi/core@2.6.3 + - @wagmi/connectors@4.1.12 + +## 2.5.4 + +### Patch Changes + +- [`3f8203bd`](https://github.com/wevm/wagmi/commit/3f8203bd77fcf6b6756640b5971d09741ae3853d) Thanks [@tmm](https://github.com/tmm)! - Fixed `useBlock` parameters passthrough to Viem. + +## 2.5.3 + +### Patch Changes + +- [#3518](https://github.com/wevm/wagmi/pull/3518) [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +- Updated dependencies [[`414eb048`](https://github.com/wevm/wagmi/commit/414eb048af492caac70c0e874dfc87c30702804a), [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d), [`338e857d`](https://github.com/wevm/wagmi/commit/338e857d8cb2fe85e13d9207bef14cada1c1962d)]: + - @wagmi/core@2.6.2 + - @wagmi/connectors@4.1.11 + +## 2.5.2 + +### Patch Changes + +- [#3433](https://github.com/wevm/wagmi/pull/3433) [`101a7dd1`](https://github.com/wevm/wagmi/commit/101a7dd131b0cae2dc25579ecab9044290efd37b) Thanks [@tmm](https://github.com/tmm)! - Fixed `useClient` and `usePublicClient` throwing when used with unconfigured `chainId`. + +- [#3510](https://github.com/wevm/wagmi/pull/3510) [`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where connectors returning multiple addresses didn't checksum correctly. + +- Updated dependencies [[`660ff80d`](https://github.com/wevm/wagmi/commit/660ff80d5b046967a446eba43ee54b8359a37d0d), [`101a7dd1`](https://github.com/wevm/wagmi/commit/101a7dd131b0cae2dc25579ecab9044290efd37b)]: + - @wagmi/connectors@4.1.10 + - @wagmi/core@2.6.1 + +## 2.5.1 + +### Patch Changes + +- [#3496](https://github.com/wevm/wagmi/pull/3496) [`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b) Thanks [@tmm](https://github.com/tmm)! - Bumped dependencies. + +- Updated dependencies [[`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b), [`ba7f8a75`](https://github.com/wevm/wagmi/commit/ba7f8a758efb07664c6e401b5e7e325e7c62341b)]: + - @wagmi/connectors@4.1.9 + - @wagmi/core@2.6.0 + +## 2.5.0 + +### Minor Changes + +- [#3461](https://github.com/wevm/wagmi/pull/3461) [`ca98041d`](https://github.com/wevm/wagmi/commit/ca98041d1b39893d90246929485f4db0d1c6f9f7) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useTransactionConfirmations` hook. + +### Patch Changes + +- Updated dependencies [[`ca98041d`](https://github.com/wevm/wagmi/commit/ca98041d1b39893d90246929485f4db0d1c6f9f7)]: + - @wagmi/core@2.5.0 + - @wagmi/connectors@4.1.8 + +## 2.4.0 + +### Minor Changes + +- [#3427](https://github.com/wevm/wagmi/pull/3427) [`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `usePrepareTransactionRequest` hook. + +### Patch Changes + +- Updated dependencies [[`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4), [`370f1b4a`](https://github.com/wevm/wagmi/commit/370f1b4a3f154d181acf381c31c2e7862e22c0e4)]: + - @wagmi/connectors@4.1.7 + - @wagmi/core@2.4.0 + +## 2.3.1 + +### Patch Changes + +- [#3476](https://github.com/wevm/wagmi/pull/3476) [`3be5bb7b`](https://github.com/wevm/wagmi/commit/3be5bb7b0b38646e12e6da5c762ef74dff66bcc2) Thanks [@jxom](https://github.com/jxom)! - Modified persist strategy to only store "critical" properties that are needed before hydration. + +- Updated dependencies [[`3be5bb7b`](https://github.com/wevm/wagmi/commit/3be5bb7b0b38646e12e6da5c762ef74dff66bcc2)]: + - @wagmi/core@2.3.1 + - @wagmi/connectors@4.1.6 + +## 2.3.0 + +### Minor Changes + +- [#3459](https://github.com/wevm/wagmi/pull/3459) [`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useEnsText` action. + +### Patch Changes + +- [#3467](https://github.com/wevm/wagmi/pull/3467) [`90ef39bb`](https://github.com/wevm/wagmi/commit/90ef39bb0f4ecb3c914d317875348e35ba0f4524) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where connectors that share the same provider instance could reconnect when they have never been connected before. + +- [`1cfb6e5a`](https://github.com/wevm/wagmi/commit/1cfb6e5a875e707abcee00dd5739e87da05e8c90) Thanks [@jxom](https://github.com/jxom)! - Bumped listener limit on WalletConnect connector. + +- Updated dependencies [[`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5), [`d950b666`](https://github.com/wevm/wagmi/commit/d950b666b56700ca039ce16cdfdf34564991e7f5), [`90ef39bb`](https://github.com/wevm/wagmi/commit/90ef39bb0f4ecb3c914d317875348e35ba0f4524), [`1cfb6e5a`](https://github.com/wevm/wagmi/commit/1cfb6e5a875e707abcee00dd5739e87da05e8c90)]: + - @wagmi/core@2.3.0 + - @wagmi/connectors@5.0.0 + +## 2.2.1 + +### Patch Changes + +- [#3443](https://github.com/wevm/wagmi/pull/3443) [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb) Thanks [@jmrossy](https://github.com/jmrossy)! - Bumped dependencies. + +- [#3447](https://github.com/wevm/wagmi/pull/3447) [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a) Thanks [@tmm](https://github.com/tmm)! - Fixed account typing. + +- Updated dependencies [[`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb), [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a), [`a02a26ad`](https://github.com/wevm/wagmi/commit/a02a26ad030d3afb78f744377d61b5c60b65d97a), [`007024a6`](https://github.com/wevm/wagmi/commit/007024a684ddbecf924cdc06dd6a8854fc3d5eeb)]: + - @wagmi/connectors@4.1.4 + - @wagmi/core@2.2.1 + +## 2.2.0 + +### Minor Changes + +- [#3434](https://github.com/wevm/wagmi/pull/3434) [`00bf10a4`](https://github.com/wevm/wagmi/commit/00bf10a428b0d1c5dac35ebf25b19571e033ac26) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useBytecode` and `useStorageAt` hooks. + +- [#3408](https://github.com/wevm/wagmi/pull/3408) [`fb6c4148`](https://github.com/wevm/wagmi/commit/fb6c4148d9e9e2fccfbe74c8f343b444dc68dec5) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useProof` hook. + +- [#3416](https://github.com/wevm/wagmi/pull/3416) [`64c073f6`](https://github.com/wevm/wagmi/commit/64c073f6c2720961e2d6aff986670b73dbfab9c3) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useTransactionReceipt` hook. + +### Patch Changes + +- Updated dependencies [[`00bf10a4`](https://github.com/wevm/wagmi/commit/00bf10a428b0d1c5dac35ebf25b19571e033ac26), [`64c073f6`](https://github.com/wevm/wagmi/commit/64c073f6c2720961e2d6aff986670b73dbfab9c3), [`fb6c4148`](https://github.com/wevm/wagmi/commit/fb6c4148d9e9e2fccfbe74c8f343b444dc68dec5)]: + - @wagmi/core@2.2.0 + - @wagmi/connectors@5.0.0 + +## 2.1.2 + +### Patch Changes + +- [#3407](https://github.com/wevm/wagmi/pull/3407) [`e00b8205`](https://github.com/wevm/wagmi/commit/e00b82058685751637edfa9a6b2d196a12549fe7) Thanks [@jxom](https://github.com/jxom)! - Added a prelude gas estimate check to `sendTransaction`/`useSendTransaction`. + +- Updated dependencies [[`e00b8205`](https://github.com/wevm/wagmi/commit/e00b82058685751637edfa9a6b2d196a12549fe7)]: + - @wagmi/core@2.1.2 + - @wagmi/connectors@4.1.2 + +## 2.1.1 + +### Patch Changes + +- [#3402](https://github.com/wevm/wagmi/pull/3402) [`64b82282`](https://github.com/wevm/wagmi/commit/64b82282c1e57e77c25aa0814673780e4d11edd4) Thanks [@Songkeys](https://github.com/Songkeys)! - Fixed SSR cookie support for cookies that have special characters, e.g. `=`. + +- [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37) Thanks [@tmm](https://github.com/tmm)! - Added note to `metaMask` connector. + +- Updated dependencies [[`64b82282`](https://github.com/wevm/wagmi/commit/64b82282c1e57e77c25aa0814673780e4d11edd4), [`ec0d8b41`](https://github.com/wevm/wagmi/commit/ec0d8b4112181fefb11025e436a94a6114761d37)]: + - @wagmi/core@2.1.1 + - @wagmi/connectors@4.1.1 + +## 2.1.0 + +### Minor Changes + +- [#3387](https://github.com/wevm/wagmi/pull/3387) [`c9cd302e`](https://github.com/wevm/wagmi/commit/c9cd302e1c65c980deaee2e12567c2a8ec08b399) Thanks [@marthendalnunes](https://github.com/marthendalnunes)! - Added `useCall` hook. + +### Patch Changes + +- Updated dependencies [[`c9cd302e`](https://github.com/wevm/wagmi/commit/c9cd302e1c65c980deaee2e12567c2a8ec08b399)]: + - @wagmi/core@2.1.0 + - @wagmi/connectors@5.0.0 + +## 2.0.3 + +### Patch Changes + +- [#3384](https://github.com/wevm/wagmi/pull/3384) [`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844) Thanks [@tmm](https://github.com/tmm)! - Fixed connectors not bubbling error when connecting with `chainId` and subsequent user rejection. + +- Updated dependencies [[`ee868c33`](https://github.com/wevm/wagmi/commit/ee868c3385dae511230b6ddcb5627c1293cc1844)]: + - @wagmi/connectors@4.0.2 + - @wagmi/core@2.0.2 + +## 2.0.2 + +### Patch Changes + +- [#3379](https://github.com/wevm/wagmi/pull/3379) [`30a186e5`](https://github.com/wevm/wagmi/commit/30a186e53d1135657d04f72f40d1c27186025370) Thanks [@tmm](https://github.com/tmm)! - Fixed `useConnect` error getting unset. + +## 2.0.1 + +### Major Changes + +- [#3333](https://github.com/wevm/wagmi/pull/3333) [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a) Thanks [@tmm](https://github.com/tmm)! - Wagmi 2.0 featuring: + + - Full TanStack Query support + queryKeys + - Connect multiple connectors + - Switch chains while disconnected + - EIP-6963 enabled + - Strongly typed chainId and chain properties + - Smaller bundle size + - Miscellaneous improvements and bug fixes + + [Breaking Changes & Migration Guide](https://wagmi.sh/react/guides/migrate-from-v1-to-v2) + +### Patch Changes + +- Updated dependencies [[`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a), [`b3a0baaa`](https://github.com/wevm/wagmi/commit/b3a0baaaee7decf750d376aab2502cd33ca4825a)]: + - @wagmi/connectors@4.0.0 + - @wagmi/core@2.0.0 + +## 1.4.13 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.13 + +## 1.4.12 + +### Patch Changes + +- [`53ca1f7e`](https://github.com/wevm/wagmi/commit/53ca1f7eb411d912e11fcce7e03bd61ed067959c) Thanks [@tmm](https://github.com/tmm)! - Removed LedgerConnector due to security vulnerability + +- Updated dependencies [[`53ca1f7e`](https://github.com/wevm/wagmi/commit/53ca1f7eb411d912e11fcce7e03bd61ed067959c)]: + - @wagmi/core@1.4.12 + +## 1.4.11 + +### Patch Changes + +- [#3299](https://github.com/wevm/wagmi/pull/3299) [`b02020b3`](https://github.com/wevm/wagmi/commit/b02020b3724e0228198f35817611bb063295906e) Thanks [@dasanra](https://github.com/dasanra)! - Fixed issue with [Safe SDK](https://github.com/wevm/viem/issues/579) by bumping `@safe-global/safe-apps-provider@0.18.1` + +- Updated dependencies [[`b02020b3`](https://github.com/wevm/wagmi/commit/b02020b3724e0228198f35817611bb063295906e)]: + - @wagmi/core@1.4.11 + +## 1.4.10 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.10 + +## 1.4.9 + +### Patch Changes + +- [#3276](https://github.com/wevm/wagmi/pull/3276) [`83223a06`](https://github.com/wevm/wagmi/commit/83223a0659e2f675d897a1d3374c7af752c16abf) Thanks [@glitch-txs](https://github.com/glitch-txs)! - Removed required namespaces from WalletConnect connector + +- Updated dependencies [[`83223a06`](https://github.com/wevm/wagmi/commit/83223a0659e2f675d897a1d3374c7af752c16abf)]: + - @wagmi/core@1.4.9 + +## 1.4.8 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.8 + +## 1.4.7 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.7 + +## 1.4.6 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.6 + +## 1.4.5 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.5 + +## 1.4.4 + +### Patch Changes + +- [#3125](https://github.com/wagmi-dev/wagmi/pull/3125) [`725e73fe`](https://github.com/wagmi-dev/wagmi/commit/725e73feb9143dbaa6d540bb76d2009cef29da0b) Thanks [@lukasrosario](https://github.com/lukasrosario)! - Fixed an issue where `dataSuffix` was not being passed down into viem's `simulateContract`, causing the data to be omitted from requests. + +- Updated dependencies [[`725e73fe`](https://github.com/wagmi-dev/wagmi/commit/725e73feb9143dbaa6d540bb76d2009cef29da0b)]: + - @wagmi/core@1.4.4 + +## 1.4.3 + +### Patch Changes + +- [#3076](https://github.com/wagmi-dev/wagmi/pull/3076) [`4c36831b`](https://github.com/wagmi-dev/wagmi/commit/4c36831b7aa44d03b5c0decf64dcd20faae28a67) Thanks [@jxom](https://github.com/jxom)! - Pass `chain` to viem `sendTransaction`/`writeContract`. + +- [#3006](https://github.com/wagmi-dev/wagmi/pull/3006) [`f2ddce23`](https://github.com/wagmi-dev/wagmi/commit/f2ddce23324aff0a91e066100918dac552dc3b4a) Thanks [@jxom](https://github.com/jxom)! - Changed `normalize` to a dynamic import. + +- Updated dependencies [[`4c36831b`](https://github.com/wagmi-dev/wagmi/commit/4c36831b7aa44d03b5c0decf64dcd20faae28a67), [`f2ddce23`](https://github.com/wagmi-dev/wagmi/commit/f2ddce23324aff0a91e066100918dac552dc3b4a)]: + - @wagmi/core@1.4.3 + +## 1.4.2 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.2 + +## 1.4.1 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.4.1 + +## 1.4.0 + +### Minor Changes + +- [#2956](https://github.com/wagmi-dev/wagmi/pull/2956) [`2abeb285`](https://github.com/wagmi-dev/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895) Thanks [@tmm](https://github.com/tmm)! - Replaced `@wagmi/chains` with `viem/chains`. + +### Patch Changes + +- Updated dependencies [[`2abeb285`](https://github.com/wagmi-dev/wagmi/commit/2abeb285674af3e539cc2550b1f5027b1eb0c895)]: + - @wagmi/core@1.4.0 + +## 1.3.11 + +### Patch Changes + +- [`557e6400`](https://github.com/wagmi-dev/wagmi/commit/557e6400b9cef3b2c5131739143956c37d7c934a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`557e6400`](https://github.com/wagmi-dev/wagmi/commit/557e6400b9cef3b2c5131739143956c37d7c934a)]: + - @wagmi/core@1.3.10 + +## 1.3.10 + +### Patch Changes + +- [`247c5d11`](https://github.com/wagmi-dev/wagmi/commit/247c5d113e83acf3a6894264c00d4b125d455107) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`247c5d11`](https://github.com/wagmi-dev/wagmi/commit/247c5d113e83acf3a6894264c00d4b125d455107)]: + - @wagmi/core@1.3.9 + +## 1.3.9 + +### Patch Changes + +- [#2741](https://github.com/wagmi-dev/wagmi/pull/2741) [`5b1453d9`](https://github.com/wagmi-dev/wagmi/commit/5b1453d95973ed51f1c235a919fffb707eab9b70) Thanks [@jxom](https://github.com/jxom)! - Updated references + +- Updated dependencies [[`5b1453d9`](https://github.com/wagmi-dev/wagmi/commit/5b1453d95973ed51f1c235a919fffb707eab9b70)]: + - @wagmi/core@1.3.8 + +## 1.3.8 + +### Patch Changes + +- [#2700](https://github.com/wagmi-dev/wagmi/pull/2700) [`30118e97`](https://github.com/wagmi-dev/wagmi/commit/30118e979b1b00302e035f31f58c15d1aed911d5) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`30118e97`](https://github.com/wagmi-dev/wagmi/commit/30118e979b1b00302e035f31f58c15d1aed911d5)]: + - @wagmi/core@1.3.7 + +## 1.3.7 + +### Patch Changes + +- [`7ad2fdb8`](https://github.com/wagmi-dev/wagmi/commit/7ad2fdb81c7734d0c8107670800c68390e3bad99) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`7ad2fdb8`](https://github.com/wagmi-dev/wagmi/commit/7ad2fdb81c7734d0c8107670800c68390e3bad99)]: + - @wagmi/core@1.3.6 + +## 1.3.6 + +### Patch Changes + +- [`aab63fc1`](https://github.com/wagmi-dev/wagmi/commit/aab63fc1f8949004573978ecd8574fada3360758) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`aab63fc1`](https://github.com/wagmi-dev/wagmi/commit/aab63fc1f8949004573978ecd8574fada3360758)]: + - @wagmi/core@1.3.5 + +## 1.3.5 + +### Patch Changes + +- [#2669](https://github.com/wagmi-dev/wagmi/pull/2669) [`db75c459`](https://github.com/wagmi-dev/wagmi/commit/db75c4593b9c46970dc9d3c96d7adafc76878fc3) Thanks [@llllvvuu](https://github.com/llllvvuu)! - Modified `useAccount` and `useNetwork` to be reactive of wagmi Config (`config`). + +## 1.3.4 + +### Patch Changes + +- [`b056f809`](https://github.com/wagmi-dev/wagmi/commit/b056f8095674d4addc6ecd09adf6001fe52e2b15) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `onConnect` was not being called when multiple instances of `useAccount` existed. + +- [`22246d98`](https://github.com/wagmi-dev/wagmi/commit/22246d9884277d28ccad6ca2d9529b96b67d47fc) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`22246d98`](https://github.com/wagmi-dev/wagmi/commit/22246d9884277d28ccad6ca2d9529b96b67d47fc)]: + - @wagmi/core@1.3.4 + +## 1.3.3 + +### Patch Changes + +- [`1946aa43`](https://github.com/wagmi-dev/wagmi/commit/1946aa43a65b684ef41b7b4c43c67bf29c13e854) Thanks [@jxom](https://github.com/jxom)! - Updated references + +- Updated dependencies [[`1946aa43`](https://github.com/wagmi-dev/wagmi/commit/1946aa43a65b684ef41b7b4c43c67bf29c13e854)]: + - @wagmi/core@1.3.3 + +## 1.3.2 + +### Patch Changes + +- [`e86d0940`](https://github.com/wagmi-dev/wagmi/commit/e86d09409bb20b64d24e1263abcf0291314f03c7) Thanks [@jxom](https://github.com/jxom)! - Updated references + +- Updated dependencies [[`e86d0940`](https://github.com/wagmi-dev/wagmi/commit/e86d09409bb20b64d24e1263abcf0291314f03c7)]: + - @wagmi/core@1.3.2 + +## 1.3.1 + +### Patch Changes + +- [`964042fa`](https://github.com/wagmi-dev/wagmi/commit/964042fa94d682977923c595820c58283fb9244a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`964042fa`](https://github.com/wagmi-dev/wagmi/commit/964042fa94d682977923c595820c58283fb9244a)]: + - @wagmi/core@1.3.1 + +## 1.3.0 + +### Minor Changes + +- [#2619](https://github.com/wagmi-dev/wagmi/pull/2619) [`0d79748c`](https://github.com/wagmi-dev/wagmi/commit/0d79748cec2b6ac2410ad2c9816cc662f2b70962) Thanks [@jxom](https://github.com/jxom)! - Updated references: + - Updated `@safe-global/safe-apps-sdk` to `^8.0.0` (the one with `viem` support) + +### Patch Changes + +- Updated dependencies [[`0d79748c`](https://github.com/wagmi-dev/wagmi/commit/0d79748cec2b6ac2410ad2c9816cc662f2b70962)]: + - @wagmi/core@1.3.0 + +## 1.2.2 + +### Patch Changes + +- [#2611](https://github.com/wagmi-dev/wagmi/pull/2611) [`6d1ed7a1`](https://github.com/wagmi-dev/wagmi/commit/6d1ed7a156729b4df5d66fef3ae9a8b5762a2d34) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`6d1ed7a1`](https://github.com/wagmi-dev/wagmi/commit/6d1ed7a156729b4df5d66fef3ae9a8b5762a2d34)]: + - @wagmi/core@1.2.2 + +## 1.2.1 + +### Patch Changes + +- [#2589](https://github.com/wagmi-dev/wagmi/pull/2589) [`9680c347`](https://github.com/wagmi-dev/wagmi/commit/9680c347476500d28ceca20d23eeaed7931cb6e0) Thanks [@jxom](https://github.com/jxom)! - Fixed `writeContract` parameters to be compatible with `prepareWriteContract`. + +- [#2587](https://github.com/wagmi-dev/wagmi/pull/2587) [`cfff9994`](https://github.com/wagmi-dev/wagmi/commit/cfff999459384ac644ff7e62f53a7b787cf37507) Thanks [@jxom](https://github.com/jxom)! - Updated references + +- Updated dependencies [[`9680c347`](https://github.com/wagmi-dev/wagmi/commit/9680c347476500d28ceca20d23eeaed7931cb6e0), [`cfff9994`](https://github.com/wagmi-dev/wagmi/commit/cfff999459384ac644ff7e62f53a7b787cf37507)]: + - @wagmi/core@1.2.1 + +## 1.2.0 + +### Minor Changes + +- [#2536](https://github.com/wagmi-dev/wagmi/pull/2536) [`85e9760a`](https://github.com/wagmi-dev/wagmi/commit/85e9760a140cb169ac6236d9466b96e2105dd193) Thanks [@tmm](https://github.com/tmm)! - Changed `Address` type import from ABIType to viem. + +### Patch Changes + +- [#2539](https://github.com/wagmi-dev/wagmi/pull/2539) [`96319c64`](https://github.com/wagmi-dev/wagmi/commit/96319c640b9d07b375821c08a5c213355d8c290b) Thanks [@jxom](https://github.com/jxom)! - Updated references + +- Updated dependencies [[`85e9760a`](https://github.com/wagmi-dev/wagmi/commit/85e9760a140cb169ac6236d9466b96e2105dd193), [`96319c64`](https://github.com/wagmi-dev/wagmi/commit/96319c640b9d07b375821c08a5c213355d8c290b)]: + - @wagmi/core@1.2.0 + +## 1.1.1 + +### Patch Changes + +- [`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787) Thanks [@jxom](https://github.com/jxom)! - Updated `viem` peer dependency. + +- [`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787), [`02b98a9f`](https://github.com/wagmi-dev/wagmi/commit/02b98a9f9b2c503a47af4a8967e0202b5db21787)]: + - @wagmi/core@1.1.1 + +## 1.1.0 + +### Minor Changes + +- [#2482](https://github.com/wagmi-dev/wagmi/pull/2482) [`8764b54a`](https://github.com/wagmi-dev/wagmi/commit/8764b54aab68020063946112e8fe52aff650c99c) Thanks [@tmm](https://github.com/tmm)! - Bumped minimum TypeScript version to v5.0.4. + +### Patch Changes + +- [#2471](https://github.com/wagmi-dev/wagmi/pull/2471) [`74099cef`](https://github.com/wagmi-dev/wagmi/commit/74099cefd922317641529f7881a4c8a740d62cbe) Thanks [@iuriiiurevich](https://github.com/iuriiiurevich)! - Added `keepPreviousData` prop to `useContractRead`. + +- [#2484](https://github.com/wagmi-dev/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated `abitype` to 0.8.7 + +- [#2484](https://github.com/wagmi-dev/wagmi/pull/2484) [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- [`01d4a6ed`](https://github.com/wagmi-dev/wagmi/commit/01d4a6ed53110712692599095d94f04dfb5b6e38) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useInvalidateOnBlock`'s `onBlock` was being called on every render. + +- Updated dependencies [[`8764b54a`](https://github.com/wagmi-dev/wagmi/commit/8764b54aab68020063946112e8fe52aff650c99c), [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09), [`3adf1f4f`](https://github.com/wagmi-dev/wagmi/commit/3adf1f4feab863cb7b5d52c81ad46f7e4eb56f09)]: + - @wagmi/core@1.1.0 + +## 1.0.9 + +### Patch Changes + +- [#2446](https://github.com/wagmi-dev/wagmi/pull/2446) [`899d8c06`](https://github.com/wagmi-dev/wagmi/commit/899d8c0698e6cc958ca8ad9ec586883edf20516e) Thanks [@iuriiiurevich](https://github.com/iuriiiurevich)! - Added `cancelRefetch: false` to `useInvalidateOnBlock`. + +## 1.0.8 + +### Patch Changes + +- [#2441](https://github.com/wagmi-dev/wagmi/pull/2441) [`326edee4`](https://github.com/wagmi-dev/wagmi/commit/326edee4bc85db84a7a4e3768e33785849ab8d8e) Thanks [@tmm](https://github.com/tmm)! - Fixed internal type issue + +- Updated dependencies [[`326edee4`](https://github.com/wagmi-dev/wagmi/commit/326edee4bc85db84a7a4e3768e33785849ab8d8e)]: + - @wagmi/core@1.0.8 + +## 1.0.7 + +### Patch Changes + +- [#2433](https://github.com/wagmi-dev/wagmi/pull/2433) [`54fcff5f`](https://github.com/wagmi-dev/wagmi/commit/54fcff5f02f6933bbbe045ee0c83c5a78b6bba49) Thanks [@jxom](https://github.com/jxom)! - Added ability to pass an `account` to `useContractWrite`/`usePrepareContractWrite`. + +- Updated dependencies [[`54fcff5f`](https://github.com/wagmi-dev/wagmi/commit/54fcff5f02f6933bbbe045ee0c83c5a78b6bba49)]: + - @wagmi/core@1.0.7 + +## 1.0.6 + +### Patch Changes + +- [`ca2e1e96`](https://github.com/wagmi-dev/wagmi/commit/ca2e1e96149b87a7dc42c9db07e1f1ad2bb02c4a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- [#2401](https://github.com/wagmi-dev/wagmi/pull/2401) [`0f9dc875`](https://github.com/wagmi-dev/wagmi/commit/0f9dc875e90cfdd7a2028e04b7204caf9ea313b2) Thanks [@jxom](https://github.com/jxom)! - Exposed `account` on `readContract`/`useContractRead`. + +- Updated dependencies [[`ca2e1e96`](https://github.com/wagmi-dev/wagmi/commit/ca2e1e96149b87a7dc42c9db07e1f1ad2bb02c4a), [`0f9dc875`](https://github.com/wagmi-dev/wagmi/commit/0f9dc875e90cfdd7a2028e04b7204caf9ea313b2)]: + - @wagmi/core@1.0.6 + +## 1.0.5 + +### Patch Changes + +- [`90e2b3b3`](https://github.com/wagmi-dev/wagmi/commit/90e2b3b39efe0585fe28645ac2264109be17362a) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`90e2b3b3`](https://github.com/wagmi-dev/wagmi/commit/90e2b3b39efe0585fe28645ac2264109be17362a)]: + - @wagmi/core@1.0.5 + +## 1.0.4 + +### Patch Changes + +- [#2344](https://github.com/wagmi-dev/wagmi/pull/2344) [`8a725458`](https://github.com/wagmi-dev/wagmi/commit/8a72545853ae1024acd9efd18c06142e8c6c5750) Thanks [@jxom](https://github.com/jxom)! - Added gas estimation back into `prepareSendTransaction`. + +- Updated dependencies [[`8a725458`](https://github.com/wagmi-dev/wagmi/commit/8a72545853ae1024acd9efd18c06142e8c6c5750)]: + - @wagmi/core@1.0.4 + +## 1.0.3 + +### Patch Changes + +- [#2338](https://github.com/wagmi-dev/wagmi/pull/2338) [`92bfdc2c`](https://github.com/wagmi-dev/wagmi/commit/92bfdc2c744539558ba93c95f140b46ad331cee4) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where synchronous switch chain behavior (WalletConnect v2) would encounter chain id race conditions in `watchWalletClient`. + +- Updated dependencies [[`92bfdc2c`](https://github.com/wagmi-dev/wagmi/commit/92bfdc2c744539558ba93c95f140b46ad331cee4)]: + - @wagmi/core@1.0.3 + +## 1.0.2 + +### Patch Changes + +- Updated dependencies [[`09a4fd38`](https://github.com/wevm/wagmi/commit/09a4fd38f44eb176797925fd85314be17b610cd4)]: + - @wagmi/core@1.0.2 + +## 1.0.1 + +### Patch Changes + +- [`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af) Thanks [@jxom](https://github.com/jxom)! - Downgraded abitype. + +- Updated dependencies [[`ea651cd7`](https://github.com/wevm/wagmi/commit/ea651cd7fc75b7866272605467db11fd6e1d81af)]: + - @wagmi/core@1.0.1 + +## 1.0.0 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`5be0655c`](https://github.com/wevm/wagmi/commit/5be0655c8e48b25d38009022461fbf611af54349) Thanks [@jxom](https://github.com/jxom)! - Released v1. Read [Migration Guide](https://next.wagmi.sh/react/migration-guide#1xx-breaking-changes). + +### Patch Changes + +- Updated dependencies [[`5be0655c`](https://github.com/wevm/wagmi/commit/5be0655c8e48b25d38009022461fbf611af54349)]: + - @wagmi/core@1.0.0 + +## 1.0.0-next.9 + +### Patch Changes + +- Fixed `useContractEvent` effect dependencies. + +## 1.0.0-next.8 + +### Patch Changes + +- Added "use client" banner + +## 1.0.0-next.7 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de) Thanks [@jxom](https://github.com/jxom)! - Added `config.setPublicClient` & `config.setWebSocketPublicClient` + +- Updated references. + +### Patch Changes + +- Updated dependencies [[`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de)]: + - @wagmi/core@1.0.0-next.7 + +## 1.0.0-next.6 + +### Major Changes + +- Added `config.setConnectors` + +### Patch Changes + +- Updated dependencies [[`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de)]: + - @wagmi/core@1.0.0-next.6 + +## 1.0.0-next.5 + +### Major Changes + +- [#2235](https://github.com/wevm/wagmi/pull/2235) [`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de) Thanks [@jxom](https://github.com/jxom)! - Added `config.setPublicClient` & `config.setWebSocketPublicClient` + +### Patch Changes + +- Updated dependencies [[`708b2ce2`](https://github.com/wevm/wagmi/commit/708b2ce26efa8d3d910806a97cea5171dabc65de)]: + - @wagmi/core@1.0.0-next.5 + +## 1.0.0-next.4 + +### Major Changes + +- Updated viem. + Removed `goerli` export from main entrypoint. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.0.0-next.4 + +## 1.0.0-next.3 + +### Major Changes + +- Updated references. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.0.0-next.3 + +## 1.0.0-next.2 + +### Major Changes + +- **Breaking:** Renamed `createClient` to `createConfig` +- **Breaking:** Renamed `useClient` to `useConfig` +- **Breaking:** Removed `request` as an argument to `usePrepareSendTransaction` & `useSendTransaction`. Arguments now belong on the root level of the Hook. + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.0.0-next.2 + +## 1.0.0-next.1 + +### Major Changes + +- updated viem + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@1.0.0-next.1 + +## 1.0.0-next.0 + +### Major Changes + +- [`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb) Thanks [@jxom](https://github.com/jxom)! - Released v1. + +### Patch Changes + +- Updated dependencies [[`a7dda00c`](https://github.com/wevm/wagmi/commit/a7dda00c5b546f8b2c42b527e4d9ac1b9e9ab1fb)]: + - @wagmi/core@1.0.0-next.0 + +## 0.12.13 + +### Patch Changes + +- [#2270](https://github.com/wevm/wagmi/pull/2270) [`6d1fa9df`](https://github.com/wevm/wagmi/commit/6d1fa9df790287729c3b33d4f01fd23c2f8153f1) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`6d1fa9df`](https://github.com/wevm/wagmi/commit/6d1fa9df790287729c3b33d4f01fd23c2f8153f1)]: + - @wagmi/core@0.10.11 + +## 0.12.12 + +### Patch Changes + +- [#2208](https://github.com/wevm/wagmi/pull/2208) [`cfc696d8`](https://github.com/wevm/wagmi/commit/cfc696d83c6f768a2e1a29c5197efeed7f1d40a1) Thanks [@bangtoven](https://github.com/bangtoven)! - Bumped references to apply coinbase wallet sdk updates + +- Updated dependencies [[`cfc696d8`](https://github.com/wevm/wagmi/commit/cfc696d83c6f768a2e1a29c5197efeed7f1d40a1)]: + - @wagmi/core@0.10.10 + +## 0.12.11 + +### Patch Changes + +- [#2203](https://github.com/wevm/wagmi/pull/2203) [`a4ca4b05`](https://github.com/wevm/wagmi/commit/a4ca4b05c5bd20c20c5d0741bfb18f2c798b9529) Thanks [@tmm](https://github.com/tmm)! - Downgraded abitype. + +## 0.12.10 + +### Patch Changes + +- [#2143](https://github.com/wevm/wagmi/pull/2143) [`26dc5326`](https://github.com/wevm/wagmi/commit/26dc53260fde1d3278018c0b20a6d48a093d9427) Thanks [@tmm](https://github.com/tmm)! - Exported Sepolia Chain. + +- [#2146](https://github.com/wevm/wagmi/pull/2146) [`21b6842e`](https://github.com/wevm/wagmi/commit/21b6842e8c296a0bbe71ebe0780d898abc4cf4a8) Thanks [@tmm](https://github.com/tmm)! - Bumped references + +- Updated dependencies [[`26dc5326`](https://github.com/wevm/wagmi/commit/26dc53260fde1d3278018c0b20a6d48a093d9427), [`21b6842e`](https://github.com/wevm/wagmi/commit/21b6842e8c296a0bbe71ebe0780d898abc4cf4a8)]: + - @wagmi/core@0.10.9 + +## 0.12.9 + +### Patch Changes + +- [#2120](https://github.com/wevm/wagmi/pull/2120) [`664c2b16`](https://github.com/wevm/wagmi/commit/664c2b1690bdce1ad7a619ac8f673c168dec6529) Thanks [@jxom](https://github.com/jxom)! - Bumped React Query & ABIType dependencies + +## 0.12.8 + +### Patch Changes + +- [#2099](https://github.com/wevm/wagmi/pull/2099) [`f1fee5b3`](https://github.com/wevm/wagmi/commit/f1fee5b30a1bd13b5e66118bf9cdc44b0dc003a1) Thanks [@jxom](https://github.com/jxom)! - Added chains: + + - `nexi` + - `polygonZkEvm` + - `xdc` + - `xdcTestnet` + +- [#2085](https://github.com/wevm/wagmi/pull/2085) [`7d64e3f5`](https://github.com/wevm/wagmi/commit/7d64e3f538a6149777bfa84ea9435769b2a7db58) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where multicall would not throw if the target chain was not configured on the wagmi client. + +- Updated dependencies [[`f1fee5b3`](https://github.com/wevm/wagmi/commit/f1fee5b30a1bd13b5e66118bf9cdc44b0dc003a1), [`7d64e3f5`](https://github.com/wevm/wagmi/commit/7d64e3f538a6149777bfa84ea9435769b2a7db58)]: + - @wagmi/core@0.10.8 + +## 0.12.7 + +### Patch Changes + +- [#2082](https://github.com/wevm/wagmi/pull/2082) [`2ccc8a25`](https://github.com/wevm/wagmi/commit/2ccc8a255e93f0a2bb7b22101656b3905ec59abd) Thanks [@jxom](https://github.com/jxom)! - Updated references. + +- Updated dependencies [[`2ccc8a25`](https://github.com/wevm/wagmi/commit/2ccc8a255e93f0a2bb7b22101656b3905ec59abd)]: + - @wagmi/core@0.10.7 + +## 0.12.6 + +### Patch Changes + +- [#2056](https://github.com/wevm/wagmi/pull/2056) [`944f6513`](https://github.com/wevm/wagmi/commit/944f6513adf09a6f0b3bd34f591d3bbd1f1ffd2e) Thanks [@tmm](https://github.com/tmm)! - Bumped references. + +- Updated dependencies [[`944f6513`](https://github.com/wevm/wagmi/commit/944f6513adf09a6f0b3bd34f591d3bbd1f1ffd2e)]: + - @wagmi/core@0.10.6 + +## 0.12.5 + +### Patch Changes + +- [#2053](https://github.com/wevm/wagmi/pull/2053) [`665df1bf`](https://github.com/wevm/wagmi/commit/665df1bf2afccb533102069def395e19fb7194dd) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where you add a new chain to MetaMask, but the switch after is rejected. + +- Updated dependencies [[`665df1bf`](https://github.com/wevm/wagmi/commit/665df1bf2afccb533102069def395e19fb7194dd)]: + - @wagmi/core@0.10.5 + +## 0.12.4 + +### Patch Changes + +- [#2046](https://github.com/wevm/wagmi/pull/2046) [`90d8e9b8`](https://github.com/wevm/wagmi/commit/90d8e9b87962b72c54311649537e91a953660f9b) Thanks [@tmm](https://github.com/tmm)! - Exported internal type. + +- Updated dependencies [[`90d8e9b8`](https://github.com/wevm/wagmi/commit/90d8e9b87962b72c54311649537e91a953660f9b)]: + - @wagmi/core@0.10.4 + +## 0.12.3 + +### Patch Changes + +- [#2039](https://github.com/wevm/wagmi/pull/2039) [`bac893ab`](https://github.com/wevm/wagmi/commit/bac893ab26012d4d8741c4f80e8b8813aee26f0c) Thanks [@tmm](https://github.com/tmm)! - Updated references. + +- [#2043](https://github.com/wevm/wagmi/pull/2043) [`49a58320`](https://github.com/wevm/wagmi/commit/49a58320ab5f1f13bc4de25abcc028c8335e98f0) Thanks [@tmm](https://github.com/tmm)! - Removed `InjectedConnector` `shimChainChangedDisconnect` shim (no longer necessary). + +- [#2042](https://github.com/wevm/wagmi/pull/2042) [`e7ac7afc`](https://github.com/wevm/wagmi/commit/e7ac7afccb005e8d208c78d55b1fec979b8522a6) Thanks [@tmm](https://github.com/tmm)! - Fixed exposed types that weren't passed down. + +- Updated dependencies [[`bac893ab`](https://github.com/wevm/wagmi/commit/bac893ab26012d4d8741c4f80e8b8813aee26f0c), [`49a58320`](https://github.com/wevm/wagmi/commit/49a58320ab5f1f13bc4de25abcc028c8335e98f0)]: + - @wagmi/core@0.10.3 + +## 0.12.2 + +### Patch Changes + +- [#2016](https://github.com/wevm/wagmi/pull/2016) [`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab) Thanks [@jxom](https://github.com/jxom)! - Added chains: + + - `boba` + - `chronos` + - `crossbell` + - `dfk` + - `dogechain` + - `flare` + - `flareTestnet` + - `klaytn` + - `scrollTestnet` + - `shardeumSphinx` + - `skaleCalypso` + - `skaleCalypsoTestnet` + - `skaleChaosTestnet` + - `skaleCryptoBlades` + - `skaleCryptoColosseum` + - `skaleEuropa` + - `skaleEuropaTestnet` + - `skaleExorde` + - `skaleHumanProtocol` + - `skaleNebula` + - `skaleNebulaTestnet` + - `skaleRazor` + - `skaleTitan` + - `skaleTitanTestnet` + - `songbird` + - `songbirdTestnet` + - `titan` + - `titanTestnet` + - `wanchain` + - `wanchainTestnet` + +- [#2016](https://github.com/wevm/wagmi/pull/2016) [`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab) Thanks [@jxom](https://github.com/jxom)! - Updated references/ submodule. + +- Updated dependencies [[`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab), [`06bf61de`](https://github.com/wevm/wagmi/commit/06bf61dee6d2920777bd9392491e6b7aedebe7ab)]: + - @wagmi/core@0.10.2 + +## 0.12.0 + +### Minor Changes + +- [#1902](https://github.com/wevm/wagmi/pull/1902) [`0994e896`](https://github.com/wevm/wagmi/commit/0994e8966349b8811db0a5886db3831dafc99245) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** Removed the `version` config option for `WalletConnectConnector`. + + `WalletConnectConnector` now uses WalletConnect v2 by default. WalletConnect v1 is now `WalletConnectLegacyConnector`. + + ### WalletConnect v2 + + ```diff + import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + + const connector = new WalletConnectConnector({ + options: { + - version: '2', + projectId: 'abc', + }, + }) + ``` + + ### WalletConnect v1 + + ```diff + -import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + +import { WalletConnectConnector } from 'wagmi/connectors/walletConnectLegacy' + + -const connector = new WalletConnectConnector({ + +const connector = new WalletConnectLegacyConnector({ + options: { + qrcode: true, + }, + }) + ``` + +### Patch Changes + +- Updated dependencies [[`0994e896`](https://github.com/wevm/wagmi/commit/0994e8966349b8811db0a5886db3831dafc99245)]: + - @wagmi/core@0.10.0 + +## 0.11.7 + +### Patch Changes + +- [#1907](https://github.com/wevm/wagmi/pull/1907) [`cc4e74ee`](https://github.com/wevm/wagmi/commit/cc4e74ee19665eccb3767052dab6ab956ff4e676) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - `baseGoerli` + - `harmonyOne` + - `polygonZkEvmTestnet` + +- Updated dependencies [[`cc4e74ee`](https://github.com/wevm/wagmi/commit/cc4e74ee19665eccb3767052dab6ab956ff4e676)]: + - @wagmi/core@0.9.7 + +## 0.11.6 + +### Patch Changes + +- [#1882](https://github.com/wevm/wagmi/pull/1882) [`282cc1b0`](https://github.com/wevm/wagmi/commit/282cc1b02003684d582cea411b11792a59c26fd0) Thanks [@tmm](https://github.com/tmm)! - Updated references. + +- Updated dependencies [[`282cc1b0`](https://github.com/wevm/wagmi/commit/282cc1b02003684d582cea411b11792a59c26fd0)]: + - @wagmi/core@0.9.6 + +## 0.11.5 + +### Patch Changes + +- [#1812](https://github.com/wevm/wagmi/pull/1812) [`c7fd7fbd`](https://github.com/wevm/wagmi/commit/c7fd7fbde6f6c69a3a9a4f89d948c4dfb1d22679) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - `filecoinCalibration` + - `moonbaseAlpha` + - `moonbeam` + - `moonriver` + +- Updated dependencies [[`c7fd7fbd`](https://github.com/wevm/wagmi/commit/c7fd7fbde6f6c69a3a9a4f89d948c4dfb1d22679)]: + - @wagmi/core@0.9.5 + +## 0.11.4 + +### Patch Changes + +- [#1679](https://github.com/wevm/wagmi/pull/1679) [`3cef111b`](https://github.com/wevm/wagmi/commit/3cef111b1e30120233d8754b33587cdf94aedd8f) Thanks [@aj-may](https://github.com/aj-may)! - Fixed `useAccount` `onConnect` and `onDisconnect` callbacks for React Strict Mode. + +- [#1786](https://github.com/wevm/wagmi/pull/1786) [`b173a431`](https://github.com/wevm/wagmi/commit/b173a43165c7925a4e56ce1e0327a31917e7edc5) Thanks [@tmm](https://github.com/tmm)! - Locked ethers peer dependency version to >=5.5.1 <6 + +- [#1787](https://github.com/wevm/wagmi/pull/1787) [`f023fd8f`](https://github.com/wevm/wagmi/commit/f023fd8f66befb78b9a4df5ca971ceaa64e37ab4) Thanks [@tmm](https://github.com/tmm)! - Added `SafeConnector` + +- Updated dependencies [[`b173a431`](https://github.com/wevm/wagmi/commit/b173a43165c7925a4e56ce1e0327a31917e7edc5), [`f023fd8f`](https://github.com/wevm/wagmi/commit/f023fd8f66befb78b9a4df5ca971ceaa64e37ab4)]: + - @wagmi/core@0.9.4 + +## 0.11.3 + +### Patch Changes + +- [#1773](https://github.com/wevm/wagmi/pull/1773) [`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c) Thanks [@jxom](https://github.com/jxom)! - Updated `@walletconnect/universal-provider` on `WalletConnectConnector` v2. + Added more signable methods to `WalletConnectConnector` v2. + +- [#1773](https://github.com/wevm/wagmi/pull/1773) [`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c) Thanks [@jxom](https://github.com/jxom)! - Added Telos to the `wagmi/chains` entrypoint. Thanks @donnyquixotic! + +- Updated dependencies [[`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c), [`9aaf1955`](https://github.com/wevm/wagmi/commit/9aaf195514d3b5f4d085c797fc5021d42a9efb6c)]: + - @wagmi/core@0.9.3 + +## 0.11.2 + +### Patch Changes + +- [#1756](https://github.com/wevm/wagmi/pull/1756) [`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d) Thanks [@jxom](https://github.com/jxom)! - Added OKC Chain. Thanks @clark-cui! + +- [#1756](https://github.com/wevm/wagmi/pull/1756) [`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d) Thanks [@jxom](https://github.com/jxom)! - Fixed race condition between `switchNetwork` and mutation Actions that use `chainId` (e.g. `sendTransaction`). Thanks @DanInTheD4rk! + +- Updated dependencies [[`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d), [`31d06b8c`](https://github.com/wevm/wagmi/commit/31d06b8ce1e7af5e9d1a7ba57f1743b2dff7a53d)]: + - @wagmi/core@0.9.2 + +## 0.11.1 + +### Patch Changes + +- [#1752](https://github.com/wevm/wagmi/pull/1752) [`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4) Thanks [@jxom](https://github.com/jxom)! - Improved `WalletConnectConnector` (v2) initialization & updated dependencies. + +- [#1752](https://github.com/wevm/wagmi/pull/1752) [`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - Aurora – thanks @salil-naik + - Bronos – thanks @chedetinaveen + - Canto – thanks @tster + - Celo – thanks @aaronmgdr + +- Updated dependencies [[`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4), [`144a0e76`](https://github.com/wevm/wagmi/commit/144a0e76ef4bb9ba0650b5ffb9c63f95329819a4)]: + - @wagmi/core@0.9.1 + +## 0.11.0 + +### Minor Changes + +- [#1732](https://github.com/wevm/wagmi/pull/1732) [`01e21897`](https://github.com/wevm/wagmi/commit/01e2189747a5c22dc758c6d719b4145adc2a643c) Thanks [@tmm](https://github.com/tmm)! - Bumped minimum TypeScript version to typescript@>=4.9.4. TypeScript 5.0 is coming soon and has some great features we are excited to bring into wagmi. To prepare for this, update your TypeScript version to 4.9.4 or higher. There are likely no [breaking changes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#correctness-fixes-and-breaking-changes) if you are coming from typescript@4.7.x || typescript@4.8.x. + +### Patch Changes + +- Updated dependencies [[`01e21897`](https://github.com/wevm/wagmi/commit/01e2189747a5c22dc758c6d719b4145adc2a643c)]: + - @wagmi/core@0.9.0 + +## 0.10.15 + +### Patch Changes + +- [#1718](https://github.com/wevm/wagmi/pull/1718) [`e62b5ef8`](https://github.com/wevm/wagmi/commit/e62b5ef8aaa8063abb5264790768899ea35bbd31) Thanks [@tmm](https://github.com/tmm)! - Updated references + +- Updated dependencies [[`e62b5ef8`](https://github.com/wevm/wagmi/commit/e62b5ef8aaa8063abb5264790768899ea35bbd31)]: + - @wagmi/core@0.8.19 + +## 0.10.14 + +### Patch Changes + +- [#1708](https://github.com/wevm/wagmi/pull/1708) [`07fc3801`](https://github.com/wevm/wagmi/commit/07fc3801fa13c2cb5f7cf9b86ba8320b05a6a135) Thanks [@jxom](https://github.com/jxom)! - Updated `references/` submodule. + +- Updated dependencies [[`07fc3801`](https://github.com/wevm/wagmi/commit/07fc3801fa13c2cb5f7cf9b86ba8320b05a6a135)]: + - @wagmi/core@0.8.18 + +## 0.10.13 + +### Patch Changes + +- [#1705](https://github.com/wevm/wagmi/pull/1705) [`9ff797dc`](https://github.com/wevm/wagmi/commit/9ff797dcb979dc86b798a432b74c98598165430d) Thanks [@jxom](https://github.com/jxom)! - Added the following chains to the `wagmi/chains` entrypoint: + + - `crossbell` (thanks @Songkeys) + - `filecoin` & `filecoinHyperspace` (thanks @neil0x46dc) + - `gnosisChiado` (thanks @theNvN) + - `metis` & `metisGoerli` (thanks @CookedCookee) + +- Updated dependencies [[`9ff797dc`](https://github.com/wevm/wagmi/commit/9ff797dcb979dc86b798a432b74c98598165430d)]: + - @wagmi/core@0.8.17 + +## 0.10.12 + +### Patch Changes + +- [#1699](https://github.com/wevm/wagmi/pull/1699) [`2f1e7950`](https://github.com/wevm/wagmi/commit/2f1e7950e55550d9b50ef5ccb97cb609f4af39b1) Thanks [@tmm](https://github.com/tmm)! - Added public RPC URL property to Chain + +- Updated dependencies [[`2f1e7950`](https://github.com/wevm/wagmi/commit/2f1e7950e55550d9b50ef5ccb97cb609f4af39b1)]: + - @wagmi/core@0.8.16 + +## 0.10.11 + +### Patch Changes + +- [#1685](https://github.com/wevm/wagmi/pull/1685) [`917f5bc1`](https://github.com/wevm/wagmi/commit/917f5bc1fad578e35a8c6ee787e339bfdc156bab) Thanks [@jxom](https://github.com/jxom)! - Replaced qrcodemodal with web3modal for the WalletConnect v2 Connector. + +- Updated dependencies [[`917f5bc1`](https://github.com/wevm/wagmi/commit/917f5bc1fad578e35a8c6ee787e339bfdc156bab)]: + - @wagmi/core@0.8.15 + +## 0.10.10 + +### Patch Changes + +- [#1648](https://github.com/wevm/wagmi/pull/1648) [`a2db9170`](https://github.com/wevm/wagmi/commit/a2db91709720161cd70eeb5e84dd78433264f0a3) Thanks [@tmm](https://github.com/tmm)! - Exported internal type. + +## 0.10.9 + +### Patch Changes + +- [#1646](https://github.com/wevm/wagmi/pull/1646) [`fcdbe353`](https://github.com/wevm/wagmi/commit/fcdbe3531e6d05cda4a4a511bae1ad4c9e426d88) Thanks [@jxom](https://github.com/jxom)! - Upgraded `zustand` to v4.3.1. + +- Updated dependencies [[`fcdbe353`](https://github.com/wevm/wagmi/commit/fcdbe3531e6d05cda4a4a511bae1ad4c9e426d88)]: + - @wagmi/core@0.8.14 + +## 0.10.8 + +### Patch Changes + +- [#1639](https://github.com/wevm/wagmi/pull/1639) [`c6869f06`](https://github.com/wevm/wagmi/commit/c6869f0604fffb197752a08256f31db77f52e746) Thanks [@jxom](https://github.com/jxom)! - Added `isRainbow` flag to `InjectedConnector`. + +- Updated dependencies [[`c6869f06`](https://github.com/wevm/wagmi/commit/c6869f0604fffb197752a08256f31db77f52e746)]: + - @wagmi/core@0.8.13 + +## 0.10.7 + +### Patch Changes + +- [#1636](https://github.com/wevm/wagmi/pull/1636) [`025f6771`](https://github.com/wevm/wagmi/commit/025f6771b32ff7eed22f527be81c5141ddaf9c3d) Thanks [@DanielSinclair](https://github.com/DanielSinclair)! - Added `isRainbow` flag to injected `window.ethereum` types. + +- Updated dependencies [[`025f6771`](https://github.com/wevm/wagmi/commit/025f6771b32ff7eed22f527be81c5141ddaf9c3d)]: + - @wagmi/core@0.8.12 + +## 0.10.6 + +### Patch Changes + +- [#1623](https://github.com/wevm/wagmi/pull/1623) [`c97a4bc5`](https://github.com/wevm/wagmi/commit/c97a4bc5df422dc9a9d3d8bac0261ec6933ce15b) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useSigner` would not update on account change. + +## 0.10.5 + +### Patch Changes + +- [#1621](https://github.com/wevm/wagmi/pull/1621) [`5812b590`](https://github.com/wevm/wagmi/commit/5812b5909277bf2862cb57a31d52465b47291410) Thanks [@tmm](https://github.com/tmm)! - Bumped @wagmi/connectors + +- Updated dependencies [[`5812b590`](https://github.com/wevm/wagmi/commit/5812b5909277bf2862cb57a31d52465b47291410)]: + - @wagmi/core@0.8.11 + +## 0.10.4 + +### Patch Changes + +- [#1607](https://github.com/wevm/wagmi/pull/1607) [`49a41357`](https://github.com/wevm/wagmi/commit/49a41357f9ca39479bdf759f5998bc169a91ac87) Thanks [@tmm](https://github.com/tmm)! - Exported hook types. + +## 0.10.3 + +### Patch Changes + +- [#1598](https://github.com/wevm/wagmi/pull/1598) [`fc10ebe6`](https://github.com/wevm/wagmi/commit/fc10ebe659dd5f3b7a8e00581f094652280a779b) Thanks [@jxom](https://github.com/jxom)! - Fixed CJS dependency version range + +- Updated dependencies [[`fc10ebe6`](https://github.com/wevm/wagmi/commit/fc10ebe659dd5f3b7a8e00581f094652280a779b)]: + - @wagmi/core@0.8.10 + +## 0.10.2 + +### Patch Changes + +- [#1593](https://github.com/wevm/wagmi/pull/1593) [`216d555c`](https://github.com/wevm/wagmi/commit/216d555c62bd95c3c7c8f8e20f7269f6c8504610) Thanks [@jxom](https://github.com/jxom)! - Added CJS escape hatch bundle under the "cjs" tag. + +- Updated dependencies [[`216d555c`](https://github.com/wevm/wagmi/commit/216d555c62bd95c3c7c8f8e20f7269f6c8504610)]: + - @wagmi/core@0.8.9 + +## 0.10.1 + +### Patch Changes + +- [#1573](https://github.com/wevm/wagmi/pull/1573) [`ef380d9c`](https://github.com/wevm/wagmi/commit/ef380d9c6d51ae0495b9c35925d2843c75d97fd4) Thanks [@tmm](https://github.com/tmm)! - Updated internal types. + +- Updated dependencies [[`ef380d9c`](https://github.com/wevm/wagmi/commit/ef380d9c6d51ae0495b9c35925d2843c75d97fd4)]: + - @wagmi/core@0.8.8 + +## 0.10.0 + +### Minor Changes + +- [#1470](https://github.com/wevm/wagmi/pull/1470) [`3a1a6c9f`](https://github.com/wevm/wagmi/commit/3a1a6c9fe5db5c360adfd116f9a03a1238b5720c) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `useSigner` hook now always returns `undefined` when no signer is present. Previously, it returned `null`. + + When no signer is present, the hook will be in an `"idle"` status. + +### Patch Changes + +- [#1470](https://github.com/wevm/wagmi/pull/1470) [`3a1a6c9f`](https://github.com/wevm/wagmi/commit/3a1a6c9fe5db5c360adfd116f9a03a1238b5720c) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useSigner` would broadcast to other `useSigner`s unnecessarily. + +- [#1470](https://github.com/wevm/wagmi/pull/1470) [`3a1a6c9f`](https://github.com/wevm/wagmi/commit/3a1a6c9fe5db5c360adfd116f9a03a1238b5720c) Thanks [@jxom](https://github.com/jxom)! - The `WalletConnectConnector` now supports WalletConnect v2. + + It can be enabled by setting `version` to `'2'` and supplying a [WalletConnect Cloud `projectId`](https://cloud.walletconnect.com/sign-in). + +- [#1570](https://github.com/wevm/wagmi/pull/1570) [`216f585b`](https://github.com/wevm/wagmi/commit/216f585be8a9e3a56e3243f49ccd54d655b5a6dd) Thanks [@jxom](https://github.com/jxom)! - Added `useWatchPendingTransactions` + +- Updated dependencies [[`216f585b`](https://github.com/wevm/wagmi/commit/216f585be8a9e3a56e3243f49ccd54d655b5a6dd), [`3a1a6c9f`](https://github.com/wevm/wagmi/commit/3a1a6c9fe5db5c360adfd116f9a03a1238b5720c)]: + - @wagmi/core@0.8.7 + +## 0.9.6 + +### Patch Changes + +- [#1539](https://github.com/wevm/wagmi/pull/1539) [`732da004`](https://github.com/wevm/wagmi/commit/732da0042c7e28091b2e36a484ea8239971306f5) Thanks [@0xFlicker](https://github.com/0xFlicker)! - All Providers (ie. Alchemy, Infura, Public) now use the ENS Registry address on the wagmi `Chain` object (`chain.contracts.ensRegistry`). + +- [#1574](https://github.com/wevm/wagmi/pull/1574) [`ecde3d10`](https://github.com/wevm/wagmi/commit/ecde3d1029ccdf90e2853ba0e9ae4f5f4ebb9c4c) Thanks [@jxom](https://github.com/jxom)! - Added the following chains: + + - `iotex` + - `iotexTestnet` + - `zkSync` + - `zkSyncTestnet` + +- Updated dependencies [[`732da004`](https://github.com/wevm/wagmi/commit/732da0042c7e28091b2e36a484ea8239971306f5), [`ecde3d10`](https://github.com/wevm/wagmi/commit/ecde3d1029ccdf90e2853ba0e9ae4f5f4ebb9c4c)]: + - @wagmi/core@0.8.6 + +## 0.9.5 + +### Patch Changes + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Added the following chains: + + - `evmos` + - `evmosTestnet` + - `gnosis` + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Updated Goerli symbol to `"ETH"`. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Updated Arbitrum Goerli RPC and Block Explorer. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where connecting to MetaMask may return with a stale address. + +- [#1542](https://github.com/wevm/wagmi/pull/1542) [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549) Thanks [@jxom](https://github.com/jxom)! - Removed ENS registry for Sepolia. + +- Updated dependencies [[`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549), [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549), [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549), [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549), [`731b3b73`](https://github.com/wevm/wagmi/commit/731b3b733c6a093d1693d49de601705b7c730549)]: + - @wagmi/core@0.8.5 + +## 0.9.4 + +### Patch Changes + +- [#1508](https://github.com/wevm/wagmi/pull/1508) [`0b50b62f`](https://github.com/wevm/wagmi/commit/0b50b62f7389619e429509a3e337e451e823b059) Thanks [@jxom](https://github.com/jxom)! - Updated `@wagmi/chains` to `0.1.3`. + +- [#1507](https://github.com/wevm/wagmi/pull/1507) [`7a083bcf`](https://github.com/wevm/wagmi/commit/7a083bcf31d671817a4da2f40fb2160a1ba9d7b7) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useBlockNumber` would return data when `watch` is enabled and `enabled` is falsy. + +- [#1504](https://github.com/wevm/wagmi/pull/1504) [`11b8b794`](https://github.com/wevm/wagmi/commit/11b8b794fbfd4a2b40f39962e2758e9fbf48cb54) Thanks [@tmm](https://github.com/tmm)! - Converted ethers custom "ACTION_REJECTED" error to standard RPC Error. + +- Updated dependencies [[`0b50b62f`](https://github.com/wevm/wagmi/commit/0b50b62f7389619e429509a3e337e451e823b059), [`11b8b794`](https://github.com/wevm/wagmi/commit/11b8b794fbfd4a2b40f39962e2758e9fbf48cb54)]: + - @wagmi/core@0.8.4 + +## 0.9.3 + +### Patch Changes + +- [#1431](https://github.com/wevm/wagmi/pull/1431) [`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc) Thanks [@jxom](https://github.com/jxom)! - Re-export connectors from `@wagmi/connectors` + +- [#1431](https://github.com/wevm/wagmi/pull/1431) [`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc) Thanks [@jxom](https://github.com/jxom)! - Added `LedgerConnector` connector + +- Updated dependencies [[`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc), [`af28f8f9`](https://github.com/wevm/wagmi/commit/af28f8f9cfc227e7c391927fdb934183edb5c2dc)]: + - @wagmi/core@0.8.3 + +## 0.9.2 + +### Patch Changes + +- [#1442](https://github.com/wevm/wagmi/pull/1442) [`cde15289`](https://github.com/wevm/wagmi/commit/cde152899c758dea10787412b0aef669ed7202b2) Thanks [@0xproflupin](https://github.com/0xproflupin)! - Added Phantom wallet support to `InjectedConnector` + +- [#1448](https://github.com/wevm/wagmi/pull/1448) [`c6075f3a`](https://github.com/wevm/wagmi/commit/c6075f3a16885d850ad2656272351f9517c9f67b) Thanks [@tmm](https://github.com/tmm)! - Updated [ABIType](https://github.com/wevm/abitype) version. + +- [#1444](https://github.com/wevm/wagmi/pull/1444) [`310a8bc4`](https://github.com/wevm/wagmi/commit/310a8bc428ce4e7f68377f581b45dcdd64381cce) Thanks [@jxom](https://github.com/jxom)! - Assert that a `connector` exists before invoking the callback in `watchSigner`. + +- [#1434](https://github.com/wevm/wagmi/pull/1434) [`100e2a3b`](https://github.com/wevm/wagmi/commit/100e2a3b22f4602716554487b1d98738e053be76) Thanks [@tmm](https://github.com/tmm)! - Updated `MockConnector` `chainId` behavior to default to first chain from `chains` if not provided in `options`. + +- Updated dependencies [[`cde15289`](https://github.com/wevm/wagmi/commit/cde152899c758dea10787412b0aef669ed7202b2), [`c6075f3a`](https://github.com/wevm/wagmi/commit/c6075f3a16885d850ad2656272351f9517c9f67b), [`310a8bc4`](https://github.com/wevm/wagmi/commit/310a8bc428ce4e7f68377f581b45dcdd64381cce), [`100e2a3b`](https://github.com/wevm/wagmi/commit/100e2a3b22f4602716554487b1d98738e053be76)]: + - @wagmi/core@0.8.2 + +## 0.9.1 + +### Patch Changes + +- [#1437](https://github.com/wevm/wagmi/pull/1437) [`c34a3dc6`](https://github.com/wevm/wagmi/commit/c34a3dc6396e6473d9f0505fad88ec910f8f5275) Thanks [@jxom](https://github.com/jxom)! - Omitted `"EIP712Domain"` type from `signTypedData` `types` arg since ethers throws an [internal error](https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/hash/src.ts/typed-data.ts#L466) if you include it. + +- [#1445](https://github.com/wevm/wagmi/pull/1445) [`51dd53cb`](https://github.com/wevm/wagmi/commit/51dd53cba3fe0f79fa1393270b738194577ddf54) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where the wagmi client wouldn't rehydrate the store in local storage when `autoConnect` is truthy. + +- Updated dependencies [[`c34a3dc6`](https://github.com/wevm/wagmi/commit/c34a3dc6396e6473d9f0505fad88ec910f8f5275), [`51dd53cb`](https://github.com/wevm/wagmi/commit/51dd53cba3fe0f79fa1393270b738194577ddf54)]: + - @wagmi/core@0.8.1 + +## 0.9.0 + +### Minor Changes + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: With the introduction of the [`wagmi/chains` entrypoint](/react/chains#wagmichains), `wagmi` no longer exports the following: + + - `chain` + - `allChains` + - `defaultChains` + - `defaultL2Chains` + - `chainId` + - `etherscanBlockExplorers` + - `alchemyRpcUrls`, `infuraRpcUrls`, `publicRpcUrls` + + Read below for migration steps. + + #### Removed `chain` + + The `chain` export has been removed. `wagmi` now only exports the `mainnet` & `goerli` chains. If you need to use an alternative chain (`polygon`, `optimism`, etc), you will need to import it from the [`wagmi/chains` entrypoint](/react/chains#wagmichains). + + ```diff + import { + - chain + configureChains + } from 'wagmi' + + import { mainnet, polygon, optimism } from 'wagmi/chains' + + const { ... } = configureChains( + - [chain.mainnet, chain.polygon, chain.optimism], + + [mainnet, polygon, optimism], + { + ... + } + ) + ``` + + #### Removed `allChains` + + The `allChains` export has been removed. If you need a list of all chains, you can utilize [`wagmi/chains` entrypoint](/react/chains#wagmichains). + + ```diff + - import { allChains } from 'wagmi' + + import * as allChains from 'wagmi/chains' + + const { ... } = configureChains(allChains, ...) + ``` + + #### Removed `defaultChains` & `defaultL2Chains` + + The `defaultChains` & `defaultL2Chains` exports have been removed. If you still need the `defaultChains` or `defaultL2Chains` exports, you can build them yourself: + + ```diff + - import { defaultChains } from 'wagmi' + + import { mainnet, goerli } from 'wagmi/chains' + + + const defaultChains = [mainnet, goerli] + ``` + + > The `defaultChains` export was previously populated with `mainnet` & `goerli`. + + ```diff + - import { defaultL2Chains } from 'wagmi' + + import { + + arbitrum, + + arbitrumGoerli, + + polygon, + + polygonMumbai, + + optimism, + + optimismGoerli + + } from 'wagmi/chains' + + + const defaultL2Chains = [ + + arbitrum, + + arbitrumGoerli, + + polygon, + + polygonMumbai, + + optimism + + optimismGoerli + + ] + ``` + + > The `defaultL2Chains` export was previously populated with `arbitrum` & `optimism`. + + #### Removed `chainId` + + The `chainId` export has been removed. You can extract a chain ID from the chain itself. + + ```diff + - import { chainId } from 'wagmi' + + import { mainnet, polygon, optimism } from 'wagmi/chains' + + -const mainnetChainId = chainId.mainnet + -const polygonChainId = chainId.polygon + -const optimismChainId = chainId.optimism + +const mainnetChainId = mainnet.chainId + +const polygonChainId = polygon.chainId + +const optimismChainId = optimism.chainId + ``` + + #### Removed `etherscanBlockExplorers` + + The `etherscanBlockExplorers` export has been removed. You can extract a block explorer from the chain itself. + + ```diff + - import { etherscanBlockExplorers } from 'wagmi' + + import { mainnet, polygon, optimism } from 'wagmi/chains' + + -const mainnetEtherscanBlockExplorer = etherscanBlockExplorers.mainnet + -const polygonEtherscanBlockExplorer = etherscanBlockExplorers.polygon + -const optimismEtherscanBlockExplorer = etherscanBlockExplorers.optimism + +const mainnetEtherscanBlockExplorer = mainnet.blockExplorer + +const polygonEtherscanBlockExplorer = polygon.blockExplorer + +const optimismEtherscanBlockExplorer = optimism.blockExplorer + ``` + + #### Removed `alchemyRpcUrls`, `infuraRpcUrls` & `publicRpcUrls` + + The `alchemyRpcUrls`, `infuraRpcUrls` & `publicRpcUrls` exports have been removed. You can extract a RPC URL from the chain itself. + + ```diff + - import { alchemyRpcUrls, infuraRpcUrls, publicRpcUrls } from 'wagmi' + + import { mainnet } from 'wagmi/chains' + + -const mainnetAlchemyRpcUrl = alchemyRpcUrls.mainnet + -const mainnetInfuraRpcUrl = infuraRpcUrls.mainnet + -const mainnetOptimismRpcUrl = publicRpcUrls.mainnet + +const mainnetAlchemyRpcUrl = mainnet.rpcUrls.alchemy + +const mainnetInfuraRpcUrl = mainnet.rpcUrls.infura + +const mainnetOptimismRpcUrl = mainnet.rpcUrls.optimism + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: the shape of the `Chain` type has been modified. + + #### RPC URLs + + The `rpcUrls` shape has changed to include an array of URLs, and also the transport method (`http` or `webSocket`): + + ```diff + type Chain = { + ... + rpcUrls: { + - [key: string]: string + + [key: string]: { + + http: string[] + + webSocket: string[] + + } + } + ... + } + ``` + + Note that you will also need to ensure that usage is migrated: + + ```diff + - const rpcUrl = mainnet.rpcUrls.alchemy + + const rpcUrl = mainnet.rpcUrls.alchemy.http[0] + ``` + + #### Contracts + + The `multicall` and `ens` attributes have been moved into the `contracts` object: + + ```diff + type Contract = { + address: Address + blockCreated?: number + } + + type Chain = { + ... + - multicall: Contract + - ens: Contract + + contracts: { + + multicall3: Contract + + ensRegistry: Contract + + } + ... + } + ``` + + Note that you will also need to ensure that usage is migrated: + + ```diff + - const multicallContract = mainnet.multicall + + const multicallContract = mainnet.contracts.multicall3 + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: Removed the `wait` config option on `useWaitForTransaction`. Use the transaction `hash` instead. + + ```diff + const { data } = useWaitForTransaction({ + - wait: transaction.wait + + hash: transaction.hash + }) + ``` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - Updated errors to use `cause` instead of `internal` + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - `useEnsResolver`'s result is no longer persisted by the query client since it cannot serialize its prototype methods. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: Changed `useWaitForTransaction` behavior to return an error if the transaction reverted. + +### Patch Changes + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - `useWaitForTransaction` now throws an error for cancelled or replaced transactions. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - `useWaitForTransaction` now respects repriced (sped up) transactions. + +- [#1344](https://github.com/wevm/wagmi/pull/1344) [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652) Thanks [@jxom](https://github.com/jxom)! - Updated `@coinbase/wallet-sdk` to `^3.6.0`. + +- Updated dependencies [[`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652), [`57a19374`](https://github.com/wevm/wagmi/commit/57a1937464a4ccf72719fc86c38d1734f6306652)]: + - @wagmi/core@0.8.0 + +## 0.8.10 + +### Patch Changes + +- [#1411](https://github.com/wevm/wagmi/pull/1411) [`659be184`](https://github.com/wevm/wagmi/commit/659be1840c613ce9f7aca9ac96694c4f60da4a66) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where block invalidation was not properly disabled when setting `enabled: false`. + +- [#1409](https://github.com/wevm/wagmi/pull/1409) [`b557b3ee`](https://github.com/wevm/wagmi/commit/b557b3ee4fc58217e61d860fc3d1109d2abc813e) Thanks [@jxom](https://github.com/jxom)! - Ensure that `useSyncExternalStoreWithTracked` rerenders when no values are being tracked. + +- Updated dependencies [[`659be184`](https://github.com/wevm/wagmi/commit/659be1840c613ce9f7aca9ac96694c4f60da4a66)]: + - @wagmi/core@0.7.9 + +## 0.8.9 + +### Patch Changes + +- [#1406](https://github.com/wevm/wagmi/pull/1406) [`4f18c450`](https://github.com/wevm/wagmi/commit/4f18c450a4d7952bfcfa6c533348ffbe55893d3c) Thanks [@tmm](https://github.com/tmm)! - Function for selecting the [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Provider to target. Defaults to `() => typeof window !== 'undefined' ? window.ethereum : undefined`. + + ```ts + import { InjectedConnector } from "wagmi/connectors/injected"; + + const connector = new InjectedConnector({ + options: { + name: "My Injected Wallet", + getProvider: () => + typeof window !== "undefined" ? window.myInjectedWallet : undefined, + }, + }); + ``` + +- Updated dependencies [[`4f18c450`](https://github.com/wevm/wagmi/commit/4f18c450a4d7952bfcfa6c533348ffbe55893d3c)]: + - @wagmi/core@0.7.8 + +## 0.8.8 + +### Patch Changes + +- [#1386](https://github.com/wevm/wagmi/pull/1386) [`206a2adb`](https://github.com/wevm/wagmi/commit/206a2adbb4ee5149a364543b34612050ccf78c21) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `persister` would still use `window.localStorage` instead of the wagmi `storage`. + +- [#1376](https://github.com/wevm/wagmi/pull/1376) [`a70a9528`](https://github.com/wevm/wagmi/commit/a70a9528f93f4d7fea28b7652751dfef2dcacf9b) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `switchChain` on `WalletConnectConnector` would not resolve. + +- [#1392](https://github.com/wevm/wagmi/pull/1392) [`88afc849`](https://github.com/wevm/wagmi/commit/88afc84978afe9689ab7364633e4422ecd7699ea) Thanks [@tmm](https://github.com/tmm)! - Added check for active connector when connecting + +- Updated dependencies [[`206a2adb`](https://github.com/wevm/wagmi/commit/206a2adbb4ee5149a364543b34612050ccf78c21), [`a70a9528`](https://github.com/wevm/wagmi/commit/a70a9528f93f4d7fea28b7652751dfef2dcacf9b), [`206a2adb`](https://github.com/wevm/wagmi/commit/206a2adbb4ee5149a364543b34612050ccf78c21), [`88afc849`](https://github.com/wevm/wagmi/commit/88afc84978afe9689ab7364633e4422ecd7699ea)]: + - @wagmi/core@0.7.7 + +## 0.8.7 + +### Patch Changes + +- [#1384](https://github.com/wevm/wagmi/pull/1384) [`027e88d6`](https://github.com/wevm/wagmi/commit/027e88d6e5f8d028d46ee78aec8500701e0173d9) Thanks [@tmm](https://github.com/tmm)! - Fixed issue reconnecting after disconnect with `MetaMaskConnector` in MetaMask mobile browser. + +- [#1377](https://github.com/wevm/wagmi/pull/1377) [`089c4f3b`](https://github.com/wevm/wagmi/commit/089c4f3b3b8ce5cf7807f144410e2f64b72e0580) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where transforming `useContractRead`, `useContractReads` or `useContractInfiniteReads`'s return data via `select` wasn't inferring the type. + +- Updated dependencies [[`027e88d6`](https://github.com/wevm/wagmi/commit/027e88d6e5f8d028d46ee78aec8500701e0173d9)]: + - @wagmi/core@0.7.6 + +## 0.8.6 + +### Patch Changes + +- [`1169914a`](https://github.com/wevm/wagmi/commit/1169914a0f0ad2810ca1c536b1f1bc6c20f2c1be) Thanks [@jxom](https://github.com/jxom)! - Use `get_accounts` for `getSigner` in InjectedConnector + +- Updated dependencies [[`1169914a`](https://github.com/wevm/wagmi/commit/1169914a0f0ad2810ca1c536b1f1bc6c20f2c1be)]: + - @wagmi/core@0.7.5 + +## 0.8.5 + +### Patch Changes + +- [#1282](https://github.com/wevm/wagmi/pull/1282) [`6d286c9e`](https://github.com/wevm/wagmi/commit/6d286c9ed6f64a9872352904d4d171a6bc1c7a96) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useContractRead` would perform an unnecessary rerender if another hook had `watch` enabled. + +## 0.8.4 + +### Patch Changes + +- [#1309](https://github.com/wevm/wagmi/pull/1309) [`1f4a4261`](https://github.com/wevm/wagmi/commit/1f4a4261247b1d3a90e3123157bc851a35d49b9c) Thanks [@tmm](https://github.com/tmm)! - Fixed internal type + +- Updated dependencies [[`1f4a4261`](https://github.com/wevm/wagmi/commit/1f4a4261247b1d3a90e3123157bc851a35d49b9c)]: + - @wagmi/core@0.7.4 + +## 0.8.3 + +### Patch Changes + +- [#1294](https://github.com/wevm/wagmi/pull/1294) [`b2f88949`](https://github.com/wevm/wagmi/commit/b2f88949f32aabaf13f318472648cd51a8b7f2e7) Thanks [@tmm](https://github.com/tmm)! - Set `abi` return type value for `usePrepareContractWrite` as more permissive when not inferrable as `Abi`. + +- Updated dependencies [[`b2f88949`](https://github.com/wevm/wagmi/commit/b2f88949f32aabaf13f318472648cd51a8b7f2e7)]: + - @wagmi/core@0.7.3 + +## 0.8.2 + +### Patch Changes + +- [`e9f806b6`](https://github.com/wevm/wagmi/commit/e9f806b652ba62effb3ddac464815e447fc287f6) Thanks [@tmm](https://github.com/tmm)! - Bumped abitype and zustand versions. + +- [#1290](https://github.com/wevm/wagmi/pull/1290) [`88450052`](https://github.com/wevm/wagmi/commit/88450052b9f070fe53e18d84f72918c410b961f0) Thanks [@tmm](https://github.com/tmm)! - Fixed `useAccount`'s' `onConnect` callback `isReconnected` flag. + +- Updated dependencies [[`e9f806b6`](https://github.com/wevm/wagmi/commit/e9f806b652ba62effb3ddac464815e447fc287f6)]: + - @wagmi/core@0.7.2 + +## 0.8.1 + +### Patch Changes + +- [#1272](https://github.com/wevm/wagmi/pull/1272) [`1f7fc41`](https://github.com/wevm/wagmi/commit/1f7fc419f7960bbdc51dfa85c2f33b89f1ecc1bf) Thanks [@tmm](https://github.com/tmm)! - Fixed ethers import path + +- Updated dependencies [[`1f7fc41`](https://github.com/wevm/wagmi/commit/1f7fc419f7960bbdc51dfa85c2f33b89f1ecc1bf)]: + - @wagmi/core@0.7.1 + +## 0.8.0 + +### Minor Changes + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Removed the following deprecated chains: + + - `ropsten` + - `rinkeby` + - `kovan` + - `optimismKovan` + - `arbitrumRinkeby` + + If you feel you still need to include one of these testnets in your application, you will have to define it manually: + + ```diff + -import { rinkeby } from 'wagmi' + +import { Chain } from 'wagmi' + + +export const rinkeby: Chain = { + + id: 4, + + name: 'Rinkeby', + + network: 'rinkeby', + + nativeCurrency: { name: 'Rinkeby Ether', symbol: 'ETH', decimals: 18 }, + + rpcUrls: { + + alchemy: 'https://eth-rinkeby.alchemyapi.io/v2', + + default: 'https://rpc.ankr.com/eth_rinkeby', + + infura: 'https://rinkeby.infura.io/v3', + + public: 'https://rpc.ankr.com/eth_rinkeby', + + }, + + blockExplorers: { + + etherscan: 'https://rinkeby.etherscan.io', + + default: 'https://rinkeby.etherscan.io', + + }, + + ens: { + + address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + + }, + + multicall: { + + address: '0xca11bde05977b3631167028862be2a173976ca11', + + blockCreated: 10299530, + + }, + + testnet: true, + } + ``` + + You can reference these removed chains [here](https://github.com/wevm/wagmi/blob/389765f7d9af063ab0df07389a2bbfbc10a41060/packages/core/src/constants/chains.ts). + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Made `apiKey` required on `infuraProvider` and `alchemyProvider`. + + ```diff + import { configureChains } from 'wagmi' + + const config = configureChains(defaultChains, [ + - alchemyProvider(), + + alchemyProvider({ apiKey: process.env.ALCHEMY_API_KEY }) + ]) + ``` + + You can find your Alchemy API key from the [Alchemy Dashboard](https://dashboard.alchemyapi.io/), or your Infura API key from the [Infura Dashboard](https://infura.io/login). + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `addressOrName` renamed to `address` for `useBalance` and `useEnsAvatar`. + + ```diff + const result = useBalance({ + - addressOrName: '0x…', + + address: '0x…', + }) + ``` + + If you were using an ENS name instead of an address, you can resolve the name to an address before passing it to the action. + + ```diff + + const { data: address } = useEnsAddress({ name: 'example.eth' }) + const result = useBalance({ + - addressOrName: 'example.eth', + + address, + }) + ``` + +- [#1202](https://github.com/wevm/wagmi/pull/1202) [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2) Thanks [@tmm](https://github.com/tmm)! - Removed CommonJS support + +### Patch Changes + +- Updated dependencies [[`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2), [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2), [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2), [`9bf56af`](https://github.com/wevm/wagmi/commit/9bf56af3c30bdb80abb1e785c002e00986fadfb2)]: + - @wagmi/core@0.7.0 + +## 0.7.15 + +### Patch Changes + +- [#1262](https://github.com/wevm/wagmi/pull/1262) [`45e2ca4`](https://github.com/wevm/wagmi/commit/45e2ca4d1f33a7b1165c387d420b8d47f4f66935) Thanks [@tmm](https://github.com/tmm)! - Added default for `structuralSharing` for `useContractRead`, `useContractReads`, and `useContractInfiniteReads`. + +## 0.7.14 + +### Patch Changes + +- [#1260](https://github.com/wevm/wagmi/pull/1260) [`0e12f03`](https://github.com/wevm/wagmi/commit/0e12f0380442bccca9ed18991e783819778032fe) Thanks [@ilmpc](https://github.com/ilmpc)! - Deprecated `isDataEqual` option from and added `structuralSharing` option to `useContractRead`, `useContractReads`, and `useContractInfiniteReads`. + +## 0.7.13 + +### Patch Changes + +- [#1250](https://github.com/wevm/wagmi/pull/1250) [`ce2e0f4`](https://github.com/wevm/wagmi/commit/ce2e0f4a46b8fd1c509ead552012ef4c072a525b) Thanks [@tmm](https://github.com/tmm)! - Added support for Trust Wallet browser extension. + +- Updated dependencies [[`ce2e0f4`](https://github.com/wevm/wagmi/commit/ce2e0f4a46b8fd1c509ead552012ef4c072a525b)]: + - @wagmi/core@0.6.12 + +## 0.7.12 + +### Patch Changes + +- [#1234](https://github.com/wevm/wagmi/pull/1234) [`3ff9303`](https://github.com/wevm/wagmi/commit/3ff930349250f62137cca4ca3b382522882abf8a) Thanks [@tmm](https://github.com/tmm)! - Fixed issue with adding chain to wallet without block explorer URL. + +- Updated dependencies [[`3ff9303`](https://github.com/wevm/wagmi/commit/3ff930349250f62137cca4ca3b382522882abf8a)]: + - @wagmi/core@0.6.11 + +## 0.7.11 + +### Patch Changes + +- [#1232](https://github.com/wevm/wagmi/pull/1232) [`c0ca509`](https://github.com/wevm/wagmi/commit/c0ca509506dcf6d98b058df549dc761c9a5f3d1c) Thanks [@tmm](https://github.com/tmm)! - Added validation to check that chain is configured for connector when accessing `Signer`. + +- Updated dependencies [[`c0ca509`](https://github.com/wevm/wagmi/commit/c0ca509506dcf6d98b058df549dc761c9a5f3d1c)]: + - @wagmi/core@0.6.10 + +## 0.7.10 + +### Patch Changes + +- [#1206](https://github.com/wevm/wagmi/pull/1206) [`15ff089`](https://github.com/wevm/wagmi/commit/15ff0896216abecf5967294ae5aeb26ea7fb480b) Thanks [@jxom](https://github.com/jxom)! - Added `scopeKey` as a configuration option to the Hooks which scope its cache to a given context. Hooks that have identical scope will share the same cache. + +- [#1207](https://github.com/wevm/wagmi/pull/1207) [`c73d463`](https://github.com/wevm/wagmi/commit/c73d463d65c9dbfcfe709187e47323a769589741) Thanks [@lvshaoping007](https://github.com/lvshaoping007)! - Added Kucoin wallet support to `InjectedConnector` + +- Updated dependencies [[`c73d463`](https://github.com/wevm/wagmi/commit/c73d463d65c9dbfcfe709187e47323a769589741)]: + - @wagmi/core@0.6.9 + +## 0.7.9 + +### Patch Changes + +- [#1201](https://github.com/wevm/wagmi/pull/1201) [`9a07efa`](https://github.com/wevm/wagmi/commit/9a07efaa397d3ba03f2edbe527c359f21e22139a) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where non-checksum addresses did not resolve with an ENS name + +- [#1132](https://github.com/wevm/wagmi/pull/1132) [`d41c0d6`](https://github.com/wevm/wagmi/commit/d41c0d650f8c0e54145758685b7604b8909d7ae0) Thanks [@toniocodo](https://github.com/toniocodo)! - Added ERC-4626 ABI + +- Updated dependencies [[`d41c0d6`](https://github.com/wevm/wagmi/commit/d41c0d650f8c0e54145758685b7604b8909d7ae0), [`9a07efa`](https://github.com/wevm/wagmi/commit/9a07efaa397d3ba03f2edbe527c359f21e22139a)]: + - @wagmi/core@0.6.8 + +## 0.7.8 + +### Patch Changes + +- [#1174](https://github.com/wevm/wagmi/pull/1174) [`196a458`](https://github.com/wevm/wagmi/commit/196a458f64141e8a9f39c1b1e1af5937f692cb39) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `client.chains` (active connector chains) would be populated when there is no active connector (disconnected user). + +- [#1176](https://github.com/wevm/wagmi/pull/1176) [`389765f`](https://github.com/wevm/wagmi/commit/389765f7d9af063ab0df07389a2bbfbc10a41060) Thanks [@jxom](https://github.com/jxom)! - Migrate away from Alchemy RPC URLs in the public RPC URL list + +- Updated dependencies [[`196a458`](https://github.com/wevm/wagmi/commit/196a458f64141e8a9f39c1b1e1af5937f692cb39), [`389765f`](https://github.com/wevm/wagmi/commit/389765f7d9af063ab0df07389a2bbfbc10a41060)]: + - @wagmi/core@0.6.7 + +## 0.7.7 + +### Patch Changes + +- [#1166](https://github.com/wevm/wagmi/pull/1166) [`6fbe910`](https://github.com/wevm/wagmi/commit/6fbe91080b54e33e8543e9638ff5089e749ada3f) Thanks [@jxom](https://github.com/jxom)! - Export the React entrypoint `Client` type instead of `@wagmi/core`'s `Client`. + +- [`81ce9e6`](https://github.com/wevm/wagmi/commit/81ce9e64d85f7d01370324c1a529988a0919894f) Thanks [@jxom](https://github.com/jxom)! - Add `isPortal` to injected MetaMask flags. + +- [`c2c0109`](https://github.com/wevm/wagmi/commit/c2c01096ef4cd0ffadbb49062969c208604c6194) Thanks [@jxom](https://github.com/jxom)! - Add etherscan block explorer to Optimism Goerli + +- Updated dependencies [[`81ce9e6`](https://github.com/wevm/wagmi/commit/81ce9e64d85f7d01370324c1a529988a0919894f), [`c2c0109`](https://github.com/wevm/wagmi/commit/c2c01096ef4cd0ffadbb49062969c208604c6194)]: + - @wagmi/core@0.6.6 + +## 0.7.6 + +### Patch Changes + +- [#1162](https://github.com/wevm/wagmi/pull/1162) [`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where non-indexed event parameter types were set to `null`. + +- [#1162](https://github.com/wevm/wagmi/pull/1162) [`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e) Thanks [@tmm](https://github.com/tmm)! - Fixed issue where `useContractReads` and `useContractInfiniteReads` types were slowing down TypeScript compiler. + +- Updated dependencies [[`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e), [`30335b3`](https://github.com/wevm/wagmi/commit/30335b3199fb425e398e9c492b50c68d5e2ade7e)]: + - @wagmi/core@0.6.5 + +## 0.7.5 + +### Patch Changes + +- [#1103](https://github.com/wevm/wagmi/pull/1103) [`651eda0`](https://github.com/wevm/wagmi/commit/651eda06384bd0955268427f898e9337b2dc5a31) Thanks [@tmm](https://github.com/tmm)! - Bumped `abitype` dependency. + +- Updated dependencies [[`651eda0`](https://github.com/wevm/wagmi/commit/651eda06384bd0955268427f898e9337b2dc5a31)]: + - @wagmi/core@0.6.4 + +## 0.7.4 + +### Patch Changes + +- [#1099](https://github.com/wevm/wagmi/pull/1099) [`748e617`](https://github.com/wevm/wagmi/commit/748e61719ad706acae057be903321ebe0c2e817e) Thanks [@jxom](https://github.com/jxom)! - Added `isFetchedAfterMount` to the return value of hooks. + + The `isFetchedAfterMount` will be truthy if the hook has fetched after the component has been mounted. This value can be utilized to not show the result if it has previously been cached. + +- [#1091](https://github.com/wevm/wagmi/pull/1091) [`a3aaf59`](https://github.com/wevm/wagmi/commit/a3aaf590e8e993017baa9a1ac50ecd63dd287caf) Thanks [@tmm](https://github.com/tmm)! - Fixed `useAccount` `onConnect`/`onDisconnect` from not firing when the account was already connected/disconnected. + +## 0.7.3 + +### Patch Changes + +- [#1086](https://github.com/wevm/wagmi/pull/1086) [`4e28d2a`](https://github.com/wevm/wagmi/commit/4e28d2ad4c2e6b3479b728563040b9529463cbcf) Thanks [@tmm](https://github.com/tmm)! - Exposed module types. + +- Updated dependencies [[`4e28d2a`](https://github.com/wevm/wagmi/commit/4e28d2ad4c2e6b3479b728563040b9529463cbcf)]: + - @wagmi/core@0.6.3 + +## 0.7.2 + +### Patch Changes + +- [#1080](https://github.com/wevm/wagmi/pull/1080) [`3be5e8b`](https://github.com/wevm/wagmi/commit/3be5e8b01e58ed40cc9dab7ef9533c0197cb74d0) Thanks [@tmm](https://github.com/tmm)! - Added `abitype` to `dependencies` so types ship correctly. + +- Updated dependencies [[`3be5e8b`](https://github.com/wevm/wagmi/commit/3be5e8b01e58ed40cc9dab7ef9533c0197cb74d0)]: + - @wagmi/core@0.6.2 + +## 0.7.1 + +### Patch Changes + +- [#1074](https://github.com/wevm/wagmi/pull/1074) [`8db807f`](https://github.com/wevm/wagmi/commit/8db807f16149aa278c2a7db9ee5245431db12173) Thanks [@IljaDaderko](https://github.com/IljaDaderko)! - Exported `EventListener` type + +- Updated dependencies [[`8db807f`](https://github.com/wevm/wagmi/commit/8db807f16149aa278c2a7db9ee5245431db12173)]: + - @wagmi/core@0.6.1 + +## 0.7.0 + +### Minor Changes + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `usePrepareContractWrite` now throws when a `chainId` is specified and the end-user is on a different chain id (the wrong network). + + If you wish to defer this check until the click handler is pressed, you can place `chainId` in `useContractWrite` instead: + + ```diff + import { usePrepareContractWrite, useContractWrite } from 'wagmi' + import { optimism } from 'wagmi/chains' + + // ... + + const { config } = usePrepareContractWrite({ + addressOrName: '0xaf0326d92b97df1221759476b072abfd8084f9be', + contractInterface: ['function mint()'], + functionName: 'mint', + }) + const { write } = useContractWrite({ + ...config, + + chainId: optimism.id + }) + + ``` + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `usePrepareSendTransaction` hook will now only run when the end-user is connected to their wallet. + + This is to reach parity with `usePrepareContractWrite`. + + If the end-user is not connected, then the `usePrepareSendTransaction` hook will remain idle. + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: `usePrepareSendTransaction` now throws when a `chainId` is specified and the end-user is on a different chain id (the wrong network). + + If you wish to defer this check until the click handler is pressed, you can place `chainId` in `useContractWrite` instead: + + ```diff + import { usePrepareSendTransaction, useContractWrite } from 'wagmi' + import { optimism } from 'wagmi/chains' + + // ... + + const { config } = usePrepareSendTransaction({ + request: { + to: 'moxey.eth', + value: parseEther('1'), + }, + }) + const { sendTransaction } = useSendTransaction({ + ...config, + + chainId: optimism.id + }) + + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `useContractEvent` no longer accepts a `signerOrProvider` configuration option. + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `addressOrName` and `contractInterface` renamed to `address` and `abi` respectively for contract hooks: `useContract`, `useContractEvent`, `useContractRead`, `useContractReads`, `useContractInfiniteReads`, `useContractWrite`, `usePrepareContractWrite`. + + ```diff + import { useContractRead } from 'wagmi' + + const result = useContractRead({ + - addressOrName: '0x…', + + address: '0x…', + - contractInterface: […] as const, + + abi: […] as const, + functionName: 'balanceOf', + args: ['0x…'], + }) + ``` + + If you were using an ENS name instead of an address, you can resolve the name to an address before passing it to the action. + + ```diff + - import { useContractRead } from 'wagmi' + + import { useContractRead, useEnsAddress } from 'wagmi' + + + const { data: address} = useEnsAddress({ name: 'example.eth'}) + const result = useContractRead({ + - addressOrName: 'example.eth', + + address, + abi: […], + functionName: 'balanceOf', + args: ['0x…'], + }) + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Updated TypeScript generics for contract and typed data hooks. + + Adding a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to `abi` allows TypeScript to infer `functionName`, `args`, `overrides`, and return types for functions, and `eventName` and `listener` types for events. + + ```diff + import { useContractRead } from 'wagmi' + + const result = useContractRead({ + address: '0x…', + - abi: […], + + abi: […] as const, + functionName: 'balanceOf', // will autocomplete and catch typos + args: ['0x…'], // inferred based on `abi`, `functionName`, `args` + }) + result.data // inferred based on `functionName` + ``` + + This works for the following actions: `useContractRead`, `useContractWrite`, `usePrepareContractWrite`, `useContractReads`, `useContractInfiniteReads`, and `useContractEvent`. + + Adding a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to `useSignTypedData`'s config option, `types`, allows TypeScript to infer `value`. + + ```diff + import { useSignTypedData } from 'wagmi' + + const result = useSignTypedData({ + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + - }, + + } as const, + value: { // `value` is inferred based on `types` + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + }) + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Updated TypeScript version to `typescript@>=4.7.4`. + + `@wagmi/core` can now infer types based on [ABI](https://docs.soliditylang.org/en/v0.8.15/abi-spec.html#json) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data definitions, giving you full end-to-end type-safety from your contracts to your frontend and incredible developer experience (e.g. autocomplete contract function names and catch misspellings, type contract function arguments, etc.). + + For this to work, you must upgrade to `typescript@>=4.7.4`. Why is TypeScript v4.7.4 or greater necessary? TypeScript 4.7.4 introduced the ability to [extend constraints on inferred type variables](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#extends-constraints-on-infer-type-variables), which is used extensively to help narrow types for ABIs. Good news! When upgrading TypeScript from 4.6 to 4.7 there are likely no [breaking changes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#breaking-changes) for your set up. + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Updated `paginatedIndexesConfig` `fn` parameter return type. `fn` now returns an array instead of a single object. + + ```diff + import { BigNumber } from 'ethers' + import { paginatedIndexesConfig, useContractInfiniteReads } from 'wagmi' + + useContractInfiniteReads({ + cacheKey: 'contracts', + ...paginatedIndexesConfig( + - (index) => ({ + + (index) => [{ + ...mlootContractConfig, + functionName: 'tokenURI', + args: [BigNumber.from(index)] as const, + - }), + + }], + { start: 0, perPage: 10, direction: 'increment' }, + ), + }) + ``` + +- [#941](https://github.com/wevm/wagmi/pull/941) [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `args` config option must now be an array for the following hooks: `useContractRead`, `useContractWrite`, `usePrepareContractWrite`, `useContractReads`, and `useContractInfiniteReads`. + + ```diff + import { useContractRead } from 'wagmi' + + const { data } = useContractRead({ + address: '0x…', + abi: […], + functionName: 'balanceOf', + - args: '0x…', + + args: ['0x…'], + }) + ``` + +### Patch Changes + +- [#940](https://github.com/wevm/wagmi/pull/940) [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663) Thanks [@jxom](https://github.com/jxom)! - The `useSigner` hook now accepts an optional `chainId` to use for signer initialization as an argument. + + ```tsx + import { useSigner } from "wagmi"; + import { optimism } from "wagmi/core"; + + // ... + + useSigner({ chainId: optimism.id }); + ``` + +- [#1061](https://github.com/wevm/wagmi/pull/1061) [`a4ffe8b`](https://github.com/wevm/wagmi/commit/a4ffe8b25516d5504685ae94579da4cd8c409329) Thanks [@alecananian](https://github.com/alecananian)! - Added Arbitrum Goerli Arbiscan block explorer + +- [#1050](https://github.com/wevm/wagmi/pull/1050) [`73d4d47`](https://github.com/wevm/wagmi/commit/73d4d47bc679f4f9a1cf46010fe2bf858c9d0b5c) Thanks [@jxom](https://github.com/jxom)! - update dependencies + + - `@coinbase/wallet-sdk@3.5.3` + - `@tanstack/query-sync-storage-persister@4.10.1` + - `@tanstack/react-query@4.10.1` + - `@tanstack/react-query-persist-client@4.10.1` + - `@walletconnect/ethereum-provider@1.8.0` + +- [#1048](https://github.com/wevm/wagmi/pull/1048) [`ed13074`](https://github.com/wevm/wagmi/commit/ed130747c0f28c1d9980a1328883e4000a60455e) Thanks [@Max-3-7](https://github.com/Max-3-7)! - Added support for Avalanche core wallet + +- [#1046](https://github.com/wevm/wagmi/pull/1046) [`ab9ecaa`](https://github.com/wevm/wagmi/commit/ab9ecaa74dfa4324279e167dd7e348319ef7d35d) Thanks [@jxom](https://github.com/jxom)! - make ethers block format validator compatible with Celo + +- Updated dependencies [[`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663), [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663), [`a4ffe8b`](https://github.com/wevm/wagmi/commit/a4ffe8b25516d5504685ae94579da4cd8c409329), [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663), [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c), [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663), [`ed13074`](https://github.com/wevm/wagmi/commit/ed130747c0f28c1d9980a1328883e4000a60455e), [`b6cb8f4`](https://github.com/wevm/wagmi/commit/b6cb8f4cd15eb13073bc7e9ecb4bfa2c261c0663), [`ab9ecaa`](https://github.com/wevm/wagmi/commit/ab9ecaa74dfa4324279e167dd7e348319ef7d35d), [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c), [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c), [`73d4d47`](https://github.com/wevm/wagmi/commit/73d4d47bc679f4f9a1cf46010fe2bf858c9d0b5c), [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c), [`0c96009`](https://github.com/wevm/wagmi/commit/0c96009398647a515a57f72ef25c32724f7c978c)]: + - @wagmi/core@0.6.0 + +## 0.6.8 + +### Patch Changes + +- [`0b77286b`](https://github.com/wevm/wagmi/commit/0b77286b89cb8603426cf5081872416c291a6531) Thanks [@jxom](https://github.com/jxom)! - Isolate wagmi's React Query `queryClient` instance. + +* [`8cb07462`](https://github.com/wevm/wagmi/commit/8cb07462acc3c5637398d11d2451f8b8e330d553) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` as an argument to `watchBlockNumber`. + +- [`53c1a474`](https://github.com/wevm/wagmi/commit/53c1a4747d03b685e8cfbf55361fc2a56777fb06) Thanks [@tmm](https://github.com/tmm)! - Added missing `decimals` option to `Connector` `watchAsset` + +* [`4d74dd4f`](https://github.com/wevm/wagmi/commit/4d74dd4ff827ba5c43c3546a218f38cee45ea76a) Thanks [@jxom](https://github.com/jxom)! - Support ERC20 contracts that represent strings as bytes32 + +* Updated dependencies [[`8cb07462`](https://github.com/wevm/wagmi/commit/8cb07462acc3c5637398d11d2451f8b8e330d553), [`53c1a474`](https://github.com/wevm/wagmi/commit/53c1a4747d03b685e8cfbf55361fc2a56777fb06), [`4d74dd4f`](https://github.com/wevm/wagmi/commit/4d74dd4ff827ba5c43c3546a218f38cee45ea76a)]: + - @wagmi/core@0.5.8 + +## 0.6.7 + +### Patch Changes + +- [`aa51bc4d`](https://github.com/wevm/wagmi/commit/aa51bc4dc5683bf0178597d2fdb8f2e9d82e7970) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue in `CoinbaseWalletConnector` where the browser extension would unintendedly reset the network when the browser is refreshed. + +* [#955](https://github.com/wevm/wagmi/pull/955) [`e326cd80`](https://github.com/wevm/wagmi/commit/e326cd80fe65267db623eb6c80ccdd75572914cf) Thanks [@0xFlicker](https://github.com/0xFlicker)! - Added Infura RPC URL for Sepolia + +- [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useProvider` & `getProvider` were not returning referentially equal providers. + +* [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where the `watch` option was not respecting the neighboring `chainId` option in `useBlockNumber`. + +- [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where block listeners (via `watch`) were firing excessively on L2 chains. + +- Updated dependencies [[`aa51bc4d`](https://github.com/wevm/wagmi/commit/aa51bc4dc5683bf0178597d2fdb8f2e9d82e7970), [`e326cd80`](https://github.com/wevm/wagmi/commit/e326cd80fe65267db623eb6c80ccdd75572914cf), [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69), [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69), [`cec14089`](https://github.com/wevm/wagmi/commit/cec14089500c86687226ab272b4c3fcb85ae3d69)]: + - @wagmi/core@0.5.7 + +## 0.6.6 + +### Patch Changes + +- [#936](https://github.com/wevm/wagmi/pull/936) [`3329d1f`](https://github.com/wevm/wagmi/commit/3329d1f5880431566e14ac1640f48d0975aec4c2) Thanks [@jxom](https://github.com/jxom)! - Added the ability to provide a custom logger to override how logs are broadcasted to the consumer in wagmi. + + A custom logger can be provided to the wagmi client via `logger`. + + ### API + + ```tsx + logger?: { + warn: typeof console.warn | null + } + ``` + + ### Examples + + **Passing in a custom logger** + + You can pass in a function to define your own custom logger. + + ```diff + + import { logWarn } from './logger'; + + const client = createClient({ + ... + + logger: { + + warn: message => logWarn(message) + + } + ... + }) + ``` + + **Disabling a logger** + + You can disable a logger by passing `null` as the value. + + ```diff + const client = createClient({ + ... + + logger: { + + warn: null + + } + ... + }) + ``` + +* [#889](https://github.com/wevm/wagmi/pull/889) [`27788ed`](https://github.com/wevm/wagmi/commit/27788ed989b5dc26849c7945fb91a92e56766018) Thanks [@jxom](https://github.com/jxom)! - Make multicall & readContracts more error robust + +* Updated dependencies [[`3329d1f`](https://github.com/wevm/wagmi/commit/3329d1f5880431566e14ac1640f48d0975aec4c2), [`27788ed`](https://github.com/wevm/wagmi/commit/27788ed989b5dc26849c7945fb91a92e56766018)]: + - @wagmi/core@0.5.6 + +## 0.6.5 + +### Patch Changes + +- [#912](https://github.com/wevm/wagmi/pull/912) [`e529e12`](https://github.com/wevm/wagmi/commit/e529e125c713ed3ef24a59c6bf226fe4deee7ac9) Thanks [@zouhangwithsweet](https://github.com/zouhangwithsweet)! - Added BitKeep to injected flags + +- [#912](https://github.com/wevm/wagmi/pull/910) Thanks [@mytangying](https://github.com/zouhangwithsweet)! - Added MathWallet to injected flags + +- [#904](https://github.com/wevm/wagmi/pull/904) [`c231058`](https://github.com/wevm/wagmi/commit/c23105850f335f8798031e14c7098b7dee8c2975) Thanks [@jxom](https://github.com/jxom)! - Removed `contractInterface` & `signer` from persisted query keys. + +- Updated dependencies [[`e529e12`](https://github.com/wevm/wagmi/commit/e529e125c713ed3ef24a59c6bf226fe4deee7ac9), [`c231058`](https://github.com/wevm/wagmi/commit/c23105850f335f8798031e14c7098b7dee8c2975)]: + - @wagmi/core@0.5.5 + +## 0.6.4 + +### Patch Changes + +- [#852](https://github.com/wevm/wagmi/pull/852) [`c3192d0`](https://github.com/wevm/wagmi/commit/c3192d0663aa332ae9edfd9dd49b333454013ab7) Thanks [@skeithc](https://github.com/skeithc)! - Added support for the Sepolia testnet + +- Updated dependencies [[`c3192d0`](https://github.com/wevm/wagmi/commit/c3192d0663aa332ae9edfd9dd49b333454013ab7)]: + - @wagmi/core@0.5.4 + +## 0.6.3 + +### Patch Changes + +- [#835](https://github.com/wevm/wagmi/pull/835) [`1b85e54`](https://github.com/wevm/wagmi/commit/1b85e54ae654e2564cf5bc2dae6411fe0a25875c) Thanks [@jxom](https://github.com/jxom)! - Update `@coinbase/wallet-sdk` to `3.4.1` + +* [#843](https://github.com/wevm/wagmi/pull/843) [`e77dee6`](https://github.com/wevm/wagmi/commit/e77dee6a606b8aac4279569c54cec8902476fee9) Thanks [@tmm](https://github.com/tmm)! - Fix `MockConnector` entrypoint path + +- [#834](https://github.com/wevm/wagmi/pull/834) [`9655879`](https://github.com/wevm/wagmi/commit/96558793b0319df47aefafa6b7b9c959068d491b) Thanks [@jxom](https://github.com/jxom)! - Update zustand to `4.0.0` + +* [#833](https://github.com/wevm/wagmi/pull/833) [`3ae6d0f`](https://github.com/wevm/wagmi/commit/3ae6d0f5e2d65432024272b43afe68a8f63bb7ea) Thanks [@jxom](https://github.com/jxom)! - Updated `react-query@4.0.0-beta.23` to `@tanstack/react-query@^4.0.10` + +* Updated dependencies [[`1b85e54`](https://github.com/wevm/wagmi/commit/1b85e54ae654e2564cf5bc2dae6411fe0a25875c), [`9655879`](https://github.com/wevm/wagmi/commit/96558793b0319df47aefafa6b7b9c959068d491b)]: + - @wagmi/core@0.5.3 + +## 0.6.2 + +### Patch Changes + +- [#823](https://github.com/wevm/wagmi/pull/823) [`10b8b78`](https://github.com/wevm/wagmi/commit/10b8b78605b7246b2c55b8d69f96663906e5cd20) Thanks [@tmm](https://github.com/tmm)! - Add Optimism Goerli to `chain` lookup. + +- Updated dependencies [[`10b8b78`](https://github.com/wevm/wagmi/commit/10b8b78605b7246b2c55b8d69f96663906e5cd20)]: + - @wagmi/core@0.5.2 + +## 0.6.1 + +### Patch Changes + +- [#767](https://github.com/wevm/wagmi/pull/767) [`e9392f3`](https://github.com/wevm/wagmi/commit/e9392f396e48e928bd9d2522e3ad671c589f08cb) Thanks [@klyap](https://github.com/klyap)! - Add Optimism Goerli chain ahead of [Kovan deprecation](https://dev.optimism.io/kovan-to-goerli). + +* [#817](https://github.com/wevm/wagmi/pull/817) [`7e5cac7`](https://github.com/wevm/wagmi/commit/7e5cac75815dcd8aa563462342a4853fc5207735) Thanks [@alecananian](https://github.com/alecananian)! - Added custom name mapping for 1inch Wallet injected provider + +- [#806](https://github.com/wevm/wagmi/pull/806) [`0b34e56`](https://github.com/wevm/wagmi/commit/0b34e56db97e6dcdb71088e0149b2d55ebc604a5) Thanks [@vmichalik](https://github.com/vmichalik)! - Fix canonical testnet native asset symbols by changing them to ETH + +* [#778](https://github.com/wevm/wagmi/pull/778) [`0892908`](https://github.com/wevm/wagmi/commit/08929084eeeba1a3a55aa098fa9d92a243685ad5) Thanks [@0xcadams](https://github.com/0xcadams)! - Add Arbitrum Goerli chain. + +* Updated dependencies [[`e9392f3`](https://github.com/wevm/wagmi/commit/e9392f396e48e928bd9d2522e3ad671c589f08cb), [`7e5cac7`](https://github.com/wevm/wagmi/commit/7e5cac75815dcd8aa563462342a4853fc5207735), [`0b34e56`](https://github.com/wevm/wagmi/commit/0b34e56db97e6dcdb71088e0149b2d55ebc604a5), [`0892908`](https://github.com/wevm/wagmi/commit/08929084eeeba1a3a55aa098fa9d92a243685ad5)]: + - @wagmi/core@0.5.1 + +## 0.6.0 + +### Minor Changes + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** The `useSendTransaction` hook's `data` now returns an object only consisting of `hash` & `wait`, and not the full [`TransactionResponse`](https://docs.ethers.io/v5/api/providers/types/#providers-TransactionResponse). + + If you require the full `TransactionResponse`, you can use `useTransaction`: + + ```diff + import { useSendTransaction, useTransaction } from 'wagmi' + + const { + data: { + hash, + wait, + - ...transaction + } + } = useSendTransaction(...) + + +const { data: transaction } = useTransaction({ hash }) + ``` + + > Why? The old implementation of `useSendTransaction` created a long-running async task, causing [UX pitfalls](https://wagmi.sh/docs/prepare-hooks#ux-pitfalls-without-prepare-hooks) when invoked in a click handler. + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The configuration passed to the `useSendTransaction` hook now needs to be either: + + - prepared with the `usePrepareSendTransaction` hook **(new)**, or + - recklessly unprepared **(previous functionality)** + + > Why? [Read here](https://wagmi.sh/docs/prepare-hooks) + + ### Prepared usage + + ```diff + import { usePrepareSendTransaction, useSendTransaction } from 'wagmi' + + +const { config } = usePrepareSendTransaction({ + + request: { + + to: 'moxey.eth', + + value: parseEther('1'), + + } + +}) + + const { data } = useSendTransaction({ + - request: { + - to: 'moxey.eth', + - value: parseEther('1') + - } + + ...config + }) + ``` + + ### Recklessly unprepared usage + + If you are not ready to upgrade to `usePrepareSendTransaction`, it is possible to use `useSendTransaction` without preparing the configuration first by passing `mode: 'recklesslyUnprepared'`. + + ```diff + import { useSendTransaction } from 'wagmi' + + const { data } = useSendTransaction({ + + mode: 'recklesslyUnprepared', + request: { + to: 'moxey.eth', + value: parseEther('1'), + } + }) + ``` + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: If a `chainId` is passed to `useContractWrite` or `useSendTransaction`, it will no longer attempt to switch chain before sending the transaction. Instead, it will throw an error if the user is on the wrong chain. + + > Why? Eagerly prompting to switch chain in these actions created a long-running async task that that makes [iOS App Links](https://wagmi.sh/docs/prepare-hooks#ios-app-link-constraints) vulnerable. + +* [#760](https://github.com/wevm/wagmi/pull/760) [`d8af6bf`](https://github.com/wevm/wagmi/commit/d8af6bf50885aec110ae4d64716642453aa27896) Thanks [@tmm](https://github.com/tmm)! - **Breaking:** `alchemyProvider` and `infuraProvider` now use a generic `apiKey` configuration option instead of `alchemyId` and `infuraId`. + + ```diff + import { alchemyProvider } from '@wagmi/core/providers/alchemy' + import { infuraProvider } from '@wagmi/core/providers/infura' + + alchemyProvider({ + - alchemyId: 'yourAlchemyApiKey', + + apiKey: 'yourAlchemyApiKey', + }) + + infuraProvider({ + - infuraId: 'yourInfuraApiKey', + + apiKey: 'yourInfuraApiKey', + }) + ``` + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - Added the `usePrepareContractWrite` hook that eagerly fetches the parameters required for sending a contract write transaction such as the gas estimate. + + It returns config to be passed through to `useContractWrite`. + + ```ts + const { config } = usePrepareContractWrite({ + addressOrName: "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + contractInterface: wagmigotchiABI, + functionName: "feed", + }); + const { write } = useContractWrite(config); + ``` + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** When `useSendTransaction` is in "prepare mode" (used with `usePrepareSendTransaction`), `sendTransaction`/`sendTransactionAsync` will be `undefined` until the configuration has been prepared. Ensure that your usage reflects this. + + ```tsx + const { config } = usePrepareSendTransaction({ ... }) + const { sendTransaction } = useSendTransaction(config) + + + ``` + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - Added the `usePrepareSendTransaction` hook that eagerly fetches the parameters required for sending a transaction such as the gas estimate and resolving an ENS address (if required). + + It returns config to be passed through to `useSendTransaction`. + + ```ts + import { usePrepareSendTransaction, useSendTransaction } from "@wagmi/core"; + + const { config } = usePrepareSendTransaction({ + request: { + to: "moxey.eth", + value: parseEther("1"), + }, + }); + const { sendTransaction } = useSendTransaction(config); + ``` + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** The `sendTransaction`/`sendTransactionAsync` configuration object has now been altered to only accept "reckless" configuration. If one or more of these values are set, it can lead to [UX pitfalls](https://wagmi.sh/docs/prepare-hooks#ux-pitfalls-without-prepare-hooks). + + ```diff + + ``` + +- [#727](https://github.com/wevm/wagmi/pull/727) [`ac3b9b8`](https://github.com/wevm/wagmi/commit/ac3b9b87f80cb45b65d003f09d916d7d1427a62e) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: Moved the `pollingInterval` config option from the chain provider config to `configureChains` config. + + ```diff + const { chains, provider } = configureChains( + [chain.mainnet, chain.polygon], + [ + - alchemyProvider({ apiKey, pollingInterval: 5000 }), + - publicProvider({ pollingInterval: 5000 }) + + alchemyProvider({ apiKey }), + + publicProvider() + ], + + { pollingInterval: 5000 } + ) + ``` + +* [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** When `useContractWrite` is in "prepare mode" (used with `usePrepareContractWrite`), `write`/`writeAsync` will be `undefined` until the configuration has been prepared. Ensure that your usage reflects this. + + ```tsx + const { config } = usePrepareContractWrite({ ... }) + const { write } = useContractWrite(config) + + + ``` + +- [#658](https://github.com/wevm/wagmi/pull/658) [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The configuration passed to the `useContractWrite` hook now needs to be either: + + - prepared with the `usePrepareContractWrite` hook **(new)**, or + - recklessly unprepared **(previous functionality)** + + > Why? [Read here](https://wagmi.sh/docs/prepare-hooks) + + ### Prepared usage + + ```diff + import { usePrepareContractWrite, useContractWrite } from 'wagmi' + + +const { config } = usePrepareContractWrite({ + + addressOrName: '0x...', + + contractInterface: wagmiAbi, + + functionName: 'mint', + + args: [tokenId] + +}) + + const { data } = useContractWrite({ + - addressOrName: '0x...', + - contractInterface: wagmiAbi, + - functionName: 'mint', + - args: [tokenId], + + ...config + }) + ``` + + ### Recklessly unprepared usage + + If you are not ready to upgrade to `usePrepareContractWrite`, it is possible to use `useContractWrite` without preparing the configuration first by passing `mode: 'recklesslyUnprepared'`. + + ```diff + import { useContractWrite } from 'wagmi' + + const { data } = useContractWrite({ + + mode: 'recklesslyUnprepared', + addressOrName: '0x...', + contractInterface: wagmiAbi, + functionName: 'mint', + args: [tokenId], + }) + ``` + +### Patch Changes + +- [#733](https://github.com/wevm/wagmi/pull/733) [`6232487`](https://github.com/wevm/wagmi/commit/623248703bc728d539e28bf8a89b8ab22f0a5703) Thanks [@tmm](https://github.com/tmm)! - Add mock connector entrypoint + +* [#762](https://github.com/wevm/wagmi/pull/762) [`ccaeed5`](https://github.com/wevm/wagmi/commit/ccaeed53d731f51879e0cdd5648797a32f7d7a31) Thanks [@jxom](https://github.com/jxom)! - Fix `useContractRead` return value unexpectedly returning null for falsy values + +- [#734](https://github.com/wevm/wagmi/pull/734) [`7c2fa04`](https://github.com/wevm/wagmi/commit/7c2fa04e9b695840d6fa088e1f8d069f3c916551) Thanks [@jxom](https://github.com/jxom)! - Fix issue where `useProvider` & `useWebSocketProvider` would not update when `chainId` config changes + +* [#739](https://github.com/wevm/wagmi/pull/739) [`c2295a5`](https://github.com/wevm/wagmi/commit/c2295a56cc86d02cc6602e2b4557b8ab9a091a3f) Thanks [@tmm](https://github.com/tmm)! - Fix balance formatting for tokens that do not have 18 decimals. + +- [#759](https://github.com/wevm/wagmi/pull/759) [`959953d`](https://github.com/wevm/wagmi/commit/959953d1f5b3e8189bac56de245c62333470d18e) Thanks [@tmm](https://github.com/tmm)! - Added `useTransaction` hook: + + ```ts + import { useTransaction } from "wagmi"; + + const result = useTransaction({ + hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", + }); + ``` + +- Updated dependencies [[`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`d8af6bf`](https://github.com/wevm/wagmi/commit/d8af6bf50885aec110ae4d64716642453aa27896), [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`c2295a5`](https://github.com/wevm/wagmi/commit/c2295a56cc86d02cc6602e2b4557b8ab9a091a3f), [`ac3b9b8`](https://github.com/wevm/wagmi/commit/ac3b9b87f80cb45b65d003f09d916d7d1427a62e), [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`d70c115`](https://github.com/wevm/wagmi/commit/d70c115131f299fb61f87867b6ac4218e0bcf432), [`959953d`](https://github.com/wevm/wagmi/commit/959953d1f5b3e8189bac56de245c62333470d18e)]: + - @wagmi/core@0.5.0 + +## 0.5.11 + +### Patch Changes + +- [`4c7d123`](https://github.com/wevm/wagmi/commit/4c7d123c8e74f93e770096857eb8c40ce0a47681) Thanks [@jxom](https://github.com/jxom)! - Fix issue where `useProvider` & `useWebSocketProvider` would not update when `chainId` config changes + +## 0.5.10 + +### Patch Changes + +- [#713](https://github.com/tmm/wagmi/pull/713) [`08b0113`](https://github.com/tmm/wagmi/commit/08b0113bef9f32dceab2ecd823b1ee00f9bdc45d) Thanks [@jxom](https://github.com/jxom)! - Fix an issue where the `useContractRead` query function could return `undefined` instead of a serializable `null`. + +* [#725](https://github.com/tmm/wagmi/pull/725) [`b976920`](https://github.com/tmm/wagmi/commit/b97692051d778d7e112872663832c97963a8029a) Thanks [@tmm](https://github.com/tmm)! - Lock `react-query` version + +- [#721](https://github.com/tmm/wagmi/pull/721) [`abea25f`](https://github.com/tmm/wagmi/commit/abea25fd15d81d1ecaec9d3fbd687042ab29b1e6) Thanks [@tmm](https://github.com/tmm)! - Add `name` to `useToken` `data` value. + +- Updated dependencies [[`abea25f`](https://github.com/tmm/wagmi/commit/abea25fd15d81d1ecaec9d3fbd687042ab29b1e6), [`abea25f`](https://github.com/tmm/wagmi/commit/abea25fd15d81d1ecaec9d3fbd687042ab29b1e6)]: + - @wagmi/core@0.4.9 + +## 0.5.9 + +### Patch Changes + +- [#677](https://github.com/tmm/wagmi/pull/677) [`35e4219`](https://github.com/tmm/wagmi/commit/35e42199af9dd346549c1718e144728f55b8d7dd) Thanks [@jxom](https://github.com/jxom)! - Move `parseContractResult` to `@wagmi/core` + +* [#677](https://github.com/tmm/wagmi/pull/677) [`35e4219`](https://github.com/tmm/wagmi/commit/35e42199af9dd346549c1718e144728f55b8d7dd) Thanks [@jxom](https://github.com/jxom)! - Parse tuples correctly in `parseContractResult` + +* Updated dependencies [[`35e4219`](https://github.com/tmm/wagmi/commit/35e42199af9dd346549c1718e144728f55b8d7dd)]: + - @wagmi/core@0.4.7 + +## 0.5.8 + +### Patch Changes + +- [#670](https://github.com/tmm/wagmi/pull/670) [`29a0d21`](https://github.com/tmm/wagmi/commit/29a0d21ee83995559f63542778dfa805f15e7441) Thanks [@tmm](https://github.com/tmm)! - Fix broken release not containing `deepEqual` from `@wagmi/core`. + +- Updated dependencies [[`29a0d21`](https://github.com/tmm/wagmi/commit/29a0d21ee83995559f63542778dfa805f15e7441)]: + - @wagmi/core@0.4.6 + +## 0.5.7 + +### Patch Changes + +- [#659](https://github.com/tmm/wagmi/pull/659) [`be76586`](https://github.com/tmm/wagmi/commit/be76586431238dc5a0970a6f10a3dff9faa8ca2d) Thanks [@jxom](https://github.com/jxom)! - Added an `isDataEqual` config option to `useContractRead`, `useContractReads` & `useContractInfiniteReads` to define whether or not that data has changed. Defaults to `deepEqual`. + +* [#659](https://github.com/tmm/wagmi/pull/659) [`be76586`](https://github.com/tmm/wagmi/commit/be76586431238dc5a0970a6f10a3dff9faa8ca2d) Thanks [@jxom](https://github.com/jxom)! - Added `onBlock` config to `useBlockNumber` + +- [#659](https://github.com/tmm/wagmi/pull/659) [`be76586`](https://github.com/tmm/wagmi/commit/be76586431238dc5a0970a6f10a3dff9faa8ca2d) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue where `useContractRead` & `useContractReads` would return unstable data. + +## 0.5.6 + +### Patch Changes + +- [#654](https://github.com/tmm/wagmi/pull/654) [`e66530b`](https://github.com/tmm/wagmi/commit/e66530bf4881b3533c528f8c5a5f41be0eab0a64) Thanks [@jxom](https://github.com/jxom)! - omit `contractInterface` from `useContractRead` & `useContractReads` query key hash + +* [#654](https://github.com/tmm/wagmi/pull/654) [`e66530b`](https://github.com/tmm/wagmi/commit/e66530bf4881b3533c528f8c5a5f41be0eab0a64) Thanks [@jxom](https://github.com/jxom)! - fix `multicall` returning nullish data for all calls unexpectedly + +* Updated dependencies [[`e66530b`](https://github.com/tmm/wagmi/commit/e66530bf4881b3533c528f8c5a5f41be0eab0a64)]: + - @wagmi/core@0.4.5 + +## 0.5.5 + +### Patch Changes + +- [#629](https://github.com/tmm/wagmi/pull/629) [`199db71`](https://github.com/tmm/wagmi/commit/199db7165eed43d36cb882d373f95e7c49212f23) Thanks [@jxom](https://github.com/jxom)! - Add `wagmi/actions` entrypoint that exports imperative `@wagmi/core` actions + +* [#616](https://github.com/tmm/wagmi/pull/616) [`7a7a17a`](https://github.com/tmm/wagmi/commit/7a7a17a46d4c9e6465cc46a111b5fe8a56109f1b) Thanks [@tmm](https://github.com/tmm)! - Adds `UNSTABLE_shimOnConnectSelectAccount` flag. With this flag and "disconnected" with `shimDisconnect` enabled, the user is prompted to select a different MetaMask account (than the currently connected account) when trying to connect (e.g. `useConnect`/`connect` action). + +* Updated dependencies [[`7a7a17a`](https://github.com/tmm/wagmi/commit/7a7a17a46d4c9e6465cc46a111b5fe8a56109f1b)]: + - @wagmi/core@0.4.4 + +## 0.5.4 + +### Patch Changes + +- [#631](https://github.com/tmm/wagmi/pull/631) [`a780e32`](https://github.com/tmm/wagmi/commit/a780e32e91a0072c795fa0b5a6111302768e2a01) Thanks [@tmm](https://github.com/tmm)! - Fix WalletConnect stale session + +- Updated dependencies [[`a780e32`](https://github.com/tmm/wagmi/commit/a780e32e91a0072c795fa0b5a6111302768e2a01)]: + - @wagmi/core@0.4.3 + +## 0.5.3 + +### Patch Changes + +- [#627](https://github.com/tmm/wagmi/pull/627) [`5985530`](https://github.com/tmm/wagmi/commit/59855301d138313e83a607b3f05053e9f46a78a8) Thanks [@jxom](https://github.com/jxom)! - equalityFn in `useSyncExternalStoreWithTracked` should return truthy when there are no tracked keys. + +## 0.5.2 + +### Patch Changes + +- [#624](https://github.com/tmm/wagmi/pull/624) [`416fa7e`](https://github.com/tmm/wagmi/commit/416fa7ee1f8019ab86e33fb93783ffddecc02c49) Thanks [@jxom](https://github.com/jxom)! - Fix broken `WebSocketProvider` type defs + +- Updated dependencies [[`416fa7e`](https://github.com/tmm/wagmi/commit/416fa7ee1f8019ab86e33fb93783ffddecc02c49)]: + - @wagmi/core@0.4.2 + +## 0.5.1 + +### Patch Changes + +- [#622](https://github.com/tmm/wagmi/pull/622) [`d171581`](https://github.com/tmm/wagmi/commit/d171581464891dd870d97b6232205da0cb152d9b) Thanks [@tmm](https://github.com/tmm)! - Use `domain.chainId` to validate and switch chain before signing in `useSignTypedData`. + +* [#618](https://github.com/tmm/wagmi/pull/618) [`a5138e8`](https://github.com/tmm/wagmi/commit/a5138e82a00e4d9469ad78c97b2d34200d7f1fbe) Thanks [@tmm](https://github.com/tmm)! - Fix adding chains when using MetaMask mobile app, add `publicRpcUrls` constant, and default to public endpoint when adding chain. + +* Updated dependencies [[`d171581`](https://github.com/tmm/wagmi/commit/d171581464891dd870d97b6232205da0cb152d9b), [`a5138e8`](https://github.com/tmm/wagmi/commit/a5138e82a00e4d9469ad78c97b2d34200d7f1fbe)]: + - @wagmi/core@0.4.1 + +## 0.5.0 + +### Minor Changes + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `useContractWrite` hook parameters have been consolidated into a singular config parameter. + + Before: + + ```tsx + useContractWrite( + { + addressOrName: mlootContractAddress, + contractInterface: mlootABI, + }, + "claim", + ); + ``` + + After: + + ```tsx + useContractWrite({ + addressOrName: mlootContractAddress, + contractInterface: mlootABI, + functionName: "claim", + }); + ``` + +* [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `useContractEvent` hook parameters have been consolidated into a singular config parameter. + + Before: + + ```tsx + useContractEvent( + { + addressOrName: uniContractAddress, + contractInterface: erc20ABI, + }, + 'Transfer', + listener, + ), + ``` + + After: + + ```tsx + useContractEvent({ + addressOrName: uniContractAddress, + contractInterface: erc20ABI, + eventName: "Transfer", + listener, + }); + ``` + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `client` prop is now required on `WagmiConfig`. + + ````diff + ```tsx + import { + createClient, + + configureChains, + + defaultChains + } from 'wagmi' + +import { publicProvider } from 'wagmi/providers/public' + + +const { provider, webSocketProvider } = configureChains(defaultChains, [ + + publicProvider(), + +]) + + +const client = createClient({ + + provider, + + webSocketProvider, + +}) + + function App() { + return ( + + + + ) + } + ```` + +* [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `provider` config option is now required on `createClient`. It is recommended to pass the [`provider` given from `configureChains`](https://wagmi.sh/docs/providers/configuring-chains). + + ```diff + import { + createClient, + + defaultChains, + + configureChains + } from 'wagmi' + +import { publicProvider } from 'wagmi/providers/publicProvider' + + +const { provider } = configureChains(defaultChains, [ + + publicProvider + +]) + + const client = createClient({ + + provider + }) + ``` + + If you previously used an ethers.js Provider, you now need to provide your `chains` on the Provider instance: + + ```diff + import { + createClient, + + defaultChains + } from 'wagmi' + import ethers from 'ethers' + + const client = createClient({ + - provider: getDefaultProvider() + + provider: Object.assign(getDefaultProvider(), { chains: defaultChains }) + }) + ``` + +- [`4f8f3c0`](https://github.com/tmm/wagmi/commit/4f8f3c0d65383bd8bbdfc3f1033adfdb11d80ebb) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Use ethereum-lists chains symbols + +* [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The following changes were made to the `useAccount` return value: + + ### The `data` value is now `address` & `connector` + + ```diff + { + - data?: { + - address: string + - connector: Connector + - } + + address?: string + + connector?: Connector + } + ``` + + ### Global connection status values have been added + + The following global connection status values have been added: + + ```diff + { + + isConnecting: boolean + + isReconnecting: boolean + + isConnected: boolean + + isDisconnected: boolean + + status: 'connecting' | 'reconnecting' | 'connected' | 'disconnected' + } + ``` + + The `useAccount` hook is now aware of any connection event in your application, so now you can use these connection status values to determine if your user is connected, disconnected or connecting to a wallet on a global scope. + + ### `error`, states & `refetch` values have been removed + + Since the `useAccount` hook never dealt with asynchronous data, all of these values were + redundant & unused. + + ```diff + { + - error?: Error + - isIdle: boolean + - isLoading: boolean + - isFetching: boolean + - isSuccess: boolean + - isError: boolean + - isFetched: boolean + - isRefetching: boolean + - refetch: (options: { + - throwOnError: boolean + - cancelRefetch: boolean + - }) => Promise<{ + - address: string + - connector: Connector + - }> + - status: 'idle' | 'error' | 'loading' | 'success' + } + ``` + + ### Summary of changes + + Below is the whole diff of changes to the `useAccount` return value. + + ```diff + { + - data?: { + - address: string + - connector: Connector + - } + + address?: string + + connector?: Connector + - error?: Error + - isIdle: boolean + - isLoading: boolean + - isFetching: boolean + - isSuccess: boolean + - isError: boolean + - isFetched: boolean + - isRefetching: boolean + + isConnecting: boolean + + isReconnecting: boolean + + isConnected: boolean + + isDisconnected: boolean + - refetch: (options: { + - throwOnError: boolean + - cancelRefetch: boolean + - }) => Promise<{ + - address: string + - connector: Connector + - }> + - status: 'idle' | 'error' | 'loading' | 'success' + + status: 'connecting' | 'reconnecting' | 'connected' | 'disconnected' + } + ``` + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking:** Removed the `chainId` parameter from `connectors` function on `createClient`. + + ```diff + const client = createClient({ + - connectors({ chainId }) { + + connectors() { + ... + } + }) + ``` + + If you previously derived RPC URLs from the `chainId` on `connectors`, you can now remove that logic as `wagmi` now handles RPC URLs internally when used with `configureChains`. + + ```diff + import { + chain, + + configureChains, + createClient + } from 'wagmi'; + + +import { publicProvider } from 'wagmi/providers/public' + + import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet' + import { InjectedConnector } from 'wagmi/connectors/injected' + import { MetaMaskConnector } from 'wagmi/connectors/metaMask' + import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' + + +const { chains } = configureChains( + + [chain.mainnet], + + [publicProvider()] + +); + + const client = createClient({ + - connectors({ chainId }) { + - const chain = chains.find((x) => x.id === chainId) ?? defaultChain + - const rpcUrl = chain.rpcUrls.alchemy + - ? `${chain.rpcUrls.alchemy}/${alchemyId}` + - : chain.rpcUrls.default + - return [ + + connectors: [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: 'wagmi', + - chainId: chain.id, + - jsonRpcUrl: rpcUrl, + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + - rpc: { [chain.id]: rpcUrl }, + }, + }), + new InjectedConnector({ + chains, + options: { name: 'Injected' }, + }), + ] + - }, + }) + ``` + +* [#596](https://github.com/tmm/wagmi/pull/596) [`a770af7`](https://github.com/tmm/wagmi/commit/a770af7d2cb214b6620d5341115f1e938e1e77ff) Thanks [@tmm](https://github.com/tmm)! - **Breaking**: `TypedDataDomain` and `TypedDataField` types were removed and incorporated into `SignTypedDataArgs`. + +- [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The following changes were made to the `useAccount` configuration: + + ## `onConnect` has been added + + The `onConnect` callback is invoked when the account connects. + + It provides the connected address & connector, as well as a `isReconnected` flag for if the user reconnected via `autoConnect`. + + ```tsx + const account = useAccount({ + onConnect({ address, connector, isReconnected }) { + console.log("Connected"); + }, + }); + ``` + + ## `onDisconnect` has been added + + The `onDisconnect` callback is invoked when the account disconnected. + + ```tsx + const account = useAccount({ + onDisconnect() { + console.log("Disconnected"); + }, + }); + ``` + + ## `suspense` has been removed + + The `useAccount` hook is a synchronous hook – so `suspense` never worked. + + ```diff + const account = useAccount({ + - suspense: true, + }) + ``` + + ## `onError` has been removed + + The `useAccount` hook never had any error definitions – so `onError` was never invoked. + + ```diff + const account = useAccount({ + - onError(error) { + - console.log('Error', error) + - }, + }) + ``` + + ## `onSettled` has been removed + + The `useAccount` hook is a synchronous hook. `onSettled` was always invoked immediately. + + ```diff + const account = useAccount({ + - onSettled(data) { + - console.log('Settled', data) + - }, + }) + ``` + + If you used `onSettled`, you can move the code beneath the `useAccount` hook: + + ```diff + const account = useAccount({ + - onSettled(data) { + - console.log('Address:', data.address) + - }, + }) + + console.log('Address:', account.address) + ``` + + ## `onSuccess` has been removed + + The `useAccount` hook is a synchronous hook. `onSuccess` was always invoked immediately. + + ```diff + const account = useAccount({ + - onSuccess(data) { + - console.log('Success', data) + - }, + }) + ``` + + If you used `onSuccess`, you can move the code beneath the `useAccount` hook: + + ```diff + const account = useAccount({ + - onSuccess(data) { + - console.log('Address:', data.address) + - }, + }) + + console.log('Address:', account.address) + ``` + +* [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The following changes were made to the `useConnect` return value: + + ### Connection status flags have been moved + + The `isConnected`, `isConnecting`, `isReconnecting` & `isDisconnected` flags have been moved to the `useAccount` hook. + + ```diff + -import { useConnect } from 'wagmi' + +import { useAccount } from 'wagmi' + + function App() { + const { + isConnected, + isConnecting, + isConnecting, + isDisconnected + - } = useConnect() + + } = useAccount() + } + ``` + + ### New `connect` mutation status flags have been added + + The `isLoading`, `isSuccess` and `isError` flags have been added to `useConnect`. + + These flags represent the **local** async state of `useConnect`. + + ### `activeConnector` has been removed + + The `activeConnector` value has been removed. You can find the active connector on `useAccount`. + + ```diff + -import { useConnect } from 'wagmi' + +import { useAccount } from 'wagmi' + + function App() { + - const { activeConnector } = useConnect() + + const { connector } = useAccount() + } + ``` + +- [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The following changes were made to the `useConnect` configuration: + + ### `onBeforeConnect` has been renamed + + The `onBeforeConnect` callback has been renamed to `onMutate` + + ### `onConnect` has been renamed + + The `onConnect` callback has been renamed to `onSuccess` + +* [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `connector` parameter to `connect` & `connectAsync` now has to be in the config object parameter shape. + + ```diff + import { useConnect } from 'wagmi' + + function App() { + const { connect, connectors } = useConnect() + + return ( + + ) + } + ``` + +- [#582](https://github.com/tmm/wagmi/pull/582) [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The "switch network" functionality has been moved out of `useNetwork` into a new `useSwitchNetwork` hook. + + The `useNetwork` hook now accepts no configuration and only returns `chain` (renamed from `activeChain`) and `chains`. + + ```diff + import { + useNetwork + + useSwitchNetwork + } from 'wagmi' + + const { + - activeChain + + chain, + chains, + - data, + - error, + - isError, + - isIdle, + - isLoading, + - isSuccess, + - pendingChainId, + - switchNetwork, + - switchNetworkAsync, + - status, + - reset, + -} = useNetwork({ + - chainId: 69, + - onError(error) {}, + - onMutate(args) {}, + - onSettled(data, error) {}, + - onSuccess(data) {} + -}) + +} = useNetwork() + + +const { + + data, + + error, + + isError, + + isIdle, + + isLoading, + + isSuccess, + + pendingChainId, + + switchNetwork, + + switchNetworkAsync, + + status, + + reset, + +} = useSwitchNetwork({ + + chainId: 69, + + onError(error) {}, + + onMutate(args) {}, + + onSettled(data, error) {}, + + onSuccess(data) {} + +}) + ``` + +* [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - **Breaking**: The `useContractRead` hook parameters have been consolidated into a singular config parameter. + + Before: + + ```tsx + useContractRead( + { + addressOrName: wagmigotchiContractAddress, + contractInterface: wagmigotchiABI, + }, + "love", + { args: "0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c" }, + ); + ``` + + After: + + ```tsx + useContractRead({ + addressOrName: wagmigotchiContractAddress, + contractInterface: wagmigotchiABI, + functionName: "love", + args: "0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c", + }); + ``` + +### Patch Changes + +- [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - Added a `useContractInfiniteReads` hook that provides the ability to call multiple ethers Contract read-only methods with "infinite scrolling" ("fetch more") support. Useful for rendering a dynamic list of contract data. + + [Learn more](https://wagmi.sh/docs/hooks/useContractInfiniteReads) + +* [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4) Thanks [@jxom](https://github.com/jxom)! - Added a `useContractReads` hook that provides the ability to batch up multiple ethers Contract read-only methods. + + [Learn more](https://wagmi.sh/docs/hooks/useContractReads) + +- [#598](https://github.com/tmm/wagmi/pull/598) [`fef26bf`](https://github.com/tmm/wagmi/commit/fef26bf8aef76fc9621e3cd54d4e0ca8f69abb38) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Update `@coinbase/wallet-sdk` to fix errors when connecting with older versions of the Coinbase Wallet extension and mobile app. + +* [#611](https://github.com/tmm/wagmi/pull/611) [`3089c34`](https://github.com/tmm/wagmi/commit/3089c34196d4034acabac031e0a2f7ee63ae30cc) Thanks [@tmm](https://github.com/tmm)! - Added `chainId` config parameter for `useContractWrite` and `useSendTransaction`. + + If `chainId` is provided, the connector will validate that `chainId` is the active chain before sending a transaction (and switch to `chainId` if necessary). + +* Updated dependencies [[`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4), [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4), [`4f8f3c0`](https://github.com/tmm/wagmi/commit/4f8f3c0d65383bd8bbdfc3f1033adfdb11d80ebb), [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4), [`3089c34`](https://github.com/tmm/wagmi/commit/3089c34196d4034acabac031e0a2f7ee63ae30cc), [`a770af7`](https://github.com/tmm/wagmi/commit/a770af7d2cb214b6620d5341115f1e938e1e77ff), [`4f8f3c0`](https://github.com/tmm/wagmi/commit/4f8f3c0d65383bd8bbdfc3f1033adfdb11d80ebb), [`fef26bf`](https://github.com/tmm/wagmi/commit/fef26bf8aef76fc9621e3cd54d4e0ca8f69abb38), [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4), [`3089c34`](https://github.com/tmm/wagmi/commit/3089c34196d4034acabac031e0a2f7ee63ae30cc), [`b03830a`](https://github.com/tmm/wagmi/commit/b03830a54465215c2526f9509543fe2c978bfe70), [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4), [`fc94210`](https://github.com/tmm/wagmi/commit/fc94210b67daa91aa164625dfe189d5b6c2f92d4)]: + - @wagmi/core@0.4.0 + +## 0.4.12 + +### Patch Changes + +- [#570](https://github.com/tmm/wagmi/pull/570) [`0e3fe15`](https://github.com/tmm/wagmi/commit/0e3fe15445377f35d6f4142b49bf1c96bfeb62cd) Thanks [@tmm](https://github.com/tmm)! - adds chain for [Foundry](https://github.com/foundry-rs) + +- Updated dependencies [[`0e3fe15`](https://github.com/tmm/wagmi/commit/0e3fe15445377f35d6f4142b49bf1c96bfeb62cd)]: + - @wagmi/core@0.3.8 + +## 0.4.11 + +### Patch Changes + +- [#566](https://github.com/tmm/wagmi/pull/566) [`8713c00`](https://github.com/tmm/wagmi/commit/8713c00f70fcac3afef4ba183e3c87c6d3cbbf65) Thanks [@jxom](https://github.com/jxom)! - Fixed `parseContractResult` breaking `useContractRead` for more complex contract types + +## 0.4.10 + +### Patch Changes + +- [`20a1ab7`](https://github.com/tmm/wagmi/commit/20a1ab7bd02a24c4f1ea02be1bc3ecfbe4abc584) Thanks [@jxom](https://github.com/jxom)! - Updated to `react-query@4.0.0-beta.23` + +* [`20a1ab7`](https://github.com/tmm/wagmi/commit/20a1ab7bd02a24c4f1ea02be1bc3ecfbe4abc584) Thanks [@jxom](https://github.com/jxom)! - Fixed an issue in `useContractRead` where contract structs wouldn't be parsed back to an ethers `Result` correctly. + +## 0.4.9 + +### Patch Changes + +- [#555](https://github.com/tmm/wagmi/pull/555) [`8bf014d`](https://github.com/tmm/wagmi/commit/8bf014d8167e9f9feb1fd91488aab42dd51c92af) Thanks [@tmm](https://github.com/tmm)! - wire up `useEnsName` `chainId` + +## 0.4.8 + +### Patch Changes + +- [#550](https://github.com/tmm/wagmi/pull/550) [`2a5313e`](https://github.com/tmm/wagmi/commit/2a5313e8cbc9ba6335e8e4b85e43862c9b711bd3) Thanks [@tmm](https://github.com/tmm)! - fix `CoinbaseWalletConnector` possible type error + +* [#548](https://github.com/tmm/wagmi/pull/548) [`0c48719`](https://github.com/tmm/wagmi/commit/0c487199f2421f042abc1f1d139468ccbbc5646a) Thanks [@dohaki](https://github.com/dohaki)! - add ensAddress to Chain type + +- [#549](https://github.com/tmm/wagmi/pull/549) [`89b3a74`](https://github.com/tmm/wagmi/commit/89b3a74ead4234daacd0dcf8506659887ebf0553) Thanks [@tmm](https://github.com/tmm)! - Turns on [`noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess=) and [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks=) for better runtime safety. + +- Updated dependencies [[`2a5313e`](https://github.com/tmm/wagmi/commit/2a5313e8cbc9ba6335e8e4b85e43862c9b711bd3), [`0c48719`](https://github.com/tmm/wagmi/commit/0c487199f2421f042abc1f1d139468ccbbc5646a), [`89b3a74`](https://github.com/tmm/wagmi/commit/89b3a74ead4234daacd0dcf8506659887ebf0553)]: + - @wagmi/core@0.3.7 + +## 0.4.7 + +### Patch Changes + +- [#526](https://github.com/tmm/wagmi/pull/526) [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f) Thanks [@jxom](https://github.com/jxom)! - Added `shimChainChangedDisconnect` option to `InjectedConnector`. Defaults to `true` for `MetaMaskConnector`. + +* [#526](https://github.com/tmm/wagmi/pull/526) [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f) Thanks [@jxom](https://github.com/jxom)! - Added `chainId` config option to `useConnect()` & `connect()`. Consumers can now pick what chain they want their user to be connected to. + + Examples: + + ```tsx + import { useConnect, chain } from "wagmi"; + import { InjectedConnector } from "wagmi/connectors/injected"; + + function App() { + const connect = useConnect({ + chainId: chain.polygon.id, + }); + } + ``` + + ```tsx + import { useConnect, chain } from "wagmi"; + import { InjectedConnector } from "wagmi/connectors/injected"; + + function App() { + const connect = useConnect(); + + return ( + + ); + } + ``` + +* Updated dependencies [[`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f), [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f), [`e95c5f9`](https://github.com/tmm/wagmi/commit/e95c5f91859e57d079b962a72d06b93dce004d2f)]: + - @wagmi/core@0.3.6 + +## 0.4.6 + +### Patch Changes + +- [#543](https://github.com/tmm/wagmi/pull/543) [`4d489fd`](https://github.com/tmm/wagmi/commit/4d489fd630dd8c00440bdaf4d646de662c41ff52) Thanks [@tmm](https://github.com/tmm)! - fix fee data formatting for null values + +- Updated dependencies [[`4d489fd`](https://github.com/tmm/wagmi/commit/4d489fd630dd8c00440bdaf4d646de662c41ff52)]: + - @wagmi/core@0.3.5 + +## 0.4.5 + +### Patch Changes + +- [`01cc47b`](https://github.com/tmm/wagmi/commit/01cc47b2385c78d82bc799c2dedacb2a42457e2f) Thanks [@jxom](https://github.com/jxom)! - Update `react-query` to `4.0.0-beta.19` + +## 0.4.4 + +### Patch Changes + +- [`c4deb66`](https://github.com/tmm/wagmi/commit/c4deb6655a52e4cc4e5b3fd82202db11d6106848) Thanks [@jxom](https://github.com/jxom)! - infer `options.chainId` config from `chains` on WalletConnectConnector + +- Updated dependencies [[`c4deb66`](https://github.com/tmm/wagmi/commit/c4deb6655a52e4cc4e5b3fd82202db11d6106848)]: + - @wagmi/core@0.3.4 + +## 0.4.3 + +### Patch Changes + +- [#486](https://github.com/tmm/wagmi/pull/486) [`dbfe3dd`](https://github.com/tmm/wagmi/commit/dbfe3dd320d178d6854a8096101200c1508786bb) Thanks [@tmm](https://github.com/tmm)! - add chains entrypoint + +- Updated dependencies [[`dbfe3dd`](https://github.com/tmm/wagmi/commit/dbfe3dd320d178d6854a8096101200c1508786bb)]: + - @wagmi/core@0.3.3 + +## 0.4.2 + +### Patch Changes + +- [`b1a2e58`](https://github.com/tmm/wagmi/commit/b1a2e5830e325be448bf865aeccda60217fc8d75) Thanks [@jxom](https://github.com/jxom)! - Made the `defaultChains` type generic in `configureChains`. + +## 0.4.1 + +### Patch Changes + +- [#484](https://github.com/tmm/wagmi/pull/484) [`1b9a503`](https://github.com/tmm/wagmi/commit/1b9a5033d51c6655b4f6570c490da6e0e9a29da9) Thanks [@tmm](https://github.com/tmm)! - export React Context + +- Updated dependencies [[`1b9a503`](https://github.com/tmm/wagmi/commit/1b9a5033d51c6655b4f6570c490da6e0e9a29da9)]: + - @wagmi/core@0.3.1 + +## 0.4.0 + +### Minor Changes + +- [#468](https://github.com/tmm/wagmi/pull/468) [`44a884b`](https://github.com/tmm/wagmi/commit/44a884b84171c418f57701e80ef8de972948ef0b) Thanks [@tmm](https://github.com/tmm)! - **Breaking:** Duplicate exports with different names and the same functionality were removed to simplify the public API. In addition, confusing exports were renamed to be more descriptive. + + - `createWagmiClient` alias was removed. Use `createClient` instead. + - `useWagmiClient` alias was removed. Use `useClient` instead. + - `WagmiClient` alias was removed. Use `Client` instead. + - `createWagmiStorage` alias was removed. Use `createStorage` instead. + - `Provider` was renamed and `WagmiProvider` alias was removed. Use `WagmiConfig` instead. + +* [#408](https://github.com/tmm/wagmi/pull/408) [`bfcc3a5`](https://github.com/tmm/wagmi/commit/bfcc3a51bbb1551753e3ccde6af134e9fd4fec9a) Thanks [@jxom](https://github.com/jxom)! - Add `configureChains` API. + + The `configureChains` function allows you to configure your chains with a selected provider (Alchemy, Infura, JSON RPC, Public RPC URLs). This means you don't have to worry about deriving your own RPC URLs for each chain, or instantiating a Ethereum Provider. + + `configureChains` accepts 3 parameters: an array of chains, and an array of providers, and a config object. + + [Learn more about configuring chains & providers.](https://wagmi.sh/docs/providers/configuring-chains) + + ### Before + + ```tsx + import { providers } from "ethers"; + import { chain, createClient, defaultChains } from "wagmi"; + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const chains = defaultChains; + const defaultChain = chain.mainnet; + + const client = createClient({ + autoConnect: true, + connectors({ chainId }) { + const chain = chains.find((x) => x.id === chainId) ?? defaultChain; + const rpcUrl = chain.rpcUrls.alchemy + ? `${chain.rpcUrls.alchemy}/${alchemyId}` + : chain.rpcUrls.default; + return [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + chainId: chain.id, + jsonRpcUrl: rpcUrl, + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + rpc: { [chain.id]: rpcUrl }, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ]; + }, + provider: ({ chainId }) => + new providers.AlchemyProvider(chainId, alchemyId), + }); + ``` + + ### After + + ```tsx + import { chain, createClient, defaultChains } from "wagmi"; + + import { alchemyProvider } from "wagmi/providers/alchemy"; + import { publicProvider } from "wagmi/providers/public"; + + import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet"; + import { InjectedConnector } from "wagmi/connectors/injected"; + import { MetaMaskConnector } from "wagmi/connectors/metaMask"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + + const alchemyId = process.env.ALCHEMY_ID; + + const { chains, provider, webSocketProvider } = configureChains( + defaultChains, + [alchemyProvider({ alchemyId }), publicProvider()], + ); + + const client = createClient({ + autoConnect: true, + connectors: [ + new MetaMaskConnector({ chains }), + new CoinbaseWalletConnector({ + chains, + options: { + appName: "wagmi", + }, + }), + new WalletConnectConnector({ + chains, + options: { + qrcode: true, + }, + }), + new InjectedConnector({ + chains, + options: { + name: "Injected", + shimDisconnect: true, + }, + }), + ], + provider, + webSocketProvider, + }); + ``` + +### Patch Changes + +- [#404](https://github.com/tmm/wagmi/pull/404) [`f81c156`](https://github.com/tmm/wagmi/commit/f81c15665e2e71534f84ada3fa705f2d78627472) Thanks [@holic](https://github.com/holic)! - Add `ProviderRpcError` and `RpcError` error classes. + + Certain wagmi-standardized errors may wrap `ProviderRpcError` or `RpcError`. For these cases, you can access the original provider rpc or rpc error value using the `internal` property. + +* [#459](https://github.com/tmm/wagmi/pull/459) [`72dcf7c`](https://github.com/tmm/wagmi/commit/72dcf7c09e814261b2e43a8fa364c57675c472de) Thanks [@tmm](https://github.com/tmm)! - update dependencies + +- [#447](https://github.com/tmm/wagmi/pull/447) [`b9ebf78`](https://github.com/tmm/wagmi/commit/b9ebf782e0900725bcb76483686b30f09d357ebd) Thanks [@tmm](https://github.com/tmm)! - Fix case where connector disconnected while app was closed and stale data was returned when autoconnecting. For example, [MetaMask was locked](https://github.com/tmm/wagmi/issues/444) when page was closed. + +- Updated dependencies [[`f81c156`](https://github.com/tmm/wagmi/commit/f81c15665e2e71534f84ada3fa705f2d78627472), [`bfcc3a5`](https://github.com/tmm/wagmi/commit/bfcc3a51bbb1551753e3ccde6af134e9fd4fec9a), [`44a884b`](https://github.com/tmm/wagmi/commit/44a884b84171c418f57701e80ef8de972948ef0b), [`72dcf7c`](https://github.com/tmm/wagmi/commit/72dcf7c09e814261b2e43a8fa364c57675c472de), [`a54f3e2`](https://github.com/tmm/wagmi/commit/a54f3e23ea385ed8aa4ad188128d7089ba20f83e), [`b9ebf78`](https://github.com/tmm/wagmi/commit/b9ebf782e0900725bcb76483686b30f09d357ebd), [`bfcc3a5`](https://github.com/tmm/wagmi/commit/bfcc3a51bbb1551753e3ccde6af134e9fd4fec9a)]: + - @wagmi/core@0.3.0 + +## 0.3.5 + +### Patch Changes + +- [`4e03666`](https://github.com/tmm/wagmi/commit/4e03666428d42fc9186c617001b5eb356229677e) Thanks [@tmm](https://github.com/tmm)! - bump dependencies #429 + add imToken support for WC switch chains #432 + fix MetaMask and Brave Wallet collision #436 +- Updated dependencies [[`4e03666`](https://github.com/tmm/wagmi/commit/4e03666428d42fc9186c617001b5eb356229677e)]: + - @wagmi/core@0.2.5 + +## 0.3.4 + +### Patch Changes + +- [#421](https://github.com/tmm/wagmi/pull/421) [`a232b3f`](https://github.com/tmm/wagmi/commit/a232b3ff5cc41e882c4d2a34c599a8cb670edd2b) Thanks [@tmm](https://github.com/tmm)! - fix erc721 abi + +- Updated dependencies [[`a232b3f`](https://github.com/tmm/wagmi/commit/a232b3ff5cc41e882c4d2a34c599a8cb670edd2b)]: + - @wagmi/core@0.2.4 + +## 0.3.3 + +### Patch Changes + +- [#412](https://github.com/tmm/wagmi/pull/412) [`80bef4f`](https://github.com/tmm/wagmi/commit/80bef4ff3f714b0b8f896f1b4b658acc7266299b) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Import providers from `ethers` peer dependency rather than `@ethersproject/providers` to avoid multiple conflicting versions being installed + +- Updated dependencies [[`80bef4f`](https://github.com/tmm/wagmi/commit/80bef4ff3f714b0b8f896f1b4b658acc7266299b)]: + - @wagmi/core@0.2.3 + +## 0.3.2 + +### Patch Changes + +- [`018c2a1`](https://github.com/tmm/wagmi/commit/018c2a11b22ee513571cc7f83fd63f7eb169ee70) Thanks [@tmm](https://github.com/tmm)! - - warn and fallback to default client #380 + + - remove signerOrProvider option from read contract #390 + + - MetaMaskConnector #391 + +- Updated dependencies [[`018c2a1`](https://github.com/tmm/wagmi/commit/018c2a11b22ee513571cc7f83fd63f7eb169ee70)]: + - @wagmi/core@0.2.2 + +## 0.3.1 + +### Patch Changes + +- [`afc4607`](https://github.com/tmm/wagmi/commit/afc46071e91601ab8a2b465524da796cd60b6ad4) Thanks [@tmm](https://github.com/tmm)! - - Fix time scaling e9593df + - Use fully-specified path for use-sync-external-store import 7b235c1 + - Update serialize 236fc17 +- Updated dependencies [[`afc4607`](https://github.com/tmm/wagmi/commit/afc46071e91601ab8a2b465524da796cd60b6ad4)]: + - @wagmi/core@0.2.1 + +## 0.3.0 + +### Minor Changes + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - don't persist account data when `autoConnect` is falsy + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - fix(@wagmi/core): persist connector chains to local storage + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - Favour `message` event over `connecting` event to conform to EIP-1193 + - Export `useWaitForTransaction` + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - force address to be required in `useAccount` + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Initial 0.3.0 release + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Add `cacheOnBlock` config for `useContractRead` + Update `react-query` to v4 + Fix `watchBlockNumber` listener leak + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - - fix `useContractWrite` mutation fn arguments + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - Update react-query to 4.0.0-beta.5 + +### Patch Changes + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - add chainId to actions and hooks + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - showtime + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - improve type support for ethers providers + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update zustand + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update babel target + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - update block explorers and rpc urls structure + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - keep previous data when watching + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - republish + +- [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - fix stale connectors when switching chains + +* [#311](https://github.com/tmm/wagmi/pull/311) [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d) Thanks [@tmm](https://github.com/tmm)! - last beta + +* Updated dependencies [[`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d), [`24ce011`](https://github.com/tmm/wagmi/commit/24ce0113022b890e9582c6cc24035926e0d2b32d)]: + - @wagmi/core@0.2.0 + +## 0.3.0-next.21 + +### Patch Changes + +- showtime + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.18 + +## 0.3.0-next.20 + +### Patch Changes + +- update block explorers and rpc urls structure + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.17 + +## 0.3.0-next.19 + +### Minor Changes + +- Update react-query to 4.0.0-beta.5 + +## 0.3.0-next.18 + +### Patch Changes + +- last beta + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.16 + +## 0.3.0-next.17 + +### Patch Changes + +- update zustand + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.15 + +## 0.3.0-next.16 + +### Minor Changes + +- Add `cacheOnBlock` config for `useContractRead` +- Update `react-query` to v4 +- Fix `watchBlockNumber` listener leak + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.14 + +## 0.3.0-next.15 + +### Patch Changes + +- keep previous data when watching + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.13 + +## 0.3.0-next.14 + +### Patch Changes + +- add chainId to actions and hooks + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.12 + +## 0.3.0-next.13 + +### Patch Changes + +- fix stale connectors when switching chains + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.11 + +## 0.3.0-next.12 + +### Patch Changes + +- republish + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.10 + +## 0.3.0-next.11 + +### Patch Changes + +- improve type support for ethers providers + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.9 + +## 0.3.0-next.10 + +### Patch Changes + +- update babel target + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.8 + +## 0.3.0-next.9 + +### Minor Changes + +- - Favour `message` event over `connecting` event to conform to EIP-1193 + - Export `useWaitForTransaction` + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.7 + +## 0.3.0-next.8 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.6 + +## 0.3.0-next.7 + +### Minor Changes + +- don't persist account data when `autoConnect` is falsy + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.5 + +## 0.3.0-next.6 + +### Minor Changes + +- fix `useContractWrite` mutation fn arguments + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.4 + +## 0.3.0-next.5 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.3 + +## 0.3.0-next.4 + +### Minor Changes + +- force address to be required in `useAccount` + +## 0.3.0-next.3 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.2 + +## 0.3.0-next.2 + +### Minor Changes + +- Initial 0.3.0 release + +### Patch Changes + +- Updated dependencies []: + - @wagmi/core@0.2.0-next.1 + +## 0.2.28 + +### Patch Changes + +- [`747d895`](https://github.com/tmm/wagmi/commit/747d895a54b562958afde34b1d34e81ab5039e2c) Thanks [@tmm](https://github.com/tmm)! - add warning to WalletLinkConnector + +- Updated dependencies [[`747d895`](https://github.com/tmm/wagmi/commit/747d895a54b562958afde34b1d34e81ab5039e2c)]: + - wagmi-core@0.1.22 + +## 0.2.27 + +### Patch Changes + +- [`c858c51`](https://github.com/tmm/wagmi/commit/c858c51b44d9039f1d0db5bcf016639f47d1931f) Thanks [@tmm](https://github.com/tmm)! - update coinbase connector + +- Updated dependencies [[`c858c51`](https://github.com/tmm/wagmi/commit/c858c51b44d9039f1d0db5bcf016639f47d1931f)]: + - wagmi-core@0.1.21 + +## 0.2.26 + +### Patch Changes + +- Updated dependencies [[`36e6989`](https://github.com/tmm/wagmi/commit/36e69894f4c27aaad7fb6d678476c8bb870244bb)]: + - wagmi-core@0.1.20 + +## 0.2.25 + +### Patch Changes + +- [`d467df6`](https://github.com/tmm/wagmi/commit/d467df6374210dbc4b016788b4beb4fded54cb4d) Thanks [@tmm](https://github.com/tmm)! - fix global type leaking + +- Updated dependencies [[`d467df6`](https://github.com/tmm/wagmi/commit/d467df6374210dbc4b016788b4beb4fded54cb4d)]: + - wagmi-core@0.1.19 + +## 0.2.24 + +### Patch Changes + +- [#294](https://github.com/tmm/wagmi/pull/294) [`1d253f3`](https://github.com/tmm/wagmi/commit/1d253f3a59b61d24c88d25c99decd84a6c734e5d) Thanks [@tmm](https://github.com/tmm)! - change babel target + +- Updated dependencies [[`1d253f3`](https://github.com/tmm/wagmi/commit/1d253f3a59b61d24c88d25c99decd84a6c734e5d)]: + - wagmi-core@0.1.18 + +## 0.2.23 + +### Patch Changes + +- [#292](https://github.com/tmm/wagmi/pull/292) [`53c9be1`](https://github.com/tmm/wagmi/commit/53c9be17ee0c2ae6b8f34f2351b8858257b3f5f2) Thanks [@tmm](https://github.com/tmm)! - fix private fields + +- Updated dependencies [[`53c9be1`](https://github.com/tmm/wagmi/commit/53c9be17ee0c2ae6b8f34f2351b8858257b3f5f2)]: + - wagmi-core@0.1.17 + +## 0.2.22 + +### Patch Changes + +- [`79a2499`](https://github.com/tmm/wagmi/commit/79a249989029f818c32c0e84c0dd2c75e8aa990a) Thanks [@tmm](https://github.com/tmm)! - update build target to es2021 + +- Updated dependencies [[`79a2499`](https://github.com/tmm/wagmi/commit/79a249989029f818c32c0e84c0dd2c75e8aa990a)]: + - wagmi-core@0.1.16 + +## 0.2.21 + +### Patch Changes + +- [`f9790b5`](https://github.com/tmm/wagmi/commit/f9790b55600df09c77bb8ca349c5a3457df1b07c) Thanks [@tmm](https://github.com/tmm)! - fix WalletConnect issue + +- Updated dependencies [[`f9790b5`](https://github.com/tmm/wagmi/commit/f9790b55600df09c77bb8ca349c5a3457df1b07c)]: + - wagmi-core@0.1.15 + +## 0.2.20 + +### Patch Changes + +- [`fed29fb`](https://github.com/tmm/wagmi/commit/fed29fb4714abe234e2dabb63782cfc4439a10cf) Thanks [@tmm](https://github.com/tmm)! - export useSignTypedData + +## 0.2.19 + +### Patch Changes + +- [`6987278`](https://github.com/tmm/wagmi/commit/69872786e0b54b89a20945cc5471c1f4675b0a68) Thanks [@tmm](https://github.com/tmm)! - add useSignedTypeData + +## 0.2.18 + +### Patch Changes + +- [#236](https://github.com/tmm/wagmi/pull/236) [`53bad61`](https://github.com/tmm/wagmi/commit/53bad615788764e31121678083c382c1bd042fe8) Thanks [@markdalgleish](https://github.com/markdalgleish)! - Updated `@walletconnect/ethereum-provider` to [v1.7.4](https://github.com/WalletConnect/walletconnect-monorepo/releases/tag/1.7.4) + +- Updated dependencies [[`53bad61`](https://github.com/tmm/wagmi/commit/53bad615788764e31121678083c382c1bd042fe8)]: + - wagmi-core@0.1.14 + +## 0.2.17 + +### Patch Changes + +- [`8e9412a`](https://github.com/tmm/wagmi/commit/8e9412af71958301ae2f9748febb936e79900aa0) Thanks [@tmm](https://github.com/tmm)! - bump walletlink + +- Updated dependencies [[`8e9412a`](https://github.com/tmm/wagmi/commit/8e9412af71958301ae2f9748febb936e79900aa0)]: + - wagmi-core@0.1.13 + +## 0.2.16 + +### Patch Changes + +- [#210](https://github.com/tmm/wagmi/pull/210) [`684468a`](https://github.com/tmm/wagmi/commit/684468aee3e42a1ce2b4b599f3f17d1819213de8) Thanks [@tmm](https://github.com/tmm)! - update chains to match chainslist.org + +- Updated dependencies [[`684468a`](https://github.com/tmm/wagmi/commit/684468aee3e42a1ce2b4b599f3f17d1819213de8)]: + - wagmi-core@0.1.12 + +## 0.2.15 + +### Patch Changes + +- [#195](https://github.com/tmm/wagmi/pull/195) [`25b6083`](https://github.com/tmm/wagmi/commit/25b6083a662a0236794d1765343467691421c14b) Thanks [@tmm](https://github.com/tmm)! - rename wagmi-private to wagmi-core + +- Updated dependencies [[`25b6083`](https://github.com/tmm/wagmi/commit/25b6083a662a0236794d1765343467691421c14b)]: + - wagmi-core@0.1.11 + +## 0.2.14 + +### Patch Changes + +- [#192](https://github.com/tmm/wagmi/pull/192) [`428cedb`](https://github.com/tmm/wagmi/commit/428cedb3dec4e3e4b9f4559c8e65932e05f94e05) Thanks [@tmm](https://github.com/tmm)! - rename core and testing packages + +- Updated dependencies [[`428cedb`](https://github.com/tmm/wagmi/commit/428cedb3dec4e3e4b9f4559c8e65932e05f94e05)]: + - wagmi-core@0.1.10 + +## 0.2.13 + +### Patch Changes + +- [#190](https://github.com/tmm/wagmi/pull/190) [`7034bb8`](https://github.com/tmm/wagmi/commit/7034bb868412b9f481b206371280e84c2d52706d) Thanks [@tmm](https://github.com/tmm)! - add shim for metamask chain changed to prevent disconnect + +- Updated dependencies [[`7034bb8`](https://github.com/tmm/wagmi/commit/7034bb868412b9f481b206371280e84c2d52706d)]: + - wagmi-private@0.1.9 + +## 0.2.12 + +### Patch Changes + +- [`566b47f`](https://github.com/tmm/wagmi/commit/566b47f53c80e1cdcc368d43c53b1772eeb5be20) Thanks [@tmm](https://github.com/tmm)! - fix contract read skip option + +## 0.2.11 + +### Patch Changes + +- [`09f0719`](https://github.com/tmm/wagmi/commit/09f071947012e3133362a7eb80c0f39356899190) Thanks [@tmm](https://github.com/tmm)! - - safe state updates h/t @bpierre + - add useEnsResolveName hook h/t @shunkakinoki + +## 0.2.10 + +### Patch Changes + +- [#159](https://github.com/tmm/wagmi/pull/159) [`981438d`](https://github.com/tmm/wagmi/commit/981438d527fb6b5f025dd9bb405fa9e7a2751597) Thanks [@markdalgleish](https://github.com/markdalgleish)! - add `WagmiProvider` alias for `Provider` + +## 0.2.9 + +### Patch Changes + +- [#137](https://github.com/tmm/wagmi/pull/137) [`dceeb43`](https://github.com/tmm/wagmi/commit/dceeb430d9021fbf98366859cb1cd0149e80c55c) Thanks [@tmm](https://github.com/tmm)! - add siwe guide + +- Updated dependencies [[`dceeb43`](https://github.com/tmm/wagmi/commit/dceeb430d9021fbf98366859cb1cd0149e80c55c)]: + - wagmi-private@0.1.8 + +## 0.2.8 + +### Patch Changes + +- [`b49cb89`](https://github.com/tmm/wagmi/commit/b49cb89ef59289ee1185eafab427d3ab55c17c25) Thanks [@tmm](https://github.com/tmm)! - refactor contract read/write hook state + +## 0.2.7 + +### Patch Changes + +- [`7132631`](https://github.com/tmm/wagmi/commit/713263159899feb257c11614716f0af4f6b06a14) Thanks [@tmm](https://github.com/tmm)! - update contract read and write state + +## 0.2.6 + +### Patch Changes + +- [#127](https://github.com/tmm/wagmi/pull/127) [`f05b031`](https://github.com/tmm/wagmi/commit/f05b0310f7f7e6447e9b6c81cedbb27dcf2f3649) Thanks [@tmm](https://github.com/tmm)! - update switch chain return type + +- Updated dependencies [[`f05b031`](https://github.com/tmm/wagmi/commit/f05b0310f7f7e6447e9b6c81cedbb27dcf2f3649)]: + - wagmi-private@0.1.7 + +## 0.2.5 + +### Patch Changes + +- [`1412eed`](https://github.com/tmm/wagmi/commit/1412eed0d1494bb4f8c6845a0e890f79e4e68e03) Thanks [@tmm](https://github.com/tmm)! - add frame to injected + +- Updated dependencies [[`1412eed`](https://github.com/tmm/wagmi/commit/1412eed0d1494bb4f8c6845a0e890f79e4e68e03)]: + - wagmi-private@0.1.6 + +## 0.2.4 + +### Patch Changes + +- [#122](https://github.com/tmm/wagmi/pull/122) [`94f599c`](https://github.com/tmm/wagmi/commit/94f599cc1de74a977956d4118d85ab0d36915471) Thanks [@tmm](https://github.com/tmm)! - add decimals to useBalance + +## 0.2.3 + +### Patch Changes + +- [`e338c3b`](https://github.com/tmm/wagmi/commit/e338c3b6cc255742be6a67593aa5da6c17e90fbd) Thanks [@tmm](https://github.com/tmm)! - checksum connector address on change events + + add shim to injected connector for simulating disconnect + +- Updated dependencies [[`e338c3b`](https://github.com/tmm/wagmi/commit/e338c3b6cc255742be6a67593aa5da6c17e90fbd)]: + - wagmi-private@0.1.5 + +## 0.2.2 + +### Patch Changes + +- [`0176c4e`](https://github.com/tmm/wagmi/commit/0176c4e83fb0c5f159c3c802a1da3d6deb2184ae) Thanks [@tmm](https://github.com/tmm)! - added switchChain to WalletConnect and WalletLink connectors + +- Updated dependencies [[`0176c4e`](https://github.com/tmm/wagmi/commit/0176c4e83fb0c5f159c3c802a1da3d6deb2184ae)]: + - wagmi-private@0.1.4 + +## 0.2.1 + +### Patch Changes + +- [`f12d9cc`](https://github.com/tmm/wagmi/commit/f12d9ccfdf87a2f75299b53a7dd6b1ad046a49d8) Thanks [@tmm](https://github.com/tmm)! - fixes overrides type + +## 0.2.0 + +### Minor Changes + +- [#98](https://github.com/tmm/wagmi/pull/98) [`b2ec758`](https://github.com/tmm/wagmi/commit/b2ec7580436f52fd35005c6dd3f4472650a14d02) Thanks [@oveddan](https://github.com/oveddan)! - Exported Context + +## 0.1.7 + +### Patch Changes + +- [`d965757`](https://github.com/tmm/wagmi/commit/d9657578bc17648716c4671b8cc35ad295bc71d2) Thanks [@tmm](https://github.com/tmm)! - use balance eth symbol + +## 0.1.6 + +### Patch Changes + +- [`071d7fb`](https://github.com/tmm/wagmi/commit/071d7fbca35ec4832700b5343661ceb2dae20598) Thanks [@tmm](https://github.com/tmm)! - add hardhat chain + +- Updated dependencies [[`071d7fb`](https://github.com/tmm/wagmi/commit/071d7fbca35ec4832700b5343661ceb2dae20598)]: + - wagmi-private@0.1.3 + +## 0.1.5 + +### Patch Changes + +- [`db4d869`](https://github.com/tmm/wagmi/commit/db4d869fd9380b26a1f3f96ab34abd14ca73d068) Thanks [@tmm](https://github.com/tmm)! - - add global connecting property for `Provider` `autoConnect` (h/t @sammdec) + - fix `useContractEvent` error (h/t @math-marcellino) + +## 0.1.4 + +### Patch Changes + +- [#73](https://github.com/tmm/wagmi/pull/73) [`0c78ccc`](https://github.com/tmm/wagmi/commit/0c78ccc4e7f311525d4ea712b79cf532899e2006) Thanks [@tmm](https://github.com/tmm)! - fix module exports + +## 0.1.3 + +### Patch Changes + +- [`78bade9`](https://github.com/tmm/wagmi/commit/78bade9d0da97ab38a7e6594c34e3841ec1c8fe6) Thanks [@tmm](https://github.com/tmm)! - add type definitions + +- Updated dependencies [[`78bade9`](https://github.com/tmm/wagmi/commit/78bade9d0da97ab38a7e6594c34e3841ec1c8fe6)]: + - wagmi-private@0.1.2 + +## 0.1.2 + +### Patch Changes + +- [#56](https://github.com/tmm/wagmi/pull/56) [`2ebfd8e`](https://github.com/tmm/wagmi/commit/2ebfd8e85b560f25cd46cff04619c84643cab297) Thanks [@tmm](https://github.com/tmm)! - add chain support status + +- Updated dependencies [[`2ebfd8e`](https://github.com/tmm/wagmi/commit/2ebfd8e85b560f25cd46cff04619c84643cab297)]: + - wagmi-private@0.1.1 + +## 0.1.1 + +### Patch Changes + +- [#54](https://github.com/tmm/wagmi/pull/54) [`25acd3d`](https://github.com/tmm/wagmi/commit/25acd3dfbb4498af5e1139ae9c892f5013404cbc) Thanks [@tmm](https://github.com/tmm)! - Stale contract object when useContract hook arguments change @zakangelle + +## 0.1.0 + +### Minor Changes + +- [#52](https://github.com/tmm/wagmi/pull/52) [`da7a3a6`](https://github.com/tmm/wagmi/commit/da7a3a615def2443f65c041999100ce35e9774cc) Thanks [@tmm](https://github.com/tmm)! - Moves connectors to their own entrypoints to reduce bundle size. + + ```ts + // old - WalletLinkConnector unused, but still in final bundle + import { InjectedConnector, WalletConnectConnector } from "wagmi"; + + // new - WalletLinkConnector not in final bundle + import { InjectedConnector } from "wagmi/connectors/injected"; + import { WalletConnectConnector } from "wagmi/connectors/walletConnect"; + ``` + +### Patch Changes + +- Updated dependencies [[`da7a3a6`](https://github.com/tmm/wagmi/commit/da7a3a615def2443f65c041999100ce35e9774cc)]: + - wagmi-private@0.1.0 + +## 0.0.17 + +### Patch Changes + +- [#25](https://github.com/tmm/wagmi/pull/25) [`9a7dab7`](https://github.com/tmm/wagmi/commit/9a7dab78b3518658bc7d85dc397990f0d28da175) Thanks [@tmm](https://github.com/tmm)! - update response types + +- Updated dependencies [[`9a7dab7`](https://github.com/tmm/wagmi/commit/9a7dab78b3518658bc7d85dc397990f0d28da175)]: + - wagmi-private@0.0.17 + +## 0.0.16 + +### Patch Changes + +- [`d1574cf`](https://github.com/tmm/wagmi/commit/d1574cf5f7a578ccd480889c2e375134145a4aba) Thanks [@tmm](https://github.com/tmm)! - add better type information for contract results + +- Updated dependencies [[`d1574cf`](https://github.com/tmm/wagmi/commit/d1574cf5f7a578ccd480889c2e375134145a4aba)]: + - wagmi-private@0.0.16 + +## 0.0.15 + +### Patch Changes + +- [`3909624`](https://github.com/tmm/wagmi/commit/39096249c1fa9516beabb11735beb67c94032879) Thanks [@tmm](https://github.com/tmm)! - make contract read and write execute overrides param optional + +- Updated dependencies [[`3909624`](https://github.com/tmm/wagmi/commit/39096249c1fa9516beabb11735beb67c94032879)]: + - wagmi-private@0.0.15 + +## 0.0.14 + +### Patch Changes + +- [`63312e2`](https://github.com/tmm/wagmi/commit/63312e2b06b8d835abc2908cba399d941ca79408) Thanks [@tmm](https://github.com/tmm)! - add once to contract event + +- Updated dependencies [[`63312e2`](https://github.com/tmm/wagmi/commit/63312e2b06b8d835abc2908cba399d941ca79408)]: + - wagmi-private@0.0.14 + +## 0.0.13 + +### Patch Changes + +- [`6f890b0`](https://github.com/tmm/wagmi/commit/6f890b0dabbdbea913ec91cb8bfc970c05ed0a93) Thanks [@tmm](https://github.com/tmm)! - update readme + +- Updated dependencies [[`6f890b0`](https://github.com/tmm/wagmi/commit/6f890b0dabbdbea913ec91cb8bfc970c05ed0a93)]: + - wagmi-private@0.0.13 + +## 0.0.12 + +### Patch Changes + +- [#19](https://github.com/tmm/wagmi/pull/19) [`7bc1c47`](https://github.com/tmm/wagmi/commit/7bc1c47875e9ef24e9c79cfafc6b23e7a838b5bc) Thanks [@tmm](https://github.com/tmm)! - remove console log from walletlink connector + +- Updated dependencies [[`7bc1c47`](https://github.com/tmm/wagmi/commit/7bc1c47875e9ef24e9c79cfafc6b23e7a838b5bc)]: + - wagmi-private@0.0.12 + +## 0.0.11 + +### Patch Changes + +- [#17](https://github.com/tmm/wagmi/pull/17) [`571648b`](https://github.com/tmm/wagmi/commit/571648b754f7f538536bafc9387bd3104657ea49) Thanks [@tmm](https://github.com/tmm)! - standardize connector provider + +- Updated dependencies [[`571648b`](https://github.com/tmm/wagmi/commit/571648b754f7f538536bafc9387bd3104657ea49)]: + - wagmi-private@0.0.11 + +## 0.0.10 + +### Patch Changes + +- [#15](https://github.com/tmm/wagmi/pull/15) [`5f7675c`](https://github.com/tmm/wagmi/commit/5f7675c3ffd848522d4117c07c1f62b17dfc6616) Thanks [@tmm](https://github.com/tmm)! - read and write contract functions + +- Updated dependencies [[`5f7675c`](https://github.com/tmm/wagmi/commit/5f7675c3ffd848522d4117c07c1f62b17dfc6616)]: + - wagmi-private@0.0.10 + +## 0.0.9 + +### Patch Changes + +- [#13](https://github.com/tmm/wagmi/pull/13) [`e5545f5`](https://github.com/tmm/wagmi/commit/e5545f5565cf0bbf5e62ec7ccab3051705b1d313) Thanks [@tmm](https://github.com/tmm)! - add testing package + +- Updated dependencies [[`e5545f5`](https://github.com/tmm/wagmi/commit/e5545f5565cf0bbf5e62ec7ccab3051705b1d313)]: + - wagmi-private@0.0.9 + +## 0.0.8 + +### Patch Changes + +- [`5332500`](https://github.com/tmm/wagmi/commit/5332500918ac240d29ffe4d2aed8566a8ac001e4) Thanks [@tmm](https://github.com/tmm)! - update signing + +- Updated dependencies [[`5332500`](https://github.com/tmm/wagmi/commit/5332500918ac240d29ffe4d2aed8566a8ac001e4)]: + - wagmi-private@0.0.8 + +## 0.0.7 + +### Patch Changes + +- [`0bff89a`](https://github.com/tmm/wagmi/commit/0bff89ab2ad28b2cb9b346d1ac870e859d9278bc) Thanks [@tmm](https://github.com/tmm)! - update injected connector + +- Updated dependencies [[`0bff89a`](https://github.com/tmm/wagmi/commit/0bff89ab2ad28b2cb9b346d1ac870e859d9278bc)]: + - wagmi-private@0.0.7 + +## 0.0.6 + +### Patch Changes + +- [`37d39d1`](https://github.com/tmm/wagmi/commit/37d39d174ddfa122462bbe2d02141cd61eb9db4a) Thanks [@tmm](https://github.com/tmm)! - add message signing + +- Updated dependencies [[`37d39d1`](https://github.com/tmm/wagmi/commit/37d39d174ddfa122462bbe2d02141cd61eb9db4a)]: + - wagmi-private@0.0.6 + +## 0.0.5 + +### Patch Changes + +- [`d7d94f0`](https://github.com/tmm/wagmi/commit/d7d94f06f7d30468e5e39d64db63124c6315cf82) Thanks [@tmm](https://github.com/tmm)! - fix injected connector name + +- Updated dependencies [[`d7d94f0`](https://github.com/tmm/wagmi/commit/d7d94f06f7d30468e5e39d64db63124c6315cf82)]: + - wagmi-private@0.0.5 + +## 0.0.4 + +### Patch Changes + +- [`29fbe29`](https://github.com/tmm/wagmi/commit/29fbe2920046b9e87a34faa04500ccf3c4f83748) Thanks [@tmm](https://github.com/tmm)! - fix external deps + +- Updated dependencies [[`29fbe29`](https://github.com/tmm/wagmi/commit/29fbe2920046b9e87a34faa04500ccf3c4f83748)]: + - wagmi-private@0.0.4 + +## 0.0.3 + +### Patch Changes + +- [#6](https://github.com/tmm/wagmi/pull/6) [`8dc3a5d`](https://github.com/tmm/wagmi/commit/8dc3a5d5f418813b09663534fe585d9bcf94dbeb) Thanks [@tmm](https://github.com/tmm)! - clean up deps + +- Updated dependencies [[`8dc3a5d`](https://github.com/tmm/wagmi/commit/8dc3a5d5f418813b09663534fe585d9bcf94dbeb)]: + - wagmi-private@0.0.3 + +## 0.0.2 + +### Patch Changes + +- [#4](https://github.com/tmm/wagmi/pull/4) [`2fbd821`](https://github.com/tmm/wagmi/commit/2fbd8216379bd03c9cc5c06b10b75637e75cb7d8) Thanks [@tmm](https://github.com/tmm)! - init changesets + +- Updated dependencies [[`2fbd821`](https://github.com/tmm/wagmi/commit/2fbd8216379bd03c9cc5c06b10b75637e75cb7d8)]: + - wagmi-private@0.0.2 diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 0000000000..4c87fd5e67 --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,13 @@ +# wagmi + +React Hooks for Ethereum + +## Installation + +```bash +pnpm add wagmi viem @tanstack/react-query +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000000..79311aa5ce --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,119 @@ +{ + "name": "wagmi", + "description": "React Hooks for Ethereum", + "version": "2.15.4", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/react" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains codegen connectors experimental query", + "test:build": "publint --strict && attw --pack --ignore-rules cjs-resolves-to-esm" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "/actions", + "/chains", + "/codegen", + "/connectors", + "/experimental", + "/query" + ], + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./actions": { + "types": "./dist/types/exports/actions.d.ts", + "default": "./dist/esm/exports/actions.js" + }, + "./actions/experimental": { + "types": "./dist/types/exports/actions/experimental.d.ts", + "default": "./dist/esm/exports/actions/experimental.js" + }, + "./chains": { + "types": "./dist/types/exports/chains.d.ts", + "default": "./dist/esm/exports/chains.js" + }, + "./codegen": { + "types": "./dist/types/exports/codegen.d.ts", + "default": "./dist/esm/exports/codegen.js" + }, + "./connectors": { + "types": "./dist/types/exports/connectors.d.ts", + "default": "./dist/esm/exports/connectors.js" + }, + "./experimental": { + "types": "./dist/types/exports/experimental.d.ts", + "default": "./dist/esm/exports/experimental.js" + }, + "./query": { + "types": "./dist/types/exports/query.d.ts", + "default": "./dist/esm/exports/query.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "actions": ["./dist/types/exports/actions.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "codegen": ["./dist/types/exports/codegen.d.ts"], + "connectors": ["./dist/types/exports/connectors.d.ts"], + "experimental": ["./dist/types/exports/experimental.d.ts"], + "query": ["./dist/types/exports/query.d.ts"] + } + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + }, + "dependencies": { + "@wagmi/connectors": "workspace:*", + "@wagmi/core": "workspace:*", + "use-sync-external-store": "1.4.0" + }, + "devDependencies": { + "@tanstack/react-query": "catalog:", + "@testing-library/dom": "catalog:", + "@testing-library/react": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@types/use-sync-external-store": "^0.0.6", + "react": "catalog:", + "react-dom": "catalog:" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": [ + "wagmi", + "react", + "hooks", + "eth", + "ethereum", + "dapps", + "wallet", + "web3" + ] +} diff --git a/packages/react/src/context.test.tsx b/packages/react/src/context.test.tsx new file mode 100644 index 0000000000..7a716ac685 --- /dev/null +++ b/packages/react/src/context.test.tsx @@ -0,0 +1,101 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, waitFor } from '@testing-library/react' +import { http, connect, createConfig, mock } from '@wagmi/core' +import { accounts, addressRegex, config, mainnet } from '@wagmi/test' +import React from 'react' +import { expect, test } from 'vitest' + +import { WagmiProvider } from './context.js' +import { useAccount } from './hooks/useAccount.js' +import { useConnectorClient } from './hooks/useConnectorClient.js' + +test('default', () => { + function Component() { + const { address } = useAccount() + const { data } = useConnectorClient() + return ( +
+

wevm

+
useAccount: {address}
+
useConnectorClient: {data?.account?.address}
+
+ ) + } + + const queryClient = new QueryClient() + const result = render( + + + + + , + ) + expect(result.getByRole('heading').innerText).toMatchInlineSnapshot(`"wevm"`) + result.unmount() +}) + +test('fake ssr config', () => { + const config = createConfig({ + chains: [mainnet], + pollingInterval: 100, + ssr: true, + transports: { + [mainnet.id]: http(), + }, + }) + const queryClient = new QueryClient() + + const result = render( + + +

wevm

+
+
, + ) + expect(result.getAllByRole('heading')).toMatchInlineSnapshot(` + [ +

+ wevm +

, + ] + `) + result.unmount() +}) + +test('mock reconnect', async () => { + function Component() { + const { address } = useAccount() + return ( +
+

{address}

+
+ ) + } + + const connector = mock({ + accounts, + features: { reconnect: true }, + }) + const config = createConfig({ + chains: [mainnet], + connectors: [connector], + storage: null, + transports: { + [mainnet.id]: http(), + }, + }) + await connect(config, { connector }) + + const queryClient = new QueryClient() + const result = render( + + + + + , + ) + await waitFor(() => + expect(result.getByRole('heading').innerText).toMatch(addressRegex), + ) + result.unmount() +}) diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts new file mode 100644 index 0000000000..ec484ccd74 --- /dev/null +++ b/packages/react/src/context.ts @@ -0,0 +1,28 @@ +'use client' + +import type { ResolvedRegister, State } from '@wagmi/core' +import { createContext, createElement } from 'react' +import { Hydrate } from './hydrate.js' + +export const WagmiContext = createContext< + ResolvedRegister['config'] | undefined +>(undefined) + +export type WagmiProviderProps = { + config: ResolvedRegister['config'] + initialState?: State | undefined + reconnectOnMount?: boolean | undefined +} + +export function WagmiProvider( + parameters: React.PropsWithChildren, +) { + const { children, config } = parameters + + const props = { value: config } + return createElement( + Hydrate, + parameters, + createElement(WagmiContext.Provider, props, children), + ) +} diff --git a/packages/react/src/errors/base.test.ts b/packages/react/src/errors/base.test.ts new file mode 100644 index 0000000000..2980541ed8 --- /dev/null +++ b/packages/react/src/errors/base.test.ts @@ -0,0 +1,155 @@ +import { expect, test } from 'vitest' + +import { BaseError } from './base.js' + +test('BaseError', () => { + expect(new BaseError('An error occurred.')).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Version: wagmi@x.y.z] + `) + + expect( + new BaseError('An error occurred.', { details: 'details' }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: wagmi@x.y.z] + `) + + expect(new BaseError('', { details: 'details' })).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: wagmi@x.y.z] + `) +}) + +test('BaseError (w/ docsPath)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: wagmi@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error', { docsPath: '/docs' }), + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/docs.html + Version: wagmi@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error'), + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html + Version: wagmi@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + docsSlug: 'test', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/react/lol.html#test + Details: details + Version: wagmi@x.y.z] + `) +}) + +test('BaseError (w/ metaMessages)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + metaMessages: ['Reason: idk', 'Cause: lol'], + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Reason: idk + Cause: lol + + Details: details + Version: wagmi@x.y.z] + `) +}) + +test('inherited BaseError', () => { + const err = new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }) + expect( + new BaseError('An internal error occurred.', { + cause: err, + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: wagmi@x.y.z] + `) +}) + +test('inherited Error', () => { + const err = new Error('details') + expect( + new BaseError('An internal error occurred.', { + cause: err, + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/react/lol.html + Details: details + Version: wagmi@x.y.z] + `) +}) + +test('walk: no predicate fn (walks to leaf)', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk()).toMatchInlineSnapshot(` + [WagmiError: test3 + + Version: wagmi@x.y.z] + `) +}) + +test('walk: predicate fn', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk((err) => err instanceof FooError)).toMatchInlineSnapshot(` + [WagmiError: test2 + + Version: wagmi@x.y.z] + `) +}) diff --git a/packages/react/src/errors/base.ts b/packages/react/src/errors/base.ts new file mode 100644 index 0000000000..b2ee83c86e --- /dev/null +++ b/packages/react/src/errors/base.ts @@ -0,0 +1,14 @@ +import { BaseError as CoreError } from '@wagmi/core' + +import { getVersion } from '../utils/getVersion.js' + +export type BaseErrorType = BaseError & { name: 'WagmiError' } +export class BaseError extends CoreError { + override name = 'WagmiError' + override get docsBaseUrl() { + return 'https://wagmi.sh/react' + } + override get version() { + return getVersion() + } +} diff --git a/packages/react/src/errors/context.test.ts b/packages/react/src/errors/context.test.ts new file mode 100644 index 0000000000..8dc5a16c4a --- /dev/null +++ b/packages/react/src/errors/context.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from 'vitest' + +import { WagmiProviderNotFoundError } from './context.js' + +test('WagmiProviderNotFoundError', () => { + expect(new WagmiProviderNotFoundError()).toMatchInlineSnapshot(` + [WagmiProviderNotFoundError: \`useConfig\` must be used within \`WagmiProvider\`. + + Docs: https://wagmi.sh/react/api/WagmiProvider.html + Version: wagmi@x.y.z] + `) +}) diff --git a/packages/react/src/errors/context.ts b/packages/react/src/errors/context.ts new file mode 100644 index 0000000000..0ea5cadaaa --- /dev/null +++ b/packages/react/src/errors/context.ts @@ -0,0 +1,13 @@ +import { BaseError } from './base.js' + +export type WagmiProviderNotFoundErrorType = WagmiProviderNotFoundError & { + name: 'WagmiProviderNotFoundError' +} +export class WagmiProviderNotFoundError extends BaseError { + override name = 'WagmiProviderNotFoundError' + constructor() { + super('`useConfig` must be used within `WagmiProvider`.', { + docsPath: '/api/WagmiProvider', + }) + } +} diff --git a/packages/react/src/experimental/hooks/useWriteContracts.test.ts b/packages/react/src/experimental/hooks/useWriteContracts.test.ts new file mode 100644 index 0000000000..ea6d4815de --- /dev/null +++ b/packages/react/src/experimental/hooks/useWriteContracts.test.ts @@ -0,0 +1,45 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useWriteContracts } from './useWriteContracts.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useWriteContracts()) + + result.current.writeContracts({ + contracts: [ + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + { + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchInlineSnapshot( + ` + { + "id": "0x8913636bd97cf4bcc0a6343c730905a27ead0f7480ff82190072e916439eb212", + } + `, + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/experimental/hooks/useWriteContracts.ts b/packages/react/src/experimental/hooks/useWriteContracts.ts new file mode 100644 index 0000000000..8d0d32aefb --- /dev/null +++ b/packages/react/src/experimental/hooks/useWriteContracts.ts @@ -0,0 +1,85 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ResolvedRegister } from '@wagmi/core' +import { + type WriteContractsData, + type WriteContractsErrorType, + type WriteContractsMutate, + type WriteContractsMutateAsync, + type WriteContractsVariables, + writeContractsMutationOptions, +} from '@wagmi/core/experimental' +import type { Compute } from '@wagmi/core/internal' +import type { ContractFunctionParameters } from 'viem' + +import { useConfig } from '../../hooks/useConfig.js' +import type { ConfigParameter } from '../../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../../utils/query.js' + +export type UseWriteContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables< + contracts, + config, + config['chains'][number]['id'] + >, + context + > + | undefined + } +> + +export type UseWriteContractsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + WriteContractsData, + WriteContractsErrorType, + WriteContractsVariables, + context + > & { + writeContracts: WriteContractsMutate + writeContractsAsync: WriteContractsMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useWriteContracts */ +export function useWriteContracts< + const contracts extends + readonly unknown[] = readonly ContractFunctionParameters[], + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteContractsParameters = {}, +): UseWriteContractsReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = writeContractsMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseWriteContractsReturnType + return { + ...result, + writeContracts: mutate as Return['writeContracts'], + writeContractsAsync: mutateAsync as Return['writeContractsAsync'], + } +} diff --git a/packages/react/src/exports/actions.test.ts b/packages/react/src/exports/actions.test.ts new file mode 100644 index 0000000000..eaaedba14f --- /dev/null +++ b/packages/react/src/exports/actions.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from 'vitest' + +import * as actions from './actions.js' + +test('exports', () => { + expect(Object.keys(actions)).toMatchInlineSnapshot(` + [ + "call", + "connect", + "deployContract", + "disconnect", + "estimateGas", + "estimateFeesPerGas", + "estimateMaxPriorityFeePerGas", + "getAccount", + "getBalance", + "fetchBalance", + "getBlock", + "getBlockNumber", + "fetchBlockNumber", + "getBlockTransactionCount", + "getBytecode", + "getCallsStatus", + "getCapabilities", + "getChainId", + "getChains", + "getClient", + "getConnections", + "getConnectors", + "getConnectorClient", + "getEnsAddress", + "fetchEnsAddress", + "getEnsAvatar", + "fetchEnsAvatar", + "getEnsName", + "fetchEnsName", + "getEnsResolver", + "fetchEnsResolver", + "getEnsText", + "getFeeHistory", + "getGasPrice", + "getProof", + "getPublicClient", + "getStorageAt", + "getToken", + "fetchToken", + "getTransaction", + "fetchTransaction", + "getTransactionConfirmations", + "getTransactionCount", + "getTransactionReceipt", + "getWalletClient", + "multicall", + "prepareTransactionRequest", + "readContract", + "readContracts", + "reconnect", + "sendCalls", + "sendTransaction", + "showCallsStatus", + "signMessage", + "signTypedData", + "simulateContract", + "switchAccount", + "switchChain", + "switchNetwork", + "verifyMessage", + "verifyTypedData", + "waitForCallsStatus", + "watchAccount", + "watchAsset", + "watchBlocks", + "watchBlockNumber", + "watchChainId", + "watchClient", + "watchConnections", + "watchConnectors", + "watchContractEvent", + "watchPendingTransactions", + "watchPublicClient", + "waitForTransactionReceipt", + "waitForTransaction", + "writeContract", + ] + `) +}) diff --git a/packages/react/src/exports/actions.ts b/packages/react/src/exports/actions.ts new file mode 100644 index 0000000000..3ff9c743c5 --- /dev/null +++ b/packages/react/src/exports/actions.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/actions' diff --git a/packages/react/src/exports/actions/experimental.test.ts b/packages/react/src/exports/actions/experimental.test.ts new file mode 100644 index 0000000000..7c4b92df8c --- /dev/null +++ b/packages/react/src/exports/actions/experimental.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from 'vitest' + +import * as experimentalActions from './experimental.js' + +test('exports', () => { + expect(Object.keys(experimentalActions)).toMatchInlineSnapshot(` + [ + "getCallsStatus", + "getCapabilities", + "sendCalls", + "showCallsStatus", + "waitForCallsStatus", + "writeContracts", + "getCallsStatusQueryOptions", + "getCallsStatusQueryKey", + "getCapabilitiesQueryOptions", + "getCapabilitiesQueryKey", + "sendCallsMutationOptions", + "showCallsStatusMutationOptions", + "waitForCallsStatusQueryKey", + "waitForCallsStatusQueryOptions", + "writeContractsMutationOptions", + ] + `) +}) diff --git a/packages/react/src/exports/actions/experimental.ts b/packages/react/src/exports/actions/experimental.ts new file mode 100644 index 0000000000..6ee0334af5 --- /dev/null +++ b/packages/react/src/exports/actions/experimental.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/experimental +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/experimental' diff --git a/packages/react/src/exports/chains.ts b/packages/react/src/exports/chains.ts new file mode 100644 index 0000000000..1fca7f537f --- /dev/null +++ b/packages/react/src/exports/chains.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// viem/chains +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from 'viem/chains' diff --git a/packages/react/src/exports/codegen.test.ts b/packages/react/src/exports/codegen.test.ts new file mode 100644 index 0000000000..19697cc9a9 --- /dev/null +++ b/packages/react/src/exports/codegen.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from 'vitest' + +import * as codegen from './codegen.js' + +test('exports', () => { + expect(Object.keys(codegen)).toMatchInlineSnapshot(` + [ + "createSimulateContract", + "createReadContract", + "createWatchContractEvent", + "createWriteContract", + "createUseSimulateContract", + "createUseReadContract", + "createUseWatchContractEvent", + "createUseWriteContract", + ] + `) +}) diff --git a/packages/react/src/exports/codegen.ts b/packages/react/src/exports/codegen.ts new file mode 100644 index 0000000000..c642f63bcb --- /dev/null +++ b/packages/react/src/exports/codegen.ts @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/codegen +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/codegen' + +//////////////////////////////////////////////////////////////////////////////// +// Hooks +//////////////////////////////////////////////////////////////////////////////// + +export { + type CreateUseSimulateContractParameters, + type CreateUseSimulateContractReturnType, + createUseSimulateContract, +} from '../hooks/codegen/createUseSimulateContract.js' + +export { + type CreateUseReadContractParameters, + type CreateUseReadContractReturnType, + createUseReadContract, +} from '../hooks/codegen/createUseReadContract.js' + +export { + type CreateUseWatchContractEventParameters, + type CreateUseWatchContractEventReturnType, + createUseWatchContractEvent, +} from '../hooks/codegen/createUseWatchContractEvent.js' + +export { + type CreateUseWriteContractParameters, + type CreateUseWriteContractReturnType, + createUseWriteContract, +} from '../hooks/codegen/createUseWriteContract.js' diff --git a/packages/react/src/exports/connectors.test.ts b/packages/react/src/exports/connectors.test.ts new file mode 100644 index 0000000000..068db8227c --- /dev/null +++ b/packages/react/src/exports/connectors.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' + +import * as connectors from './connectors.js' + +test('exports', () => { + expect(Object.keys(connectors)).toMatchInlineSnapshot(` + [ + "injected", + "mock", + "coinbaseWallet", + "metaMask", + "safe", + "walletConnect", + "version", + ] + `) +}) diff --git a/packages/react/src/exports/connectors.ts b/packages/react/src/exports/connectors.ts new file mode 100644 index 0000000000..e10367e318 --- /dev/null +++ b/packages/react/src/exports/connectors.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/connectors +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/connectors' diff --git a/packages/react/src/exports/experimental.ts b/packages/react/src/exports/experimental.ts new file mode 100644 index 0000000000..996eb56a73 --- /dev/null +++ b/packages/react/src/exports/experimental.ts @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// Hooks +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + /** @deprecated This is no longer experimental – use `import type { UseCallsStatusParameters } from 'wagmi'` instead. */ + type UseCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { UseCallsStatusReturnType } from 'wagmi'` instead. */ + type UseCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { useCallsStatus } from 'wagmi'` instead. */ + useCallsStatus, +} from '../hooks/useCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { UseCapabilitiesParameters } from 'wagmi'` instead. */ + type UseCapabilitiesParameters, + /** @deprecated This is no longer experimental – use `import type { UseCapabilitiesReturnType } from 'wagmi'` instead. */ + type UseCapabilitiesReturnType, + /** @deprecated This is no longer experimental – use `import { useCapabilities } from 'wagmi'` instead. */ + useCapabilities, +} from '../hooks/useCapabilities.js' + +export { + /** @deprecated This is no longer experimental – use `import type { UseSendCallsParameters } from 'wagmi'` instead. */ + type UseSendCallsParameters, + /** @deprecated This is no longer experimental – use `import type { UseSendCallsReturnType } from 'wagmi'` instead. */ + type UseSendCallsReturnType, + /** @deprecated This is no longer experimental – use `import { useSendCalls } from 'wagmi'` instead. */ + useSendCalls, +} from '../hooks/useSendCalls.js' + +export { + /** @deprecated This is no longer experimental – use `import type { UseShowCallsStatusParameters } from 'wagmi'` instead. */ + type UseShowCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { UseShowCallsStatusReturnType } from 'wagmi'` instead. */ + type UseShowCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { useShowCallsStatus } from 'wagmi'` instead. */ + useShowCallsStatus, +} from '../hooks/useShowCallsStatus.js' + +export { + /** @deprecated This is no longer experimental – use `import type { UseWaitForCallsStatusParameters } from 'wagmi'` instead. */ + type UseWaitForCallsStatusParameters, + /** @deprecated This is no longer experimental – use `import type { UseWaitForCallsStatusReturnType } from 'wagmi'` instead. */ + type UseWaitForCallsStatusReturnType, + /** @deprecated This is no longer experimental – use `import { useWaitForCallsStatus } from 'wagmi'` instead. */ + useWaitForCallsStatus, +} from '../hooks/useWaitForCallsStatus.js' + +export { + /** @deprecated Use `UseSendCallsParameters` instead. */ + type UseWriteContractsParameters, + /** @deprecated Use `UseSendCallsReturnType` instead. */ + type UseWriteContractsReturnType, + /** @deprecated Use `useSendCalls` instead. */ + useWriteContracts, +} from '../experimental/hooks/useWriteContracts.js' diff --git a/packages/react/src/exports/index.test.ts b/packages/react/src/exports/index.test.ts new file mode 100644 index 0000000000..d8d6f7c6d5 --- /dev/null +++ b/packages/react/src/exports/index.test.ts @@ -0,0 +1,111 @@ +import { expect, test } from 'vitest' + +import * as react from './index.js' + +test('exports', () => { + expect(Object.keys(react)).toMatchInlineSnapshot(` + [ + "WagmiContext", + "WagmiProvider", + "Context", + "WagmiConfig", + "BaseError", + "WagmiProviderNotFoundError", + "useAccount", + "useAccountEffect", + "useBalance", + "useBlock", + "useBlockNumber", + "useBlockTransactionCount", + "useBytecode", + "useCallsStatus", + "useCapabilities", + "useCall", + "useChainId", + "useChains", + "useClient", + "useConfig", + "useConnect", + "useConnections", + "useConnectors", + "useConnectorClient", + "useDeployContract", + "useDisconnect", + "useEnsAddress", + "useEnsAvatar", + "useEnsName", + "useEnsResolver", + "useEnsText", + "useEstimateFeesPerGas", + "useFeeData", + "useEstimateGas", + "useEstimateMaxPriorityFeePerGas", + "useFeeHistory", + "useGasPrice", + "useInfiniteReadContracts", + "useContractInfiniteReads", + "usePrepareTransactionRequest", + "useProof", + "usePublicClient", + "useReadContract", + "useContractRead", + "useReadContracts", + "useContractReads", + "useReconnect", + "useSendCalls", + "useSendTransaction", + "useShowCallsStatus", + "useSignMessage", + "useSignTypedData", + "useSimulateContract", + "useStorageAt", + "useSwitchAccount", + "useSwitchChain", + "useToken", + "useTransaction", + "useTransactionConfirmations", + "useTransactionCount", + "useTransactionReceipt", + "useVerifyMessage", + "useVerifyTypedData", + "useWalletClient", + "useWaitForCallsStatus", + "useWaitForTransactionReceipt", + "useWatchAsset", + "useWatchBlocks", + "useWatchBlockNumber", + "useWatchContractEvent", + "useWatchPendingTransactions", + "useWriteContract", + "useContractWrite", + "Hydrate", + "createConfig", + "createConnector", + "injected", + "mock", + "ChainNotConfiguredError", + "ConnectorAlreadyConnectedError", + "ConnectorNotFoundError", + "ConnectorAccountNotFoundError", + "ConnectorChainMismatchError", + "ConnectorUnavailableReconnectingError", + "ProviderNotFoundError", + "SwitchChainNotSupportedError", + "createStorage", + "noopStorage", + "custom", + "fallback", + "http", + "webSocket", + "unstable_connector", + "cookieStorage", + "cookieToInitialState", + "deepEqual", + "deserialize", + "normalizeChainId", + "parseCookie", + "serialize", + "version", + ] + `) +}) diff --git a/packages/react/src/exports/index.ts b/packages/react/src/exports/index.ts new file mode 100644 index 0000000000..a4b8502982 --- /dev/null +++ b/packages/react/src/exports/index.ts @@ -0,0 +1,487 @@ +//////////////////////////////////////////////////////////////////////////////// +// Context +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type WagmiProviderProps, + WagmiContext, + WagmiProvider, + /** @deprecated Use `WagmiContext` instead */ + WagmiContext as Context, + /** @deprecated Use `WagmiProvider` instead */ + WagmiProvider as WagmiConfig, +} from '../context.js' + +//////////////////////////////////////////////////////////////////////////////// +// Errors +//////////////////////////////////////////////////////////////////////////////// + +export { type BaseErrorType, BaseError } from '../errors/base.js' + +export { + type WagmiProviderNotFoundErrorType, + WagmiProviderNotFoundError, +} from '../errors/context.js' + +//////////////////////////////////////////////////////////////////////////////// +// Hooks +//////////////////////////////////////////////////////////////////////////////// + +export { + type UseAccountParameters, + type UseAccountReturnType, + useAccount, +} from '../hooks/useAccount.js' + +export { + type UseAccountEffectParameters, + useAccountEffect, +} from '../hooks/useAccountEffect.js' + +export { + type UseBalanceParameters, + type UseBalanceReturnType, + useBalance, +} from '../hooks/useBalance.js' + +export { + type UseBlockParameters, + type UseBlockReturnType, + useBlock, +} from '../hooks/useBlock.js' + +export { + type UseBlockNumberParameters, + type UseBlockNumberReturnType, + useBlockNumber, +} from '../hooks/useBlockNumber.js' + +export { + type UseBlockTransactionCountParameters, + type UseBlockTransactionCountReturnType, + useBlockTransactionCount, +} from '../hooks/useBlockTransactionCount.js' + +export { + type UseBytecodeParameters, + type UseBytecodeReturnType, + useBytecode, +} from '../hooks/useBytecode.js' + +export { + type UseCallsStatusParameters, + type UseCallsStatusReturnType, + useCallsStatus, +} from '../hooks/useCallsStatus.js' + +export { + type UseCapabilitiesParameters, + type UseCapabilitiesReturnType, + useCapabilities, +} from '../hooks/useCapabilities.js' + +export { + type UseCallParameters, + type UseCallReturnType, + useCall, +} from '../hooks/useCall.js' + +export { + type UseChainIdParameters, + type UseChainIdReturnType, + useChainId, +} from '../hooks/useChainId.js' + +export { + type UseChainsParameters, + type UseChainsReturnType, + useChains, +} from '../hooks/useChains.js' + +export { + type UseClientParameters, + type UseClientReturnType, + useClient, +} from '../hooks/useClient.js' + +export { + type UseConfigParameters, + type UseConfigReturnType, + useConfig, +} from '../hooks/useConfig.js' + +export { + type UseConnectParameters, + type UseConnectReturnType, + useConnect, +} from '../hooks/useConnect.js' + +export { + type UseConnectionsParameters, + type UseConnectionsReturnType, + useConnections, +} from '../hooks/useConnections.js' + +export { + type UseConnectorsParameters, + type UseConnectorsReturnType, + useConnectors, +} from '../hooks/useConnectors.js' + +export { + type UseConnectorClientParameters, + type UseConnectorClientReturnType, + useConnectorClient, +} from '../hooks/useConnectorClient.js' + +export { + type UseDeployContractParameters, + type UseDeployContractReturnType, + useDeployContract, +} from '../hooks/useDeployContract.js' + +export { + type UseDisconnectParameters, + type UseDisconnectReturnType, + useDisconnect, +} from '../hooks/useDisconnect.js' + +export { + type UseEnsAddressParameters, + type UseEnsAddressReturnType, + useEnsAddress, +} from '../hooks/useEnsAddress.js' + +export { + type UseEnsAvatarParameters, + type UseEnsAvatarReturnType, + useEnsAvatar, +} from '../hooks/useEnsAvatar.js' + +export { + type UseEnsNameParameters, + type UseEnsNameReturnType, + useEnsName, +} from '../hooks/useEnsName.js' + +export { + type UseEnsResolverParameters, + type UseEnsResolverReturnType, + useEnsResolver, +} from '../hooks/useEnsResolver.js' + +export { + type UseEnsTextParameters, + type UseEnsTextReturnType, + useEnsText, +} from '../hooks/useEnsText.js' + +export { + type UseEstimateFeesPerGasParameters, + type UseEstimateFeesPerGasReturnType, + useEstimateFeesPerGas, + /** @deprecated Use `useEstimateFeesPerGas` instead */ + useEstimateFeesPerGas as useFeeData, +} from '../hooks/useEstimateFeesPerGas.js' + +export { + type UseEstimateGasParameters, + type UseEstimateGasReturnType, + useEstimateGas, +} from '../hooks/useEstimateGas.js' + +export { + type UseEstimateMaxPriorityFeePerGasParameters, + type UseEstimateMaxPriorityFeePerGasReturnType, + useEstimateMaxPriorityFeePerGas, +} from '../hooks/useEstimateMaxPriorityFeePerGas.js' + +export { + type UseFeeHistoryParameters, + type UseFeeHistoryReturnType, + useFeeHistory, +} from '../hooks/useFeeHistory.js' + +export { + type UseGasPriceParameters, + type UseGasPriceReturnType, + useGasPrice, +} from '../hooks/useGasPrice.js' + +export { + type UseInfiniteContractReadsParameters, + type UseInfiniteContractReadsReturnType, + useInfiniteReadContracts, + /** @deprecated Use `useInfiniteReadContracts` instead */ + useInfiniteReadContracts as useContractInfiniteReads, +} from '../hooks/useInfiniteReadContracts.js' + +export { + type UsePrepareTransactionRequestParameters, + type UsePrepareTransactionRequestReturnType, + usePrepareTransactionRequest, +} from '../hooks/usePrepareTransactionRequest.js' + +export { + type UseProofParameters, + type UseProofReturnType, + useProof, +} from '../hooks/useProof.js' + +export { + type UsePublicClientParameters, + type UsePublicClientReturnType, + usePublicClient, +} from '../hooks/usePublicClient.js' + +export { + type UseReadContractParameters, + type UseReadContractReturnType, + useReadContract, + /** @deprecated Use `useWriteContract` instead */ + useReadContract as useContractRead, +} from '../hooks/useReadContract.js' + +export { + type UseReadContractsParameters, + type UseReadContractsReturnType, + useReadContracts, + /** @deprecated Use `useWriteContract` instead */ + useReadContracts as useContractReads, +} from '../hooks/useReadContracts.js' + +export { + type UseReconnectParameters, + type UseReconnectReturnType, + useReconnect, +} from '../hooks/useReconnect.js' + +export { + type UseSendCallsParameters, + type UseSendCallsReturnType, + useSendCalls, +} from '../hooks/useSendCalls.js' + +export { + type UseSendTransactionParameters, + type UseSendTransactionReturnType, + useSendTransaction, +} from '../hooks/useSendTransaction.js' + +export { + type UseShowCallsStatusParameters, + type UseShowCallsStatusReturnType, + useShowCallsStatus, +} from '../hooks/useShowCallsStatus.js' + +export { + type UseSignMessageParameters, + type UseSignMessageReturnType, + useSignMessage, +} from '../hooks/useSignMessage.js' + +export { + type UseSignTypedDataParameters, + type UseSignTypedDataReturnType, + useSignTypedData, +} from '../hooks/useSignTypedData.js' + +export { + type UseSimulateContractParameters, + type UseSimulateContractReturnType, + useSimulateContract, +} from '../hooks/useSimulateContract.js' + +export { + type UseStorageAtParameters, + type UseStorageAtReturnType, + useStorageAt, +} from '../hooks/useStorageAt.js' + +export { + type UseSwitchAccountParameters, + type UseSwitchAccountReturnType, + useSwitchAccount, +} from '../hooks/useSwitchAccount.js' + +export { + type UseSwitchChainParameters, + type UseSwitchChainReturnType, + useSwitchChain, +} from '../hooks/useSwitchChain.js' + +export { + type UseTokenParameters, + type UseTokenReturnType, + /** @deprecated Use `useReadContracts` instead */ + useToken, +} from '../hooks/useToken.js' + +export { + type UseTransactionParameters, + type UseTransactionReturnType, + useTransaction, +} from '../hooks/useTransaction.js' + +export { + type UseTransactionConfirmationsParameters, + type UseTransactionConfirmationsReturnType, + useTransactionConfirmations, +} from '../hooks/useTransactionConfirmations.js' + +export { + type UseTransactionCountParameters, + type UseTransactionCountReturnType, + useTransactionCount, +} from '../hooks/useTransactionCount.js' + +export { + type UseTransactionReceiptParameters, + type UseTransactionReceiptReturnType, + useTransactionReceipt, +} from '../hooks/useTransactionReceipt.js' + +export { + type UseVerifyMessageParameters, + type UseVerifyMessageReturnType, + useVerifyMessage, +} from '../hooks/useVerifyMessage.js' + +export { + type UseVerifyTypedDataParameters, + type UseVerifyTypedDataReturnType, + useVerifyTypedData, +} from '../hooks/useVerifyTypedData.js' + +export { + type UseWalletClientParameters, + type UseWalletClientReturnType, + useWalletClient, +} from '../hooks/useWalletClient.js' + +export { + type UseWaitForCallsStatusParameters, + type UseWaitForCallsStatusReturnType, + useWaitForCallsStatus, +} from '../hooks/useWaitForCallsStatus.js' + +export { + type UseWaitForTransactionReceiptParameters, + type UseWaitForTransactionReceiptReturnType, + useWaitForTransactionReceipt, +} from '../hooks/useWaitForTransactionReceipt.js' + +export { + type UseWatchAssetParameters, + type UseWatchAssetReturnType, + useWatchAsset, +} from '../hooks/useWatchAsset.js' + +export { + type UseWatchBlocksParameters, + type UseWatchBlocksReturnType, + useWatchBlocks, +} from '../hooks/useWatchBlocks.js' + +export { + type UseWatchBlockNumberParameters, + type UseWatchBlockNumberReturnType, + useWatchBlockNumber, +} from '../hooks/useWatchBlockNumber.js' + +export { + type UseWatchContractEventParameters, + type UseWatchContractEventReturnType, + useWatchContractEvent, +} from '../hooks/useWatchContractEvent.js' + +export { + type UseWatchPendingTransactionsParameters, + type UseWatchPendingTransactionsReturnType, + useWatchPendingTransactions, +} from '../hooks/useWatchPendingTransactions.js' + +export { + type UseWriteContractParameters, + type UseWriteContractReturnType, + useWriteContract, + /** @deprecated Use `useWriteContract` instead */ + useWriteContract as useContractWrite, +} from '../hooks/useWriteContract.js' + +//////////////////////////////////////////////////////////////////////////////// +// Hydrate +//////////////////////////////////////////////////////////////////////////////// + +export { + type HydrateProps, + Hydrate, +} from '../hydrate.js' + +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core +//////////////////////////////////////////////////////////////////////////////// + +export { + // Config + type Connection, + type Connector, + type Config, + type CreateConfigParameters, + type PartializedState, + type State, + createConfig, + // Connector + type ConnectorEventMap, + type CreateConnectorFn, + createConnector, + injected, + mock, + // Errors + type ChainNotConfiguredErrorType, + ChainNotConfiguredError, + type ConnectorAlreadyConnectedErrorType, + ConnectorAlreadyConnectedError, + type ConnectorNotFoundErrorType, + ConnectorNotFoundError, + type ConnectorAccountNotFoundErrorType, + ConnectorAccountNotFoundError, + type ConnectorChainMismatchErrorType, + ConnectorChainMismatchError, + type ConnectorUnavailableReconnectingErrorType, + ConnectorUnavailableReconnectingError, + type ProviderNotFoundErrorType, + ProviderNotFoundError, + type SwitchChainNotSupportedErrorType, + SwitchChainNotSupportedError, + // Storage + type CreateStorageParameters, + type Storage, + createStorage, + noopStorage, + // Transports + custom, + fallback, + http, + webSocket, + unstable_connector, + type Transport, + // Types + type Register, + type ResolvedRegister, + // Utilities + cookieStorage, + cookieToInitialState, + deepEqual, + deserialize, + normalizeChainId, + parseCookie, + serialize, +} from '@wagmi/core' + +//////////////////////////////////////////////////////////////////////////////// +// Version +//////////////////////////////////////////////////////////////////////////////// + +export { version } from '../version.js' diff --git a/packages/react/src/exports/query.test.ts b/packages/react/src/exports/query.test.ts new file mode 100644 index 0000000000..002b6abaab --- /dev/null +++ b/packages/react/src/exports/query.test.ts @@ -0,0 +1,100 @@ +import { expect, test } from 'vitest' + +import * as query from './query.js' + +test('exports', () => { + expect(Object.keys(query)).toMatchInlineSnapshot(` + [ + "callQueryKey", + "callQueryOptions", + "connectMutationOptions", + "deployContractMutationOptions", + "disconnectMutationOptions", + "estimateFeesPerGasQueryKey", + "estimateFeesPerGasQueryOptions", + "estimateGasQueryKey", + "estimateGasQueryOptions", + "estimateMaxPriorityFeePerGasQueryKey", + "estimateMaxPriorityFeePerGasQueryOptions", + "getBalanceQueryKey", + "getBalanceQueryOptions", + "getBlockQueryKey", + "getBlockQueryOptions", + "getBlockNumberQueryKey", + "getBlockNumberQueryOptions", + "getBlockTransactionCountQueryKey", + "getBlockTransactionCountQueryOptions", + "getBytecodeQueryKey", + "getBytecodeQueryOptions", + "getCallsStatusQueryKey", + "getCallsStatusQueryOptions", + "getCapabilitiesQueryKey", + "getCapabilitiesQueryOptions", + "getConnectorClientQueryKey", + "getConnectorClientQueryOptions", + "getEnsAddressQueryKey", + "getEnsAddressQueryOptions", + "getEnsAvatarQueryKey", + "getEnsAvatarQueryOptions", + "getEnsNameQueryKey", + "getEnsNameQueryOptions", + "getEnsResolverQueryKey", + "getEnsResolverQueryOptions", + "getEnsTextQueryKey", + "getEnsTextQueryOptions", + "getFeeHistoryQueryKey", + "getFeeHistoryQueryOptions", + "getGasPriceQueryKey", + "getGasPriceQueryOptions", + "getProofQueryKey", + "getProofQueryOptions", + "getStorageAtQueryKey", + "getStorageAtQueryOptions", + "getTokenQueryKey", + "getTokenQueryOptions", + "getTransactionQueryKey", + "getTransactionQueryOptions", + "getTransactionConfirmationsQueryKey", + "getTransactionConfirmationsQueryOptions", + "getTransactionCountQueryKey", + "getTransactionCountQueryOptions", + "getTransactionReceiptQueryKey", + "getTransactionReceiptQueryOptions", + "getWalletClientQueryKey", + "getWalletClientQueryOptions", + "infiniteReadContractsQueryKey", + "infiniteReadContractsQueryOptions", + "prepareTransactionRequestQueryKey", + "prepareTransactionRequestQueryOptions", + "readContractQueryKey", + "readContractQueryOptions", + "readContractsQueryKey", + "readContractsQueryOptions", + "reconnectMutationOptions", + "sendCallsMutationOptions", + "showCallsStatusMutationOptions", + "sendTransactionMutationOptions", + "signMessageMutationOptions", + "signTypedDataMutationOptions", + "switchAccountMutationOptions", + "simulateContractQueryKey", + "simulateContractQueryOptions", + "switchChainMutationOptions", + "verifyMessageQueryKey", + "verifyMessageQueryOptions", + "verifyTypedDataQueryKey", + "verifyTypedDataQueryOptions", + "waitForCallsStatusQueryKey", + "waitForCallsStatusQueryOptions", + "waitForTransactionReceiptQueryKey", + "waitForTransactionReceiptQueryOptions", + "watchAssetMutationOptions", + "writeContractMutationOptions", + "hashFn", + "structuralSharing", + "useInfiniteQuery", + "useMutation", + "useQuery", + ] + `) +}) diff --git a/packages/react/src/exports/query.ts b/packages/react/src/exports/query.ts new file mode 100644 index 0000000000..ff91fee2d9 --- /dev/null +++ b/packages/react/src/exports/query.ts @@ -0,0 +1,19 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/query +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/query' + +export { + type UseInfiniteQueryParameters, + type UseInfiniteQueryReturnType, + type UseMutationParameters, + type UseMutationReturnType, + type UseQueryParameters, + type UseQueryReturnType, + useInfiniteQuery, + useMutation, + useQuery, +} from '../utils/query.js' diff --git a/packages/react/src/hooks/codegen/createUseReadContract.test-d.ts b/packages/react/src/hooks/codegen/createUseReadContract.test-d.ts new file mode 100644 index 0000000000..f0331efed2 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseReadContract.test-d.ts @@ -0,0 +1,152 @@ +import { abi, mainnet, optimism } from '@wagmi/test' +import { assertType, expectTypeOf, test } from 'vitest' + +import { createUseReadContract } from './createUseReadContract.js' + +test('default', () => { + const useReadErc20 = createUseReadContract({ + abi: abi.erc20, + }) + + const result = useReadErc20({ + functionName: 'balanceOf', + args: ['0x'], + chainId: 123, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('select data', () => { + const useReadErc20 = createUseReadContract({ + abi: abi.erc20, + }) + + const result = useReadErc20({ + address: '0x', + functionName: 'balanceOf', + args: ['0x'], + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('multichain address', () => { + const useReadErc20 = createUseReadContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const result = useReadErc20({ + functionName: 'balanceOf', + args: ['0x'], + chainId: mainnet.id, + // ^? + }) + assertType(result.data) + + useReadErc20({ + functionName: 'balanceOf', + args: ['0x'], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + useReadErc20({ + functionName: 'balanceOf', + args: ['0x'], + // @ts-expect-error address not allowed + address: '0x', + }) +}) + +test('overloads', () => { + const useReadViewOverloads = createUseReadContract({ + abi: abi.viewOverloads, + }) + + const result1 = useReadViewOverloads({ + functionName: 'foo', + }) + assertType(result1.data) + + const result2 = useReadViewOverloads({ + functionName: 'foo', + args: [], + }) + assertType(result2.data) + + const result3 = useReadViewOverloads({ + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data) + + const result4 = useReadViewOverloads({ + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data) +}) + +test('functionName', () => { + const useReadErc20BalanceOf = createUseReadContract({ + abi: abi.erc20, + address: '0x', + functionName: 'balanceOf', + }) + + const result = useReadErc20BalanceOf({ + args: ['0x'], + chainId: 1, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('functionName with overloads', () => { + const useReadViewOverloads = createUseReadContract({ + abi: abi.viewOverloads, + functionName: 'foo', + }) + + const result1 = useReadViewOverloads() + assertType(result1.data) + + const result2 = useReadViewOverloads({ + args: [], + }) + assertType(result2.data) + + const result3 = useReadViewOverloads({ + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data) + + const result4 = useReadViewOverloads({ + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data) +}) diff --git a/packages/react/src/hooks/codegen/createUseReadContract.test.ts b/packages/react/src/hooks/codegen/createUseReadContract.test.ts new file mode 100644 index 0000000000..4419c030c5 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseReadContract.test.ts @@ -0,0 +1,177 @@ +import { abi, address, chain } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { createUseReadContract } from './createUseReadContract.js' + +test('default', async () => { + const useReadWagmiMintExample = createUseReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + }) + + const { result } = renderHook(() => + useReadWagmiMintExample({ + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('multichain', async () => { + const useReadWagmiMintExample = createUseReadContract({ + address: { + [chain.mainnet.id]: address.wagmiMintExample, + [chain.mainnet2.id]: address.wagmiMintExample, + }, + abi: abi.wagmiMintExample, + }) + + const { result } = renderHook(() => + useReadWagmiMintExample({ + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 456, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('functionName', async () => { + const useReadWagmiMintExampleBalanceOf = createUseReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + }) + + const { result } = renderHook(() => + useReadWagmiMintExampleBalanceOf({ + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/codegen/createUseReadContract.ts b/packages/react/src/hooks/codegen/createUseReadContract.ts new file mode 100644 index 0000000000..1c5f509e58 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseReadContract.ts @@ -0,0 +1,127 @@ +import type { + Config, + ReadContractErrorType, + ReadContractParameters, + ResolvedRegister, +} from '@wagmi/core' +import type { + ScopeKeyParameter, + UnionCompute, + UnionExactPartial, + UnionStrictOmit, +} from '@wagmi/core/internal' +import type { + ReadContractData, + ReadContractQueryFnData, + ReadContractQueryKey, +} from '@wagmi/core/query' +import type { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' + +import type { ConfigParameter, QueryParameter } from '../../types/properties.js' +import { useAccount } from '../useAccount.js' +import { useChainId } from '../useChainId.js' +import { useConfig } from '../useConfig.js' +import { + type UseReadContractReturnType, + useReadContract, +} from '../useReadContract.js' + +type stateMutability = 'pure' | 'view' + +export type CreateUseReadContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateUseReadContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, + /// + omittedProperties extends 'abi' | 'address' | 'chainId' | 'functionName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (address extends Record ? 'chainId' : never) + | (functionName extends undefined ? never : 'functionName'), +> = < + name extends functionName extends ContractFunctionName + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractData, +>( + parameters?: UnionCompute< + UnionExactPartial< + UnionStrictOmit< + ReadContractParameters, + omittedProperties + > + > & + ScopeKeyParameter & + ConfigParameter & + QueryParameter< + ReadContractQueryFnData, + ReadContractErrorType, + selectData, + ReadContractQueryKey + > + > & + (address extends Record + ? { chainId?: keyof address | undefined } + : unknown), +) => UseReadContractReturnType + +export function createUseReadContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + props: CreateUseReadContractParameters, +): CreateUseReadContractReturnType { + if (props.address !== undefined && typeof props.address === 'object') + return (parameters) => { + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const account = useAccount({ config }) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return useReadContract({ + ...(parameters as any), + ...(props.functionName ? { functionName: props.functionName } : {}), + address: props.address?.[chainId], + abi: props.abi, + }) + } + + return (parameters) => { + return useReadContract({ + ...(parameters as any), + ...(props.address ? { address: props.address } : {}), + ...(props.functionName ? { functionName: props.functionName } : {}), + abi: props.abi, + }) + } +} diff --git a/packages/react/src/hooks/codegen/createUseSimulateContract.test-d.ts b/packages/react/src/hooks/codegen/createUseSimulateContract.test-d.ts new file mode 100644 index 0000000000..5388e69109 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseSimulateContract.test-d.ts @@ -0,0 +1,199 @@ +import { abi, mainnet, optimism } from '@wagmi/test' +import type { Address } from 'viem' +import { assertType, expectTypeOf, test } from 'vitest' + +import { createUseSimulateContract } from './createUseSimulateContract.js' + +test('default', () => { + const useSimulateErc20 = createUseSimulateContract({ + abi: abi.erc20, + }) + + const result = useSimulateErc20({ + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 123, + }) + result.data?.request.chainId + expectTypeOf(result.data).toMatchTypeOf< + | { + result: boolean + request: { + chainId: 123 + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) + +test('select data', () => { + const useSimulateErc20 = createUseSimulateContract({ + abi: abi.erc20, + }) + + const result = useSimulateErc20({ + address: '0x', + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + query: { + select(data) { + expectTypeOf(data.result).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('multichain address', () => { + const useSimulateErc20 = createUseSimulateContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const result = useSimulateErc20({ + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: optimism.id, + }) + expectTypeOf(result.data?.result).toEqualTypeOf() + + useSimulateErc20({ + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + useSimulateErc20({ + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error address not allowed + address: '0x', + }) +}) + +test('overloads', () => { + const useSimulateWriteOverloads = createUseSimulateContract({ + abi: abi.writeOverloads, + }) + + const result1 = useSimulateWriteOverloads({ + functionName: 'foo', + }) + assertType(result1.data?.result) + + const result2 = useSimulateWriteOverloads({ + functionName: 'foo', + args: [], + }) + assertType(result2.data?.result) + + const result3 = useSimulateWriteOverloads({ + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data?.result) + + const result4 = useSimulateWriteOverloads({ + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data?.result) +}) + +test('functionName', () => { + const useSimulateErc20 = createUseSimulateContract({ + abi: abi.erc20, + functionName: 'transferFrom', + }) + + const result = useSimulateErc20({ + args: ['0x', '0x', 123n], + chainId: 123, + }) + result.data?.request.chainId + expectTypeOf(result.data).toMatchTypeOf< + | { + result: boolean + request: { + chainId: 123 + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) + +test('functionName with overloads', () => { + const useSimulateWriteOverloads = createUseSimulateContract({ + abi: abi.writeOverloads, + functionName: 'foo', + }) + + const result1 = useSimulateWriteOverloads({}) + assertType(result1.data?.result) + + const result2 = useSimulateWriteOverloads({ + args: [], + }) + assertType(result2.data?.result) + + const result3 = useSimulateWriteOverloads({ + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data?.result) + + const result4 = useSimulateWriteOverloads({ + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data?.result) +}) diff --git a/packages/react/src/hooks/codegen/createUseSimulateContract.test.ts b/packages/react/src/hooks/codegen/createUseSimulateContract.test.ts new file mode 100644 index 0000000000..c6e70b90ca --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseSimulateContract.test.ts @@ -0,0 +1,258 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, chain, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { createUseSimulateContract } from './createUseSimulateContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const useSimulateWagmiMintExample = createUseSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + }) + + const { result } = renderHook(() => + useSimulateWagmiMintExample({ + functionName: 'mint', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 1, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "simulateContract", + { + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "functionName": "mint", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('multichain', async () => { + await connect(config, { connector }) + + const useReadWagmiMintExample = createUseSimulateContract({ + address: { + [chain.mainnet.id]: address.wagmiMintExample, + [chain.mainnet2.id]: address.wagmiMintExample, + }, + abi: abi.wagmiMintExample, + }) + + const { result } = renderHook(() => + useReadWagmiMintExample({ + functionName: 'mint', + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "chainId": 456, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 456, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "simulateContract", + { + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 456, + "functionName": "mint", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('functionName', async () => { + await connect(config, { connector }) + + const useSimulateWagmiMintExample = createUseSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + }) + + const { result } = renderHook(() => useSimulateWagmiMintExample({})) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 1, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "simulateContract", + { + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "functionName": "mint", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/codegen/createUseSimulateContract.ts b/packages/react/src/hooks/codegen/createUseSimulateContract.ts new file mode 100644 index 0000000000..51758b995a --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseSimulateContract.ts @@ -0,0 +1,120 @@ +import type { + Config, + ResolvedRegister, + SimulateContractErrorType, + SimulateContractParameters, +} from '@wagmi/core' +import type { ScopeKeyParameter, UnionExactPartial } from '@wagmi/core/internal' +import type { + SimulateContractData, + SimulateContractQueryFnData, + SimulateContractQueryKey, +} from '@wagmi/core/query' +import type { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' + +import type { ConfigParameter, QueryParameter } from '../../types/properties.js' +import { useAccount } from '../useAccount.js' +import { useChainId } from '../useChainId.js' +import { useConfig } from '../useConfig.js' +import { + type UseSimulateContractReturnType, + useSimulateContract, +} from '../useSimulateContract.js' + +type stateMutability = 'nonpayable' | 'payable' + +export type CreateUseSimulateContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateUseSimulateContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, +> = < + name extends functionName extends ContractFunctionName + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +>( + parameters?: { + abi?: undefined + address?: address extends undefined ? Address : undefined + functionName?: functionName extends undefined ? name : undefined + chainId?: address extends Record + ? + | keyof address + | (chainId extends keyof address ? chainId : never) + | undefined + : chainId | number | undefined + } & UnionExactPartial< + // TODO: Take `abi` and `address` from above and omit from below (currently breaks inference) + SimulateContractParameters + > & + ScopeKeyParameter & + ConfigParameter & + QueryParameter< + SimulateContractQueryFnData, + SimulateContractErrorType, + selectData, + SimulateContractQueryKey + >, +) => UseSimulateContractReturnType + +export function createUseSimulateContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + props: CreateUseSimulateContractParameters, +): CreateUseSimulateContractReturnType { + if (props.address !== undefined && typeof props.address === 'object') + return (parameters) => { + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const account = useAccount({ config }) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return useSimulateContract({ + ...(parameters as any), + ...(props.functionName ? { functionName: props.functionName } : {}), + address: props.address?.[chainId], + abi: props.abi, + }) + } + + return (parameters) => { + return useSimulateContract({ + ...(parameters as any), + ...(props.address ? { address: props.address } : {}), + ...(props.functionName ? { functionName: props.functionName } : {}), + abi: props.abi, + }) + } +} diff --git a/packages/react/src/hooks/codegen/createUseWatchContractEvent.test-d.ts b/packages/react/src/hooks/codegen/createUseWatchContractEvent.test-d.ts new file mode 100644 index 0000000000..b3a69775b8 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWatchContractEvent.test-d.ts @@ -0,0 +1,123 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { abi, mainnet, optimism } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { createUseWatchContractEvent } from './createUseWatchContractEvent.js' + +test('default', () => { + const useWatchErc20Event = createUseWatchContractEvent({ + abi: abi.erc20, + }) + + useWatchErc20Event({ + eventName: 'Transfer', + chainId: 123, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) + +test('multichain address', () => { + const useWatchErc20Event = createUseWatchContractEvent({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + useWatchErc20Event({ + eventName: 'Transfer', + chainId: mainnet.id, + // ^? + }) + + useWatchErc20Event({ + eventName: 'Transfer', + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + useWatchErc20Event({ + eventName: 'Transfer', + // @ts-expect-error chain id must match address keys + address: '0x', + }) +}) + +test('differing transports', () => { + const useWatchErc20Event = createUseWatchContractEvent({ + abi: abi.erc20, + }) + + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + useWatchErc20Event({ + config, + poll: false, + address: '0x', + onLogs() {}, + }) + + useWatchErc20Event({ + config, + chainId: mainnet.id, + poll: true, + address: '0x', + onLogs() {}, + }) + useWatchErc20Event({ + config, + chainId: mainnet.id, + // @ts-expect-error poll required since http transport + poll: false, + address: '0x', + onLogs() {}, + }) + + useWatchErc20Event({ + config, + chainId: optimism.id, + poll: true, + address: '0x', + onLogs() {}, + }) + useWatchErc20Event({ + config, + chainId: optimism.id, + poll: false, + address: '0x', + onLogs() {}, + }) +}) + +test('eventName', () => { + const useWatchErc20Event = createUseWatchContractEvent({ + abi: abi.erc20, + eventName: 'Transfer', + }) + + useWatchErc20Event({ + chainId: 123, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) diff --git a/packages/react/src/hooks/codegen/createUseWatchContractEvent.test.ts b/packages/react/src/hooks/codegen/createUseWatchContractEvent.test.ts new file mode 100644 index 0000000000..61464fe3d2 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWatchContractEvent.test.ts @@ -0,0 +1,44 @@ +import { abi, address, chain } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import type { WatchEventOnLogsParameter } from 'viem' +import { test } from 'vitest' + +import { createUseWatchContractEvent } from './createUseWatchContractEvent.js' + +test('default', async () => { + const useWatchErc20Event = createUseWatchContractEvent({ + address: address.usdc, + abi: abi.wagmiMintExample, + }) + + let logs: WatchEventOnLogsParameter = [] + renderHook(() => + useWatchErc20Event({ + eventName: 'Transfer', + onLogs(next) { + logs = logs.concat(next) + }, + }), + ) +}) + +test('multichain', async () => { + const useWatchErc20Event = createUseWatchContractEvent({ + address: { + [chain.mainnet.id]: address.usdc, + [chain.mainnet2.id]: address.usdc, + }, + abi: abi.wagmiMintExample, + }) + + let logs: WatchEventOnLogsParameter = [] + renderHook(() => + useWatchErc20Event({ + eventName: 'Transfer', + chainId: chain.mainnet2.id, + onLogs(next) { + logs = logs.concat(next) + }, + }), + ) +}) diff --git a/packages/react/src/hooks/codegen/createUseWatchContractEvent.ts b/packages/react/src/hooks/codegen/createUseWatchContractEvent.ts new file mode 100644 index 0000000000..e453b9442b --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWatchContractEvent.ts @@ -0,0 +1,101 @@ +import type { + Config, + ResolvedRegister, + WatchContractEventParameters, +} from '@wagmi/core' +import type { + UnionCompute, + UnionExactPartial, + UnionStrictOmit, +} from '@wagmi/core/internal' +import type { Abi, Address, ContractEventName } from 'viem' + +import type { + ConfigParameter, + EnabledParameter, +} from '../../types/properties.js' +import { useAccount } from '../useAccount.js' +import { useChainId } from '../useChainId.js' +import { useConfig } from '../useConfig.js' +import { useWatchContractEvent } from '../useWatchContractEvent.js' + +export type CreateUseWatchContractEventParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + eventName extends ContractEventName | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + eventName?: eventName | ContractEventName | undefined +} + +export type CreateUseWatchContractEventReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + eventName extends ContractEventName | undefined, + /// + omittedProperties extends 'abi' | 'address' | 'chainId' | 'eventName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (address extends Record ? 'chainId' : never) + | (eventName extends undefined ? never : 'eventName'), +> = < + name extends eventName extends ContractEventName + ? eventName + : ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters?: UnionCompute< + UnionExactPartial< + UnionStrictOmit< + WatchContractEventParameters, + omittedProperties + > + > & + ConfigParameter & + EnabledParameter + > & + (address extends Record + ? { chainId?: keyof address | undefined } + : unknown), +) => void + +export function createUseWatchContractEvent< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + eventName extends ContractEventName | undefined = undefined, +>( + props: CreateUseWatchContractEventParameters, +): CreateUseWatchContractEventReturnType { + if (props.address !== undefined && typeof props.address === 'object') + return (parameters) => { + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const account = useAccount({ config }) + const chainId = + (parameters as { chainId?: number })?.chainId ?? + account.chainId ?? + configChainId + return useWatchContractEvent({ + ...(parameters as any), + ...(props.eventName ? { eventName: props.eventName } : {}), + address: props.address?.[chainId], + abi: props.abi, + }) + } + + return (parameters) => { + return useWatchContractEvent({ + ...(parameters as any), + ...(props.address ? { address: props.address } : {}), + ...(props.eventName ? { eventName: props.eventName } : {}), + abi: props.abi, + }) + } +} diff --git a/packages/react/src/hooks/codegen/createUseWriteContract.test-d.ts b/packages/react/src/hooks/codegen/createUseWriteContract.test-d.ts new file mode 100644 index 0000000000..14906fda41 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWriteContract.test-d.ts @@ -0,0 +1,153 @@ +import { abi } from '@wagmi/test' +import type { Address, Hash } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { useSimulateContract } from '../useSimulateContract.js' +import { createUseWriteContract } from './createUseWriteContract.js' + +const contextValue = { foo: 'bar' } as const + +test('default', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + }) + + const { writeContract } = useWriteErc20() + writeContract({ + address: '0x', + functionName: 'transfer', + args: ['0x', 123n], + }) +}) + +test('context', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + }) + + const { writeContract } = useWriteErc20({ + mutation: { + onMutate() { + return contextValue + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(variables.functionName).toEqualTypeOf() + expectTypeOf(variables.args).toEqualTypeOf< + readonly unknown[] | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + writeContract( + { + address: '0x', + functionName: 'transfer', + args: ['0x', 123n], + }, + { + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(variables.functionName).toEqualTypeOf<'transfer'>() + expectTypeOf(variables.args).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) + +test('multichain address', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const { writeContract } = useWriteErc20() + writeContract({ + functionName: 'transfer', + args: ['0x', 123n], + chainId: mainnet.id, + // ^? + }) + + writeContract({ + functionName: 'transfer', + args: ['0x', 123n], + // @ts-expect-error chain id must match address keys + chainId: 420, + }) + + writeContract({ + // @ts-expect-error address not allowed + address: '0x', + functionName: 'transfer', + args: ['0x', 123n], + }) +}) + +test('overloads', () => { + const useWriteOverloads = createUseWriteContract({ + abi: abi.writeOverloads, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const { writeContract } = useWriteOverloads() + writeContract({ + functionName: 'foo', + args: [], + }) + + writeContract({ + functionName: 'foo', + args: ['0x'], + }) + + writeContract({ + functionName: 'foo', + args: ['0x', '0x'], + }) +}) + +test('useSimulateContract', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + address: { + [mainnet.id]: '0x', + [optimism.id]: '0x', + }, + }) + + const { data } = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + const { writeContract } = useWriteErc20() + + const request = data?.request + if (request) writeContract(request) +}) + +test('functionName', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + functionName: 'transfer', + }) + + const { writeContract } = useWriteErc20() + writeContract({ + address: '0x', + args: ['0x', 123n], + }) +}) diff --git a/packages/react/src/hooks/codegen/createUseWriteContract.test.ts b/packages/react/src/hooks/codegen/createUseWriteContract.test.ts new file mode 100644 index 0000000000..e89dc62141 --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWriteContract.test.ts @@ -0,0 +1,13 @@ +import { abi } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { test } from 'vitest' + +import { createUseWriteContract } from './createUseWriteContract.js' + +test('default', () => { + const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, + }) + + renderHook(() => useWriteErc20()) +}) diff --git a/packages/react/src/hooks/codegen/createUseWriteContract.ts b/packages/react/src/hooks/codegen/createUseWriteContract.ts new file mode 100644 index 0000000000..9e58fd973b --- /dev/null +++ b/packages/react/src/hooks/codegen/createUseWriteContract.ts @@ -0,0 +1,297 @@ +import type { MutateOptions } from '@tanstack/react-query' +import type { + Config, + ResolvedRegister, + WriteContractErrorType, +} from '@wagmi/core' +import type { + ChainIdParameter, + Compute, + ConnectorParameter, + SelectChains, + UnionCompute, + UnionStrictOmit, +} from '@wagmi/core/internal' +import type { + WriteContractData, + WriteContractVariables, +} from '@wagmi/core/query' +import { useCallback } from 'react' +import type { + Abi, + Account, + Address, + Chain, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem' +import type { WriteContractParameters as viem_WriteContractParameters } from 'viem/actions' + +import { useAccount } from '../useAccount.js' +import { useChainId } from '../useChainId.js' +import { useConfig } from '../useConfig.js' +import { + type UseWriteContractParameters, + useWriteContract, + type UseWriteContractReturnType as wagmi_UseWriteContractReturnType, +} from '../useWriteContract.js' + +type stateMutability = 'nonpayable' | 'payable' + +export type CreateUseWriteContractParameters< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +> = { + abi: abi | Abi | readonly unknown[] + address?: address | Address | Record | undefined + functionName?: + | functionName + | ContractFunctionName + | undefined +} + +export type CreateUseWriteContractReturnType< + abi extends Abi | readonly unknown[], + address extends Address | Record | undefined, + functionName extends ContractFunctionName | undefined, +> = ( + parameters?: UseWriteContractParameters, +) => Compute< + Omit< + wagmi_UseWriteContractReturnType, + 'writeContract' | 'writeContractAsync' + > & { + writeContract: < + const abi2 extends abi, + name extends functionName extends ContractFunctionName< + abi, + stateMutability + > + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + chainId extends config['chains'][number]['id'], + >( + variables: Variables< + abi2, + functionName, + name, + args, + config, + chainId, + address + >, + options?: + | MutateOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + abi2, + name, + args, + config, + chainId, + // use `functionName` to make sure it's not union of all possible function names + name + >, + context + > + | undefined, + ) => void + writeContractAsync: < + const abi2 extends abi, + name extends functionName extends ContractFunctionName< + abi, + stateMutability + > + ? functionName + : ContractFunctionName, + args extends ContractFunctionArgs, + chainId extends config['chains'][number]['id'], + >( + variables: Variables< + abi2, + functionName, + name, + args, + config, + chainId, + address + >, + options?: + | MutateOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + abi2, + name, + args, + config, + chainId, + // use `functionName` to make sure it's not union of all possible function names + name + >, + context + > + | undefined, + ) => Promise + } +> + +export function createUseWriteContract< + const abi extends Abi | readonly unknown[], + const address extends + | Address + | Record + | undefined = undefined, + functionName extends + | ContractFunctionName + | undefined = undefined, +>( + props: CreateUseWriteContractParameters, +): CreateUseWriteContractReturnType { + if (props.address !== undefined && typeof props.address === 'object') + return (parameters) => { + const config = useConfig(parameters) + const result = useWriteContract(parameters) + const configChainId = useChainId({ config }) + const account = useAccount({ config }) + type Args = Parameters + return { + ...(result as any), + writeContract: useCallback( + (...args: Args) => { + let chainId: number | undefined + if (args[0].chainId) chainId = args[0].chainId + else if (args[0].account && args[0].account === account.address) + chainId = account.chainId + else if (args[0].account === undefined) chainId = account.chainId + else chainId = configChainId + + const variables = { + ...(args[0] as any), + address: chainId ? props.address?.[chainId] : undefined, + ...(props.functionName + ? { functionName: props.functionName } + : {}), + abi: props.abi, + } + result.writeContract(variables, args[1] as any) + }, + [ + account.address, + account.chainId, + props, + configChainId, + result.writeContract, + ], + ), + writeContractAsync: useCallback( + (...args: Args) => { + let chainId: number | undefined + if (args[0].chainId) chainId = args[0].chainId + else if (args[0].account && args[0].account === account.address) + chainId = account.chainId + else if (args[0].account === undefined) chainId = account.chainId + else chainId = configChainId + + const variables = { + ...(args[0] as any), + address: chainId ? props.address?.[chainId] : undefined, + ...(props.functionName + ? { functionName: props.functionName } + : {}), + abi: props.abi, + } + return result.writeContractAsync(variables, args[1] as any) + }, + [ + account.address, + account.chainId, + props, + configChainId, + result.writeContractAsync, + ], + ), + } + } + + return (parameters) => { + const result = useWriteContract(parameters) + type Args = Parameters + return { + ...(result as any), + writeContract: useCallback( + (...args: Args) => { + const variables = { + ...(args[0] as any), + ...(props.address ? { address: props.address } : {}), + ...(props.functionName ? { functionName: props.functionName } : {}), + abi: props.abi, + } + result.writeContract(variables, args[1] as any) + }, + [props, result.writeContract], + ), + writeContractAsync: useCallback( + (...args: Args) => { + const variables = { + ...(args[0] as any), + ...(props.address ? { address: props.address } : {}), + ...(props.functionName ? { functionName: props.functionName } : {}), + abi: props.abi, + } + return result.writeContractAsync(variables, args[1] as any) + }, + [props, result.writeContractAsync], + ), + } + } +} + +type Variables< + abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName | undefined, + name extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config, + chainId extends config['chains'][number]['id'], + address extends Address | Record | undefined, + /// + allFunctionNames = ContractFunctionName, + chains extends readonly Chain[] = SelectChains, + omittedProperties extends 'abi' | 'address' | 'functionName' = + | 'abi' + | (address extends undefined ? never : 'address') + | (functionName extends undefined ? never : 'functionName'), +> = UnionCompute< + { + [key in keyof chains]: UnionStrictOmit< + viem_WriteContractParameters< + abi, + name, + args, + chains[key], + Account, + chains[key], + allFunctionNames + >, + omittedProperties | 'chain' + > + }[number] & + (address extends Record + ? { + chainId?: + | keyof address + | (chainId extends keyof address ? chainId : never) + | undefined + } + : Compute>) & + ConnectorParameter & { + /** @deprecated */ + __mode?: 'prepared' + } +> diff --git a/packages/react/src/hooks/useAccount.test-d.ts b/packages/react/src/hooks/useAccount.test-d.ts new file mode 100644 index 0000000000..a22d816d6a --- /dev/null +++ b/packages/react/src/hooks/useAccount.test-d.ts @@ -0,0 +1,68 @@ +import type { Connector } from '@wagmi/core' +import type { Address, Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useAccount } from './useAccount.js' + +test('states', () => { + const result = useAccount() + + switch (result.status) { + case 'reconnecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: Chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: boolean + isConnecting: false + isDisconnected: false + isReconnecting: true + status: 'reconnecting' + }>() + break + } + case 'connecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: Chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: false + isReconnecting: false + isConnecting: true + isDisconnected: false + status: 'connecting' + }>() + break + } + case 'connected': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address + chain: Chain | undefined + chainId: number + connector: Connector + isConnected: true + isConnecting: false + isDisconnected: false + isReconnecting: false + status: 'connected' + }>() + break + } + case 'disconnected': { + expectTypeOf(result).toMatchTypeOf<{ + address: undefined + chain: undefined + chainId: undefined + connector: undefined + isConnected: false + isReconnecting: false + isConnecting: false + isDisconnected: true + status: 'disconnected' + }>() + break + } + } +}) diff --git a/packages/react/src/hooks/useAccount.test.ts b/packages/react/src/hooks/useAccount.test.ts new file mode 100644 index 0000000000..3c4af3c3f9 --- /dev/null +++ b/packages/react/src/hooks/useAccount.test.ts @@ -0,0 +1,29 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => useAccount()) + + expect(result.current.address).not.toBeDefined() + expect(result.current.status).toEqual('disconnected') + + await connect(config, { connector: config.connectors[0]! }) + rerender() + + expect(result.current.address).toBeDefined() + expect(result.current.status).toEqual('connected') + + await disconnect(config) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useAccount({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) diff --git a/packages/react/src/hooks/useAccount.ts b/packages/react/src/hooks/useAccount.ts new file mode 100644 index 0000000000..c7c1779469 --- /dev/null +++ b/packages/react/src/hooks/useAccount.ts @@ -0,0 +1,31 @@ +'use client' + +import { + type Config, + type GetAccountReturnType, + type ResolvedRegister, + getAccount, + watchAccount, +} from '@wagmi/core' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' +import { useSyncExternalStoreWithTracked } from './useSyncExternalStoreWithTracked.js' + +export type UseAccountParameters = + ConfigParameter + +export type UseAccountReturnType = + GetAccountReturnType + +/** https://wagmi.sh/react/api/hooks/useAccount */ +export function useAccount( + parameters: UseAccountParameters = {}, +): UseAccountReturnType { + const config = useConfig(parameters) + + return useSyncExternalStoreWithTracked( + (onChange) => watchAccount(config, { onChange }), + () => getAccount(config), + ) +} diff --git a/packages/react/src/hooks/useAccountEffect.test.ts b/packages/react/src/hooks/useAccountEffect.test.ts new file mode 100644 index 0000000000..b252ee0f98 --- /dev/null +++ b/packages/react/src/hooks/useAccountEffect.test.ts @@ -0,0 +1,77 @@ +import { mock } from '@wagmi/connectors' +import { http, connect, createConfig, disconnect } from '@wagmi/core' +import { accounts, chain, config } from '@wagmi/test' +import { createWrapper, renderHook, waitFor } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test, vi } from 'vitest' + +import { WagmiProvider } from '../context.js' +import { useAccountEffect } from './useAccountEffect.js' +import { useConnect } from './useConnect.js' +import { useDisconnect } from './useDisconnect.js' + +test('parameters: config', () => { + const { result } = renderHook(() => useAccountEffect({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeUndefined() +}) + +test('behavior: connect and disconnect called once', async () => { + const onConnect = vi.fn() + const onDisconnect = vi.fn() + + const { result } = renderHook(() => ({ + useAccountEffect: useAccountEffect({ onConnect, onDisconnect }), + useConnect: useConnect(), + useDisconnect: useDisconnect(), + })) + + result.current.useConnect.connect({ + connector: result.current.useConnect.connectors[0]!, + }) + await waitFor(() => expect(result.current.useConnect.isSuccess).toBeTruthy()) + + result.current.useConnect.connect({ + connector: result.current.useConnect.connectors[0]!, + }) + await waitFor(() => expect(result.current.useConnect.isSuccess).toBeTruthy()) + + result.current.useDisconnect.disconnect() + await waitFor(() => + expect(result.current.useDisconnect.isSuccess).toBeTruthy(), + ) + result.current.useDisconnect.disconnect() + await waitFor(() => + expect(result.current.useDisconnect.isSuccess).toBeTruthy(), + ) + + expect(onConnect).toBeCalledTimes(1) + expect(onDisconnect).toBeCalledTimes(1) +}) + +test('behavior: connect called on reconnect', async () => { + const config = createConfig({ + chains: [chain.mainnet], + connectors: [ + mock({ + accounts, + features: { reconnect: true }, + }), + ], + transports: { [chain.mainnet.id]: http() }, + }) + + await connect(config, { connector: config.connectors[0]! }) + const onConnect = vi.fn((data) => { + expect(data.isReconnected).toBeTruthy() + }) + + renderHook(() => useAccountEffect({ onConnect }), { + wrapper: createWrapper(WagmiProvider, { config, reconnectOnMount: true }), + }) + + await waitFor(() => expect(onConnect).toBeCalledTimes(1)) + + await disconnect(config) +}) diff --git a/packages/react/src/hooks/useAccountEffect.ts b/packages/react/src/hooks/useAccountEffect.ts new file mode 100644 index 0000000000..5c77585e35 --- /dev/null +++ b/packages/react/src/hooks/useAccountEffect.ts @@ -0,0 +1,62 @@ +'use client' + +import { type GetAccountReturnType, watchAccount } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { useEffect } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseAccountEffectParameters = Compute< + { + onConnect?( + data: Compute< + Pick< + Extract, + 'address' | 'addresses' | 'chain' | 'chainId' | 'connector' + > & { + isReconnected: boolean + } + >, + ): void + onDisconnect?(): void + } & ConfigParameter +> + +/** https://wagmi.sh/react/api/hooks/useAccountEffect */ +export function useAccountEffect(parameters: UseAccountEffectParameters = {}) { + const { onConnect, onDisconnect } = parameters + + const config = useConfig(parameters) + + useEffect(() => { + return watchAccount(config, { + onChange(data, prevData) { + if ( + (prevData.status === 'reconnecting' || + (prevData.status === 'connecting' && + prevData.address === undefined)) && + data.status === 'connected' + ) { + const { address, addresses, chain, chainId, connector } = data + const isReconnected = + prevData.status === 'reconnecting' || + // if `previousAccount.status` is `undefined`, the connector connected immediately. + prevData.status === undefined + onConnect?.({ + address, + addresses, + chain, + chainId, + connector, + isReconnected, + }) + } else if ( + prevData.status === 'connected' && + data.status === 'disconnected' + ) + onDisconnect?.() + }, + }) + }, [config, onConnect, onDisconnect]) +} diff --git a/packages/react/src/hooks/useBalance.test-d.ts b/packages/react/src/hooks/useBalance.test-d.ts new file mode 100644 index 0000000000..74630d50c7 --- /dev/null +++ b/packages/react/src/hooks/useBalance.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useBalance } from './useBalance.js' + +test('select data', () => { + const result = useBalance({ + query: { + select(data) { + return data?.value + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useBalance.test.ts b/packages/react/src/hooks/useBalance.test.ts new file mode 100644 index 0000000000..ba7f857808 --- /dev/null +++ b/packages/react/src/hooks/useBalance.test.ts @@ -0,0 +1,312 @@ +import { accounts, chain, testClient, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { type Address, parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' + +import { useBalance } from './useBalance.js' + +const address = accounts[0] + +beforeEach(async () => { + await testClient.mainnet.setBalance({ address, value: parseEther('10000') }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet2.setBalance({ address, value: parseEther('69') }) + await testClient.mainnet2.mine({ blocks: 1 }) +}) + +test('default', async () => { + const { result } = renderHook(() => useBalance({ address })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject( + expect.objectContaining({ + decimals: expect.any(Number), + formatted: expect.any(String), + symbol: expect.any(String), + value: expect.any(BigInt), + }), + ) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useBalance({ address, chainId: chain.mainnet2.id }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "69", + "symbol": "WAG", + "value": 69000000000000000000n, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: token', async () => { + const { result } = renderHook(() => + useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "0.559062564299199392", + "symbol": "DAI", + "value": 559062564299199392n, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "balance", + { + "address": "0x4557B18E779944BFE9d78A672452331C186a9f48", + "chainId": 1, + "token": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: unit', async () => { + const { result } = renderHook(() => + useBalance({ address, chainId: chain.mainnet2.id, unit: 'wei' }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "69000000000000000000", + "symbol": "WAG", + "value": 69000000000000000000n, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "unit": "wei", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let address: Address | undefined = undefined + + const { result, rerender } = renderHook(() => useBalance({ address })) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "balance", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + address = accounts[0] + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "decimals": 18, + "formatted": "10000", + "symbol": "ETH", + "value": 10000000000000000000000n, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "balance", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useBalance()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useBalance.ts b/packages/react/src/hooks/useBalance.ts new file mode 100644 index 0000000000..93568089f7 --- /dev/null +++ b/packages/react/src/hooks/useBalance.ts @@ -0,0 +1,54 @@ +'use client' + +import type { Config, GetBalanceErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetBalanceData, + type GetBalanceOptions, + type GetBalanceQueryKey, + getBalanceQueryOptions, +} from '@wagmi/core/query' +import type { GetBalanceQueryFnData } from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseBalanceParameters< + config extends Config = Config, + selectData = GetBalanceData, +> = Compute< + GetBalanceOptions & + ConfigParameter & + QueryParameter< + GetBalanceQueryFnData, + GetBalanceErrorType, + selectData, + GetBalanceQueryKey + > +> + +export type UseBalanceReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useBalance */ +export function useBalance< + config extends Config = ResolvedRegister['config'], + selectData = GetBalanceData, +>( + parameters: UseBalanceParameters = {}, +): UseBalanceReturnType { + const { address, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getBalanceQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useBlock.test-d.ts b/packages/react/src/hooks/useBlock.test-d.ts new file mode 100644 index 0000000000..04aae75076 --- /dev/null +++ b/packages/react/src/hooks/useBlock.test-d.ts @@ -0,0 +1,64 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { mainnet, optimism } from '@wagmi/core/chains' +import { expectTypeOf, test } from 'vitest' + +import { useBlock } from './useBlock.js' + +test('select data', () => { + const result = useBlock({ + query: { + select(data) { + return data?.number + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + useBlock({ + config, + watch: { + poll: false, + }, + }) + + useBlock({ + config, + chainId: mainnet.id, + watch: { + poll: true, + }, + }) + useBlock({ + config, + chainId: mainnet.id, + watch: { + // @ts-expect-error + poll: false, + }, + }) + + useBlock({ + config, + chainId: optimism.id, + watch: { + poll: true, + }, + }) + useBlock({ + config, + chainId: optimism.id, + watch: { + poll: false, + }, + }) +}) diff --git a/packages/react/src/hooks/useBlock.test.ts b/packages/react/src/hooks/useBlock.test.ts new file mode 100644 index 0000000000..0d2095ec61 --- /dev/null +++ b/packages/react/src/hooks/useBlock.test.ts @@ -0,0 +1,67 @@ +import { testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useBlock } from './useBlock.js' + +test('mounts', async () => { + const { result } = renderHook(() => useBlock()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeDefined() + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "block", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: watch', async () => { + await testClient.mainnet.restart() + + const { result } = renderHook(() => useBlock({ watch: true })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + const block = result.current.data! + expect(block).toBeDefined() + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(() => { + expect(result.current.data?.number).toEqual(block.number + 1n) + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(() => { + expect(result.current.data?.number).toEqual(block.number + 2n) + }) +}) diff --git a/packages/react/src/hooks/useBlock.ts b/packages/react/src/hooks/useBlock.ts new file mode 100644 index 0000000000..1a16a5bd9e --- /dev/null +++ b/packages/react/src/hooks/useBlock.ts @@ -0,0 +1,131 @@ +'use client' + +import { useQueryClient } from '@tanstack/react-query' +import type { Config, GetBlockErrorType, ResolvedRegister } from '@wagmi/core' +import type { + Compute, + UnionCompute, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { + type GetBlockData, + type GetBlockOptions, + type GetBlockQueryFnData, + type GetBlockQueryKey, + getBlockQueryOptions, +} from '@wagmi/core/query' +import type { BlockTag } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { + type UseWatchBlocksParameters, + useWatchBlocks, +} from './useWatchBlocks.js' + +export type UseBlockParameters< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockData, +> = Compute< + GetBlockOptions & + ConfigParameter & + QueryParameter< + GetBlockQueryFnData, + GetBlockErrorType, + selectData, + GetBlockQueryKey + > & { + watch?: + | boolean + | UnionCompute< + UnionStrictOmit< + UseWatchBlocksParameters< + includeTransactions, + blockTag, + config, + chainId + >, + 'chainId' | 'config' | 'onBlock' | 'onError' + > + > + | undefined + } +> + +export type UseBlockReturnType< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/hooks/useBlock */ +export function useBlock< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockData, +>( + parameters: UseBlockParameters< + includeTransactions, + blockTag, + config, + chainId, + selectData + > = {}, +): UseBlockReturnType< + includeTransactions, + blockTag, + config, + chainId, + selectData +> { + const { query = {}, watch } = parameters + + const config = useConfig(parameters) + const queryClient = useQueryClient() + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + const options = getBlockQueryOptions(config, { + ...parameters, + chainId, + }) + const enabled = Boolean(query.enabled ?? true) + + useWatchBlocks({ + ...({ + config: parameters.config, + chainId: parameters.chainId!, + ...(typeof watch === 'object' ? watch : {}), + } as UseWatchBlocksParameters), + enabled: Boolean( + enabled && (typeof watch === 'object' ? watch.enabled : watch), + ), + onBlock(block) { + queryClient.setQueryData(options.queryKey, block) + }, + }) + + return useQuery({ + ...(query as any), + ...options, + enabled, + }) as UseBlockReturnType< + includeTransactions, + blockTag, + config, + chainId, + selectData + > +} diff --git a/packages/react/src/hooks/useBlockNumber.test-d.ts b/packages/react/src/hooks/useBlockNumber.test-d.ts new file mode 100644 index 0000000000..dfb4ca4dbe --- /dev/null +++ b/packages/react/src/hooks/useBlockNumber.test-d.ts @@ -0,0 +1,65 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { mainnet, optimism } from '@wagmi/core/chains' +import { expectTypeOf, test } from 'vitest' + +import { useBlockNumber } from './useBlockNumber.js' + +test('select data', () => { + const result = useBlockNumber({ + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + useBlockNumber({ + config, + watch: { + poll: false, + }, + }) + + useBlockNumber({ + config, + chainId: mainnet.id, + watch: { + poll: true, + }, + }) + useBlockNumber({ + config, + chainId: mainnet.id, + watch: { + // @ts-expect-error + poll: false, + }, + }) + + useBlockNumber({ + config, + chainId: optimism.id, + watch: { + poll: true, + }, + }) + useBlockNumber({ + config, + chainId: optimism.id, + watch: { + poll: false, + }, + }) +}) diff --git a/packages/react/src/hooks/useBlockNumber.test.ts b/packages/react/src/hooks/useBlockNumber.test.ts new file mode 100644 index 0000000000..93006a6663 --- /dev/null +++ b/packages/react/src/hooks/useBlockNumber.test.ts @@ -0,0 +1,68 @@ +import { testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useBlockNumber } from './useBlockNumber.js' + +test('mounts', async () => { + await testClient.mainnet.resetFork() + + const { result } = renderHook(() => useBlockNumber()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 19258213n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockNumber", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: watch', async () => { + await testClient.mainnet.restart() + + const { result } = renderHook(() => useBlockNumber({ watch: true })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + const blockNumber = result.current.data! + expect(result.current.data).toMatchInlineSnapshot('19258213n') + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(() => { + expect(result.current.data).toEqual(blockNumber + 1n) + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(() => { + expect(result.current.data).toEqual(blockNumber + 2n) + }) +}) diff --git a/packages/react/src/hooks/useBlockNumber.ts b/packages/react/src/hooks/useBlockNumber.ts new file mode 100644 index 0000000000..f91d8466e3 --- /dev/null +++ b/packages/react/src/hooks/useBlockNumber.ts @@ -0,0 +1,97 @@ +'use client' + +import { useQueryClient } from '@tanstack/react-query' +import type { + Config, + GetBlockNumberErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { + Compute, + UnionCompute, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { + type GetBlockNumberData, + type GetBlockNumberOptions, + type GetBlockNumberQueryFnData, + type GetBlockNumberQueryKey, + getBlockNumberQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { + type UseWatchBlockNumberParameters, + useWatchBlockNumber, +} from './useWatchBlockNumber.js' + +export type UseBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +> = Compute< + GetBlockNumberOptions & + ConfigParameter & + QueryParameter< + GetBlockNumberQueryFnData, + GetBlockNumberErrorType, + selectData, + GetBlockNumberQueryKey + > & { + watch?: + | boolean + | UnionCompute< + UnionStrictOmit< + UseWatchBlockNumberParameters, + 'chainId' | 'config' | 'onBlockNumber' | 'onError' + > + > + | undefined + } +> + +export type UseBlockNumberReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useBlockNumber */ +export function useBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +>( + parameters: UseBlockNumberParameters = {}, +): UseBlockNumberReturnType { + const { query = {}, watch } = parameters + + const config = useConfig(parameters) + const queryClient = useQueryClient() + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + const options = getBlockNumberQueryOptions(config, { + ...parameters, + chainId, + }) + + useWatchBlockNumber({ + ...({ + config: parameters.config, + chainId: parameters.chainId, + ...(typeof watch === 'object' ? watch : {}), + } as UseWatchBlockNumberParameters), + enabled: Boolean( + (query.enabled ?? true) && + (typeof watch === 'object' ? watch.enabled : watch), + ), + onBlockNumber(blockNumber) { + queryClient.setQueryData(options.queryKey, blockNumber) + }, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useBlockTransactionCount.test-d.ts b/packages/react/src/hooks/useBlockTransactionCount.test-d.ts new file mode 100644 index 0000000000..ff23fcfe95 --- /dev/null +++ b/packages/react/src/hooks/useBlockTransactionCount.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useBlockTransactionCount } from './useBlockTransactionCount.js' + +test('select data', () => { + const result = useBlockTransactionCount({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useBlockTransactionCount.test.ts b/packages/react/src/hooks/useBlockTransactionCount.test.ts new file mode 100644 index 0000000000..dad6c95506 --- /dev/null +++ b/packages/react/src/hooks/useBlockTransactionCount.test.ts @@ -0,0 +1,231 @@ +import { chain } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useBlockTransactionCount } from './useBlockTransactionCount.js' + +test('default', async () => { + const { result } = renderHook(() => useBlockTransactionCount({})) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockTransactionCount", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useBlockTransactionCount({ chainId: chain.mainnet2.id }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockTransactionCount", + { + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useBlockTransactionCount({ blockNumber: 13677382n }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockTransactionCount", + { + "blockNumber": 13677382n, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockHash', async () => { + const { result } = renderHook(() => + useBlockTransactionCount({ + blockHash: + '0x6201f37a245850d1f11e4be3ac45bc51bd9d43ee4a127192cad550f351cfa575', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockTransactionCount", + { + "blockHash": "0x6201f37a245850d1f11e4be3ac45bc51bd9d43ee4a127192cad550f351cfa575", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockTag', async () => { + const { result } = renderHook(() => + useBlockTransactionCount({ + blockTag: 'safe', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "blockTransactionCount", + { + "blockTag": "safe", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useBlockTransactionCount.ts b/packages/react/src/hooks/useBlockTransactionCount.ts new file mode 100644 index 0000000000..edbd6e8ddf --- /dev/null +++ b/packages/react/src/hooks/useBlockTransactionCount.ts @@ -0,0 +1,67 @@ +'use client' + +import type { + Config, + GetBlockTransactionCountErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { UnionCompute } from '@wagmi/core/internal' +import { + type GetBlockTransactionCountData, + type GetBlockTransactionCountOptions, + type GetBlockTransactionCountQueryFnData, + type GetBlockTransactionCountQueryKey, + getBlockTransactionCountQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseBlockTransactionCountParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockTransactionCountData, +> = UnionCompute< + GetBlockTransactionCountOptions & + ConfigParameter & + QueryParameter< + GetBlockTransactionCountQueryFnData, + GetBlockTransactionCountErrorType, + selectData, + GetBlockTransactionCountQueryKey + > +> + +export type UseBlockTransactionCountReturnType< + selectData = GetBlockTransactionCountData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useBlockTransactionCount */ +export function useBlockTransactionCount< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockTransactionCountData, +>( + parameters: UseBlockTransactionCountParameters< + config, + chainId, + selectData + > = {}, +): UseBlockTransactionCountReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + const options = getBlockTransactionCountQueryOptions(config, { + ...parameters, + chainId, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useBytecode.test-d.ts b/packages/react/src/hooks/useBytecode.test-d.ts new file mode 100644 index 0000000000..eff094e710 --- /dev/null +++ b/packages/react/src/hooks/useBytecode.test-d.ts @@ -0,0 +1,15 @@ +import { expectTypeOf, test } from 'vitest' + +import type { Hex } from 'viem' +import { useBytecode } from './useBytecode.js' + +test('select data', () => { + const result = useBytecode({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useBytecode.test.ts b/packages/react/src/hooks/useBytecode.test.ts new file mode 100644 index 0000000000..4c25fa9558 --- /dev/null +++ b/packages/react/src/hooks/useBytecode.test.ts @@ -0,0 +1,291 @@ +import { address, chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import type { Address } from 'viem' +import { useBytecode } from './useBytecode.js' + +test('default', async () => { + const { result } = renderHook(() => + useBytecode({ + address: address.wagmiMintExample, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + expect(data).toMatch(/^0x.*/) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useBytecode({ + address: address.wagmiMintExample, + blockNumber: 15564163n, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockNumber": 15564163n, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockTag', async () => { + const { result } = renderHook(() => + useBytecode({ + address: address.wagmiMintExample, + blockTag: 'earliest', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockTag": "earliest", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useBytecode({ + address: address.wagmiMintExample, + chainId: chain.optimism.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 10, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let contractAddress: Address | undefined = undefined + + const { result, rerender } = renderHook(() => + useBytecode({ + address: contractAddress, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "getBytecode", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + contractAddress = address.wagmiMintExample + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + expect(data).toMatch(/^0x.*/) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useBytecode()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useBytecode.ts b/packages/react/src/hooks/useBytecode.ts new file mode 100644 index 0000000000..3dd01d438a --- /dev/null +++ b/packages/react/src/hooks/useBytecode.ts @@ -0,0 +1,57 @@ +'use client' + +import type { + Config, + GetBytecodeErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetBytecodeData, + type GetBytecodeOptions, + type GetBytecodeQueryKey, + getBytecodeQueryOptions, +} from '@wagmi/core/query' +import type { GetBytecodeQueryFnData } from '@wagmi/core/query' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseBytecodeParameters< + config extends Config = Config, + selectData = GetBytecodeData, +> = Compute< + GetBytecodeOptions & + ConfigParameter & + QueryParameter< + GetBytecodeQueryFnData, + GetBytecodeErrorType, + selectData, + GetBytecodeQueryKey + > +> + +export type UseBytecodeReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useBytecode */ +export function useBytecode< + config extends Config = ResolvedRegister['config'], + selectData = GetBytecodeData, +>( + parameters: UseBytecodeParameters = {}, +): UseBytecodeReturnType { + const { address, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getBytecodeQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useCall.test-d.ts b/packages/react/src/hooks/useCall.test-d.ts new file mode 100644 index 0000000000..5c47b096c7 --- /dev/null +++ b/packages/react/src/hooks/useCall.test-d.ts @@ -0,0 +1,14 @@ +import type { CallReturnType } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { useCall } from './useCall.js' + +test('select data', () => { + const result = useCall({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useCall.test.ts b/packages/react/src/hooks/useCall.test.ts new file mode 100644 index 0000000000..ee8ce813c4 --- /dev/null +++ b/packages/react/src/hooks/useCall.test.ts @@ -0,0 +1,224 @@ +import { accounts, address, chain } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useCall } from './useCall.js' + +const name4bytes = '0x06fdde03' + +const account = accounts[0] + +test('default', async () => { + const { result } = renderHook(() => + useCall({ + account, + data: name4bytes, + to: address.wagmiMintExample, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +// TODO: Re-enable +test.skip('parameters: blockTag', async () => { + const { result } = renderHook(() => + useCall({ + account, + data: name4bytes, + to: address.wagmiMintExample, + blockTag: 'safe', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "safe", + "chainId": 1, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +// TODO: Re-enable +test.skip('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useCall({ + account, + data: name4bytes, + to: address.wagmiMintExample, + blockNumber: 16280770n, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 16280770n, + "chainId": 1, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useCall({ + account, + data: name4bytes, + to: address.wagmiMintExample, + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "call", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "data": "0x06fdde03", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useCall.ts b/packages/react/src/hooks/useCall.ts new file mode 100644 index 0000000000..423e04fbff --- /dev/null +++ b/packages/react/src/hooks/useCall.ts @@ -0,0 +1,55 @@ +'use client' + +import type { CallErrorType, Config, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type CallData, + type CallOptions, + type CallQueryKey, + callQueryOptions, +} from '@wagmi/core/query' +import type { CallQueryFnData } from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseCallParameters< + config extends Config = Config, + selectData = CallData, +> = Compute< + CallOptions & + ConfigParameter & + QueryParameter< + CallQueryFnData, + CallErrorType, + selectData, + CallQueryKey + > +> + +export type UseCallReturnType = UseQueryReturnType< + selectData, + CallErrorType +> + +/** https://wagmi.sh/react/api/hooks/useCall */ +export function useCall< + config extends Config = ResolvedRegister['config'], + selectData = CallData, +>( + parameters: UseCallParameters = {}, +): UseCallReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = callQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useCallsStatus.test.ts b/packages/react/src/hooks/useCallsStatus.test.ts new file mode 100644 index 0000000000..f3af1c24bc --- /dev/null +++ b/packages/react/src/hooks/useCallsStatus.test.ts @@ -0,0 +1,101 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config, testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useCallsStatus } from './useCallsStatus.js' +import { useSendCalls } from './useSendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSendCalls()) + + result.current.sendCalls({ + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { result: result_2 } = renderHook(() => + useCallsStatus({ id: result.current.data?.id! }), + ) + await waitFor(() => expect(result_2.current.isSuccess).toBeTruthy()) + + expect(result_2.current.data).toMatchInlineSnapshot( + ` + { + "atomic": false, + "chainId": 1, + "id": "0x5dedb5a4ff8968db37459b52b83cbdc92b01c9c709c9cff26e345ef5cf27f92e", + "receipts": [], + "status": "pending", + "statusCode": 100, + "version": "2.0.0", + } + `, + ) + + await testClient.mainnet.mine({ blocks: 1 }) + + const { result: result_3 } = renderHook(() => + useCallsStatus({ id: result.current.data?.id! }), + ) + await waitFor(() => expect(result_3.current.isSuccess).toBeTruthy()) + + expect(result_3.current.data?.status).toBe('success') + expect(result_3.current.data?.statusCode).toBe(200) + expect( + result_3.current.data?.receipts?.map((x) => ({ + ...x, + blockHash: undefined, + })), + ).toMatchInlineSnapshot( + ` + [ + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0x13c53b2d4d9da424835525349cd66e553330f323d6fb19458b801ae1f7989a41", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xd8397b3e82b061c26a0c2093f1ceca0c3662a512614f7d6370349e89d0eea007", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x4d26e346593d9ea265bb164b115e89aa92df43b0b8778ac75d4ad28e2a22b101", + }, + ] + `, + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useCallsStatus.ts b/packages/react/src/hooks/useCallsStatus.ts new file mode 100644 index 0000000000..ab4c90c1f2 --- /dev/null +++ b/packages/react/src/hooks/useCallsStatus.ts @@ -0,0 +1,52 @@ +'use client' + +import type { + Config, + GetCallsStatusErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetCallsStatusData, + type GetCallsStatusOptions, + type GetCallsStatusQueryFnData, + type GetCallsStatusQueryKey, + getCallsStatusQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseCallsStatusParameters< + config extends Config = Config, + selectData = GetCallsStatusData, +> = Compute< + GetCallsStatusOptions & + ConfigParameter & + QueryParameter< + GetCallsStatusQueryFnData, + GetCallsStatusErrorType, + selectData, + GetCallsStatusQueryKey + > +> + +export type UseCallsStatusReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useCallsStatus */ +export function useCallsStatus< + config extends Config = ResolvedRegister['config'], + selectData = GetCallsStatusData, +>( + parameters: UseCallsStatusParameters, +): UseCallsStatusReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + + const options = getCallsStatusQueryOptions(config, parameters) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useCapabilities.test.ts b/packages/react/src/hooks/useCapabilities.test.ts new file mode 100644 index 0000000000..65dcf171b5 --- /dev/null +++ b/packages/react/src/hooks/useCapabilities.test.ts @@ -0,0 +1,167 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useCapabilities } from './useCapabilities.js' + +const connector = config.connectors[0]! + +test('mounts', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useCapabilities()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "8453": { + "paymasterService": { + "supported": true, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": true, + }, + }, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "capabilities", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('args: account', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useCapabilities({ account: accounts[1] })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "8453": { + "paymasterService": { + "supported": false, + }, + "sessionKeys": { + "supported": true, + }, + }, + "84532": { + "paymasterService": { + "supported": false, + }, + }, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "capabilities", + { + "account": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: not connected', async () => { + const { result } = renderHook(() => useCapabilities()) + + await waitFor(() => expect(result.current.isError).toBeTruthy()) + + const { error, failureReason: _, ...rest } = result.current + expect(error?.message.includes('Connector not connected.')).toBeTruthy() + expect(rest).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "errorUpdateCount": 2, + "errorUpdatedAt": 1675209600000, + "failureCount": 1, + "fetchStatus": "idle", + "isError": true, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": true, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": false, + "queryKey": [ + "capabilities", + { + "account": undefined, + }, + ], + "refetch": [Function], + "status": "error", + } + `) +}) diff --git a/packages/react/src/hooks/useCapabilities.ts b/packages/react/src/hooks/useCapabilities.ts new file mode 100644 index 0000000000..399a56cd38 --- /dev/null +++ b/packages/react/src/hooks/useCapabilities.ts @@ -0,0 +1,62 @@ +'use client' + +import type { + Config, + GetCapabilitiesErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetCapabilitiesData, + type GetCapabilitiesOptions, + type GetCapabilitiesQueryFnData, + type GetCapabilitiesQueryKey, + getCapabilitiesQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useAccount } from './useAccount.js' +import { useConfig } from './useConfig.js' + +export type UseCapabilitiesParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = GetCapabilitiesData, +> = Compute< + GetCapabilitiesOptions & + ConfigParameter & + QueryParameter< + GetCapabilitiesQueryFnData, + GetCapabilitiesErrorType, + selectData, + GetCapabilitiesQueryKey + > +> + +export type UseCapabilitiesReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = GetCapabilitiesData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useCapabilities */ +export function useCapabilities< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = GetCapabilitiesData, +>( + parameters: UseCapabilitiesParameters = {}, +): UseCapabilitiesReturnType { + const { account, query = {} } = parameters + + const { address } = useAccount() + const config = useConfig(parameters) + + const options = getCapabilitiesQueryOptions(config, { + ...parameters, + account: account ?? address, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useChainId.test-d.ts b/packages/react/src/hooks/useChainId.test-d.ts new file mode 100644 index 0000000000..682b5fab7a --- /dev/null +++ b/packages/react/src/hooks/useChainId.test-d.ts @@ -0,0 +1,14 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useChainId } from './useChainId.js' + +test('default', () => { + const chainId = useChainId() + expectTypeOf(chainId).toEqualTypeOf() +}) + +test('parameters: config', () => { + const chainId = useChainId({ config }) + expectTypeOf(chainId).toEqualTypeOf<1 | 456 | 10>() +}) diff --git a/packages/react/src/hooks/useChainId.test.ts b/packages/react/src/hooks/useChainId.test.ts new file mode 100644 index 0000000000..e4745b46e3 --- /dev/null +++ b/packages/react/src/hooks/useChainId.test.ts @@ -0,0 +1,24 @@ +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useChainId } from './useChainId.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => useChainId()) + + expect(result.current).toMatchInlineSnapshot('1') + + config.setState((x) => ({ ...x, chainId: 456 })) + rerender() + + expect(result.current).toMatchInlineSnapshot('456') +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useChainId({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) diff --git a/packages/react/src/hooks/useChainId.ts b/packages/react/src/hooks/useChainId.ts new file mode 100644 index 0000000000..ce7cb208dd --- /dev/null +++ b/packages/react/src/hooks/useChainId.ts @@ -0,0 +1,32 @@ +'use client' + +import { + type Config, + type GetChainIdReturnType, + type ResolvedRegister, + getChainId, + watchChainId, +} from '@wagmi/core' +import { useSyncExternalStore } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseChainIdParameters = + ConfigParameter + +export type UseChainIdReturnType = + GetChainIdReturnType + +/** https://wagmi.sh/react/api/hooks/useChainId */ +export function useChainId( + parameters: UseChainIdParameters = {}, +): UseChainIdReturnType { + const config = useConfig(parameters) + + return useSyncExternalStore( + (onChange) => watchChainId(config, { onChange }), + () => getChainId(config), + () => getChainId(config), + ) +} diff --git a/packages/react/src/hooks/useChains.test.ts b/packages/react/src/hooks/useChains.test.ts new file mode 100644 index 0000000000..1d9d6fca4f --- /dev/null +++ b/packages/react/src/hooks/useChains.test.ts @@ -0,0 +1,31 @@ +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useChains } from './useChains.js' + +test('default', async () => { + const { result } = renderHook(() => useChains()) + + expect(result.current.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + 10, + ] + `) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useChains({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current.map((x) => x.id)).toMatchInlineSnapshot(` + [ + 1, + 456, + 10, + ] + `) +}) diff --git a/packages/react/src/hooks/useChains.ts b/packages/react/src/hooks/useChains.ts new file mode 100644 index 0000000000..b3e93b417a --- /dev/null +++ b/packages/react/src/hooks/useChains.ts @@ -0,0 +1,32 @@ +'use client' + +import { + type Config, + type GetChainsReturnType, + type ResolvedRegister, + getChains, +} from '@wagmi/core' +import { watchChains } from '@wagmi/core/internal' +import { useSyncExternalStore } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseChainsParameters = + ConfigParameter + +export type UseChainsReturnType = + GetChainsReturnType + +/** https://wagmi.sh/react/api/hooks/useChains */ +export function useChains( + parameters: UseChainsParameters = {}, +): UseChainsReturnType { + const config = useConfig(parameters) + + return useSyncExternalStore( + (onChange) => watchChains(config, { onChange }), + () => getChains(config), + () => getChains(config), + ) +} diff --git a/packages/react/src/hooks/useClient.test-d.ts b/packages/react/src/hooks/useClient.test-d.ts new file mode 100644 index 0000000000..80ee4ec48d --- /dev/null +++ b/packages/react/src/hooks/useClient.test-d.ts @@ -0,0 +1,40 @@ +import { chain, config } from '@wagmi/test' +import type { Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useClient } from './useClient.js' + +test('default', () => { + const client = useClient({ config }) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = useClient({ + config, + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + { + const client = useClient({ chainId: 123456 }) + if (client) { + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf() + } else { + expectTypeOf(client).toEqualTypeOf() + } + } + + const client = useClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useClient.test.ts b/packages/react/src/hooks/useClient.test.ts new file mode 100644 index 0000000000..93ef8d9fa9 --- /dev/null +++ b/packages/react/src/hooks/useClient.test.ts @@ -0,0 +1,30 @@ +import { switchChain } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useClient } from './useClient.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => useClient()) + + expect(result.current?.chain.id).toEqual(1) + + await switchChain(config, { chainId: 456 }) + rerender() + + expect(result.current?.chain.id).toEqual(456) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useClient({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) + +test('behavior: unconfigured chain', () => { + const { result } = renderHook(() => useClient({ chainId: 123456 })) + expect(result.current).toBeUndefined() +}) diff --git a/packages/react/src/hooks/useClient.ts b/packages/react/src/hooks/useClient.ts new file mode 100644 index 0000000000..abfee745b3 --- /dev/null +++ b/packages/react/src/hooks/useClient.ts @@ -0,0 +1,49 @@ +'use client' + +import { + type Config, + type GetClientParameters, + type GetClientReturnType, + type ResolvedRegister, + getClient, + watchClient, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseClientParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = Compute & ConfigParameter> + +export type UseClientReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = GetClientReturnType + +/** https://wagmi.sh/react/api/hooks/useClient */ +export function useClient< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +>( + parameters: UseClientParameters = {}, +): UseClientReturnType { + const config = useConfig(parameters) + + return useSyncExternalStoreWithSelector( + (onChange) => watchClient(config, { onChange }), + () => getClient(config, parameters), + () => getClient(config, parameters), + (x) => x, + (a, b) => a?.uid === b?.uid, + ) as any +} diff --git a/packages/react/src/hooks/useConfig.test-d.ts b/packages/react/src/hooks/useConfig.test-d.ts new file mode 100644 index 0000000000..f2b9d2bb31 --- /dev/null +++ b/packages/react/src/hooks/useConfig.test-d.ts @@ -0,0 +1,16 @@ +import type { Config } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useConfig } from './useConfig.js' + +test('default', async () => { + const result = useConfig() + expectTypeOf(result).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useConfig({ config }) + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useConfig.test.ts b/packages/react/src/hooks/useConfig.test.ts new file mode 100644 index 0000000000..e0fc9c40ce --- /dev/null +++ b/packages/react/src/hooks/useConfig.test.ts @@ -0,0 +1,23 @@ +import { createWrapper, renderHook } from '@wagmi/test/react' +import { expect, test, vi } from 'vitest' + +import { useConfig } from './useConfig.js' + +test('mounts', () => { + const { result } = renderHook(() => useConfig()) + expect(result.current).toBeDefined() +}) + +test('behavior: throws when not inside Provider', () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + try { + renderHook(() => useConfig(), { + wrapper: createWrapper(() => null, undefined), + }) + } catch (error) { + expect(error).toMatchInlineSnapshot( + '[Error: `useConfig` must be used within `WagmiProvider`.]', + ) + } +}) diff --git a/packages/react/src/hooks/useConfig.ts b/packages/react/src/hooks/useConfig.ts new file mode 100644 index 0000000000..af9ea5b9c0 --- /dev/null +++ b/packages/react/src/hooks/useConfig.ts @@ -0,0 +1,22 @@ +'use client' + +import type { Config, ResolvedRegister } from '@wagmi/core' +import { useContext } from 'react' + +import { WagmiContext } from '../context.js' +import { WagmiProviderNotFoundError } from '../errors/context.js' +import type { ConfigParameter } from '../types/properties.js' + +export type UseConfigParameters = + ConfigParameter + +export type UseConfigReturnType = config + +/** https://wagmi.sh/react/api/hooks/useConfig */ +export function useConfig( + parameters: UseConfigParameters = {}, +): UseConfigReturnType { + const config = parameters.config ?? useContext(WagmiContext) + if (!config) throw new WagmiProviderNotFoundError() + return config as UseConfigReturnType +} diff --git a/packages/react/src/hooks/useConnect.test-d.ts b/packages/react/src/hooks/useConnect.test-d.ts new file mode 100644 index 0000000000..e678d2edd8 --- /dev/null +++ b/packages/react/src/hooks/useConnect.test-d.ts @@ -0,0 +1,124 @@ +import type { + ConnectErrorType, + Connector, + CreateConnectorFn, +} from '@wagmi/core' +import { config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useConnect } from './useConnect.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { connect, context, data, error, variables } = useConnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf< + | { + chainId?: number | undefined + connector: CreateConnectorFn | Connector + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + connect( + { + connector, + foo: 'bar', + }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useConnect.test.ts b/packages/react/src/hooks/useConnect.test.ts new file mode 100644 index 0000000000..d80ff30509 --- /dev/null +++ b/packages/react/src/hooks/useConnect.test.ts @@ -0,0 +1,35 @@ +import { disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { afterEach, expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useConnect } from './useConnect.js' + +const connector = config.connectors[0]! + +afterEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test('default', async () => { + const { result } = renderHook(() => ({ + useAccount: useAccount(), + useConnect: useConnect(), + })) + + expect(result.current.useAccount.address).not.toBeDefined() + expect(result.current.useAccount.status).toEqual('disconnected') + + result.current.useConnect.connect({ + connector: result.current.useConnect.connectors[0]!, + }) + + await waitFor(() => + expect(result.current.useAccount.isConnected).toBeTruthy(), + ) + + expect(result.current.useAccount.address).toBeDefined() + expect(result.current.useAccount.status).toEqual('connected') +}) diff --git a/packages/react/src/hooks/useConnect.ts b/packages/react/src/hooks/useConnect.ts new file mode 100644 index 0000000000..acfe69234d --- /dev/null +++ b/packages/react/src/hooks/useConnect.ts @@ -0,0 +1,90 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ConnectErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ConnectData, + type ConnectMutate, + type ConnectMutateAsync, + type ConnectVariables, + connectMutationOptions, +} from '@wagmi/core/query' +import { useEffect } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { type UseConnectorsReturnType, useConnectors } from './useConnectors.js' + +export type UseConnectParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > + | undefined + } +> + +export type UseConnectReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > & { + connect: ConnectMutate + connectAsync: ConnectMutateAsync + connectors: Compute | config['connectors'] + } +> + +/** https://wagmi.sh/react/api/hooks/useConnect */ +export function useConnect< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseConnectParameters = {}, +): UseConnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = connectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + // Reset mutation back to an idle state when the connector disconnects. + useEffect(() => { + return config.subscribe( + ({ status }) => status, + (status, previousStatus) => { + if (previousStatus === 'connected' && status === 'disconnected') + result.reset() + }, + ) + }, [config, result.reset]) + + type Return = UseConnectReturnType + return { + ...(result as Return), + connect: mutate as Return['connect'], + connectAsync: mutateAsync as Return['connectAsync'], + connectors: useConnectors({ config }), + } +} diff --git a/packages/react/src/hooks/useConnections.test.ts b/packages/react/src/hooks/useConnections.test.ts new file mode 100644 index 0000000000..1eb4febcdb --- /dev/null +++ b/packages/react/src/hooks/useConnections.test.ts @@ -0,0 +1,25 @@ +import { connect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useConnections } from './useConnections.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => useConnections()) + + expect(result.current).toEqual([]) + + await connect(config, { connector: config.connectors[0]! }) + rerender() + + expect(result.current.length).toBe(1) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useConnections({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) diff --git a/packages/react/src/hooks/useConnections.ts b/packages/react/src/hooks/useConnections.ts new file mode 100644 index 0000000000..db53db7227 --- /dev/null +++ b/packages/react/src/hooks/useConnections.ts @@ -0,0 +1,28 @@ +'use client' + +import { + type GetConnectionsReturnType, + getConnections, + watchConnections, +} from '@wagmi/core' +import { useSyncExternalStore } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseConnectionsParameters = ConfigParameter + +export type UseConnectionsReturnType = GetConnectionsReturnType + +/** https://wagmi.sh/react/api/hooks/useConnections */ +export function useConnections( + parameters: UseConnectionsParameters = {}, +): UseConnectionsReturnType { + const config = useConfig(parameters) + + return useSyncExternalStore( + (onChange) => watchConnections(config, { onChange }), + () => getConnections(config), + () => getConnections(config), + ) +} diff --git a/packages/react/src/hooks/useConnectorClient.test-d.ts b/packages/react/src/hooks/useConnectorClient.test-d.ts new file mode 100644 index 0000000000..b919e6a904 --- /dev/null +++ b/packages/react/src/hooks/useConnectorClient.test-d.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useConnectorClient } from './useConnectorClient.js' + +test('parameters: config', async () => { + const client = useConnectorClient({ config }) + expectTypeOf(client.data?.chain?.id!).toEqualTypeOf<1 | 456 | 10>() + + const client2 = useConnectorClient({ config, chainId: 1 }) + expectTypeOf(client2.data?.chain?.id!).toEqualTypeOf<1>() +}) diff --git a/packages/react/src/hooks/useConnectorClient.test.tsx b/packages/react/src/hooks/useConnectorClient.test.tsx new file mode 100644 index 0000000000..f8737c53ec --- /dev/null +++ b/packages/react/src/hooks/useConnectorClient.test.tsx @@ -0,0 +1,239 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, wait } from '@wagmi/test' +import { render, renderHook, waitFor } from '@wagmi/test/react' +import * as React from 'react' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useConnect } from './useConnect.js' +import { useConnectorClient } from './useConnectorClient.js' +import { useDisconnect } from './useDisconnect.js' +import { useSwitchChain } from './useSwitchChain.js' + +const connector = config.connectors[0]! + +test('default', async () => { + const { result } = renderHook(() => useConnectorClient()) + + await waitFor(() => expect(result.current.isPending).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "connectorClient", + { + "chainId": 1, + "connectorUid": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) +}) + +test('behavior: connected on mount', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useConnectorClient()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, queryKey: _, ...rest } = result.current + expect(data).toMatchObject( + expect.objectContaining({ + account: expect.any(Object), + chain: expect.any(Object), + }), + ) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": true, + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: connect and disconnect', async () => { + const { result } = renderHook(() => ({ + useConnect: useConnect(), + useConnectorClient: useConnectorClient(), + useDisconnect: useDisconnect(), + })) + + expect(result.current.useConnectorClient.data).not.toBeDefined() + + result.current.useConnect.connect({ + connector: result.current.useConnect.connectors[0]!, + }) + + await waitFor(() => + expect(result.current.useConnectorClient.data).toBeDefined(), + ) + + result.current.useDisconnect.disconnect() + + await waitFor(() => + expect(result.current.useConnectorClient.data).not.toBeDefined(), + ) +}) + +test('behavior: switch chains', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => ({ + useConnectorClient: useConnectorClient(), + useSwitchChain: useSwitchChain(), + })) + + expect(result.current.useConnectorClient.data).not.toBeDefined() + + await waitFor(() => + expect(result.current.useConnectorClient.data).toBeDefined(), + ) + + result.current.useSwitchChain.switchChain({ chainId: 456 }) + await waitFor(() => { + expect(result.current.useSwitchChain.isSuccess).toBeTruthy() + result.current.useSwitchChain.reset() + }) + expect(result.current.useConnectorClient.data?.chain.id).toEqual(456) + + result.current.useSwitchChain.switchChain({ chainId: 1 }) + await waitFor(() => + expect(result.current.useSwitchChain.isSuccess).toBeTruthy(), + ) + expect(result.current.useConnectorClient.data?.chain.id).toEqual(1) + + await disconnect(config, { connector }) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useConnectorClient()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) + +test('behavior: disabled when connecting', async () => { + const { result } = renderHook(() => useConnectorClient()) + + config.setState((x) => ({ ...x, status: 'connecting' })) + + await wait(100) + expect(result.current.isLoading).not.toBeTruthy() +}) + +test('behavior: re-render does not invalidate query', async () => { + const { getByTestId } = render() + + getByTestId('connect').click() + await waitFor(() => { + expect(getByTestId('address').innerText).toContain( + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + ) + expect(getByTestId('client').innerText).toBeTruthy() + + expect(getByTestId('child-client').innerText).toBeTruthy() + expect(getByTestId('render-count').innerText).toEqual('1') + }) + + const initialClient = getByTestId('child-client').innerText + + getByTestId('rerender').click() + await waitFor(() => { + expect(getByTestId('render-count').innerText).toEqual('2') + }) + await wait(200) + + expect(getByTestId('child-client').innerText).toEqual(initialClient) +}) + +function Parent() { + const [renderCount, setRenderCount] = React.useState(1) + + const { connectors, connect } = useConnect() + const { address } = useAccount() + const { data } = useConnectorClient() + + return ( + <> +
{address}
+
{data?.uid}
+ + + + + + ) +} + +function Child(props: { + renderCount: number +}) { + const { renderCount } = props + const { data } = useConnectorClient() + return ( +
+ {data?.uid} + {renderCount} +
+ ) +} diff --git a/packages/react/src/hooks/useConnectorClient.ts b/packages/react/src/hooks/useConnectorClient.ts new file mode 100644 index 0000000000..16bbf33ae0 --- /dev/null +++ b/packages/react/src/hooks/useConnectorClient.ts @@ -0,0 +1,113 @@ +'use client' + +import { useQueryClient } from '@tanstack/react-query' +import type { + Config, + GetConnectorClientErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute, Omit } from '@wagmi/core/internal' +import { + type GetConnectorClientData, + type GetConnectorClientOptions, + type GetConnectorClientQueryFnData, + type GetConnectorClientQueryKey, + getConnectorClientQueryOptions, +} from '@wagmi/core/query' +import { useEffect, useRef } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { + type UseQueryParameters, + type UseQueryReturnType, + useQuery, +} from '../utils/query.js' +import { useAccount } from './useAccount.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseConnectorClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +> = Compute< + GetConnectorClientOptions & + ConfigParameter & { + query?: + | Compute< + Omit< + UseQueryParameters< + GetConnectorClientQueryFnData, + GetConnectorClientErrorType, + selectData, + GetConnectorClientQueryKey + >, + 'gcTime' | 'staleTime' + > + > + | undefined + } +> + +export type UseConnectorClientReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useConnectorClient */ +export function useConnectorClient< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +>( + parameters: UseConnectorClientParameters = {}, +): UseConnectorClientReturnType { + const { query = {}, ...rest } = parameters + + const config = useConfig(rest) + const queryClient = useQueryClient() + const { address, connector, status } = useAccount({ config }) + const chainId = useChainId({ config }) + const activeConnector = parameters.connector ?? connector + + const { queryKey, ...options } = getConnectorClientQueryOptions< + config, + chainId + >(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + connector: activeConnector, + }) + const enabled = Boolean( + (status === 'connected' || + (status === 'reconnecting' && activeConnector?.getProvider)) && + (query.enabled ?? true), + ) + + const addressRef = useRef(address) + // biome-ignore lint/correctness/useExhaustiveDependencies: `queryKey` not required + useEffect(() => { + const previousAddress = addressRef.current + if (!address && previousAddress) { + // remove when account is disconnected + queryClient.removeQueries({ queryKey }) + addressRef.current = undefined + } else if (address !== previousAddress) { + // invalidate when address changes + queryClient.invalidateQueries({ queryKey }) + addressRef.current = address + } + }, [address, queryClient]) + + return useQuery({ + ...query, + ...options, + queryKey, + enabled, + staleTime: Number.POSITIVE_INFINITY, + }) +} diff --git a/packages/react/src/hooks/useConnectors.test.ts b/packages/react/src/hooks/useConnectors.test.ts new file mode 100644 index 0000000000..f287cceb32 --- /dev/null +++ b/packages/react/src/hooks/useConnectors.test.ts @@ -0,0 +1,30 @@ +import { mock } from '@wagmi/connectors' +import { accounts, config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { useConnectors } from './useConnectors.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => useConnectors()) + + const count = config.connectors.length + expect(result.current.length).toBe(count) + expect(result.current).toEqual(config.connectors) + + config._internal.connectors.setState(() => [ + ...config.connectors, + config._internal.connectors.setup(mock({ accounts })), + ]) + rerender() + + expect(result.current.length).toBe(count + 1) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => useConnectors({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) diff --git a/packages/react/src/hooks/useConnectors.ts b/packages/react/src/hooks/useConnectors.ts new file mode 100644 index 0000000000..40a681689d --- /dev/null +++ b/packages/react/src/hooks/useConnectors.ts @@ -0,0 +1,34 @@ +'use client' + +import { + type Config, + type GetConnectorsReturnType, + type ResolvedRegister, + getConnectors, + watchConnectors, +} from '@wagmi/core' +import { useSyncExternalStore } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseConnectorsParameters = + ConfigParameter + +export type UseConnectorsReturnType = + GetConnectorsReturnType + +/** https://wagmi.sh/react/api/hooks/useConnectors */ +export function useConnectors< + config extends Config = ResolvedRegister['config'], +>( + parameters: UseConnectorsParameters = {}, +): UseConnectorsReturnType { + const config = useConfig(parameters) + + return useSyncExternalStore( + (onChange) => watchConnectors(config, { onChange }), + () => getConnectors(config), + () => getConnectors(config), + ) +} diff --git a/packages/react/src/hooks/useDeployContract.test-d.ts b/packages/react/src/hooks/useDeployContract.test-d.ts new file mode 100644 index 0000000000..93e53c472f --- /dev/null +++ b/packages/react/src/hooks/useDeployContract.test-d.ts @@ -0,0 +1,105 @@ +import type { DeployContractErrorType } from '@wagmi/core' +import { abi, bytecode } from '@wagmi/test' +import type { Abi, Hash } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useDeployContract } from './useDeployContract.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, deployContract, variables } = useDeployContract( + { + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + args?: readonly unknown[] | undefined + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + args?: readonly unknown[] | undefined + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + args?: readonly unknown[] | undefined + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + args?: readonly unknown[] | undefined + }>() + }, + }, + }, + ) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + deployContract( + { + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + chainId: 1, + }, + { + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.bayc + args: readonly [string, string, bigint, bigint] + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.bayc + args: readonly [string, string, bigint, bigint] + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.bayc + args: readonly [string, string, bigint, bigint] + }>() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useDeployContract.test.ts b/packages/react/src/hooks/useDeployContract.test.ts new file mode 100644 index 0000000000..561129bdbc --- /dev/null +++ b/packages/react/src/hooks/useDeployContract.test.ts @@ -0,0 +1,24 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, bytecode, config, transactionHashRegex } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useDeployContract } from './useDeployContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + const { result } = renderHook(() => useDeployContract()) + + result.current.deployContract({ + abi: abi.bayc, + bytecode: bytecode.bayc, + args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useDeployContract.ts b/packages/react/src/hooks/useDeployContract.ts new file mode 100644 index 0000000000..d37f3206d4 --- /dev/null +++ b/packages/react/src/hooks/useDeployContract.ts @@ -0,0 +1,78 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + DeployContractErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type DeployContractData, + type DeployContractMutate, + type DeployContractMutateAsync, + type DeployContractVariables, + deployContractMutationOptions, +} from '@wagmi/core/query' +import type { Abi } from 'viem' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseDeployContractParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + DeployContractData, + DeployContractErrorType, + DeployContractVariables, + context + > + | undefined + } +> + +export type UseDeployContractReturnType< + config extends Config = Config, + context = unknown, +> = UseMutationReturnType< + DeployContractData, + DeployContractErrorType, + DeployContractVariables, + context +> & { + deployContract: DeployContractMutate + deployContractAsync: DeployContractMutateAsync +} + +/** https://wagmi.sh/react/api/hooks/useDeployContract */ +export function useDeployContract< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseDeployContractParameters = {}, +): UseDeployContractReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = deployContractMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseDeployContractReturnType + return { + ...result, + deployContract: mutate as Return['deployContract'], + deployContractAsync: mutateAsync as Return['deployContractAsync'], + } +} diff --git a/packages/react/src/hooks/useDisconnect.test-d.ts b/packages/react/src/hooks/useDisconnect.test-d.ts new file mode 100644 index 0000000000..7bf3689450 --- /dev/null +++ b/packages/react/src/hooks/useDisconnect.test-d.ts @@ -0,0 +1,87 @@ +import type { Connector, DisconnectErrorType } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useDisconnect } from './useDisconnect.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('parameter', () => { + expectTypeOf(useDisconnect().disconnect) + .parameter(0) + .toEqualTypeOf<{ connector?: Connector | undefined } | undefined>() + expectTypeOf(useDisconnect().disconnect) + .parameter(0) + .toEqualTypeOf<{ connector?: Connector | undefined } | undefined>() +}) + +test('context', () => { + const { context, data, disconnect, error, variables } = useDisconnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + disconnect( + { connector }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useDisconnect.test.ts b/packages/react/src/hooks/useDisconnect.test.ts new file mode 100644 index 0000000000..26c7787d98 --- /dev/null +++ b/packages/react/src/hooks/useDisconnect.test.ts @@ -0,0 +1,32 @@ +import { connect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { beforeEach, expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useDisconnect } from './useDisconnect.js' + +const connector = config.connectors[0]! + +beforeEach(async () => { + await connect(config, { connector }) +}) + +test('default', async () => { + const { result } = renderHook(() => ({ + useAccount: useAccount(), + useDisconnect: useDisconnect(), + })) + + expect(result.current.useAccount.address).toBeDefined() + expect(result.current.useAccount.status).toEqual('connected') + + result.current.useDisconnect.disconnect() + + await waitFor(() => + expect(result.current.useAccount.isDisconnected).toBeTruthy(), + ) + + expect(result.current.useAccount.address).not.toBeDefined() + expect(result.current.useAccount.status).toEqual('disconnected') +}) diff --git a/packages/react/src/hooks/useDisconnect.ts b/packages/react/src/hooks/useDisconnect.ts new file mode 100644 index 0000000000..b5bff5bfe3 --- /dev/null +++ b/packages/react/src/hooks/useDisconnect.ts @@ -0,0 +1,70 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Connector, DisconnectErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type DisconnectData, + type DisconnectMutate, + type DisconnectMutateAsync, + type DisconnectVariables, + disconnectMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { useConnections } from './useConnections.js' + +export type UseDisconnectParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > + | undefined + } +> + +export type UseDisconnectReturnType = Compute< + UseMutationReturnType< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > & { + connectors: readonly Connector[] + disconnect: DisconnectMutate + disconnectAsync: DisconnectMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useDisconnect */ +export function useDisconnect( + parameters: UseDisconnectParameters = {}, +): UseDisconnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = disconnectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: useConnections({ config }).map( + (connection) => connection.connector, + ), + disconnect: mutate, + disconnectAsync: mutateAsync, + } +} diff --git a/packages/react/src/hooks/useEnsAddress.test.ts b/packages/react/src/hooks/useEnsAddress.test.ts new file mode 100644 index 0000000000..ff73c45f9d --- /dev/null +++ b/packages/react/src/hooks/useEnsAddress.test.ts @@ -0,0 +1,50 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEnsAddress } from './useEnsAddress.js' + +test('default', async () => { + const { result } = renderHook(() => + useEnsAddress({ + name: 'wevm.eth', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensAddress", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEnsAddress.ts b/packages/react/src/hooks/useEnsAddress.ts new file mode 100644 index 0000000000..999e07d999 --- /dev/null +++ b/packages/react/src/hooks/useEnsAddress.ts @@ -0,0 +1,58 @@ +'use client' + +import type { + Config, + GetEnsAddressErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsAddressData, + type GetEnsAddressOptions, + type GetEnsAddressQueryFnData, + type GetEnsAddressQueryKey, + getEnsAddressQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsAddressParameters< + config extends Config = Config, + selectData = GetEnsAddressData, +> = Compute< + GetEnsAddressOptions & + ConfigParameter & + QueryParameter< + GetEnsAddressQueryFnData, + GetEnsAddressErrorType, + selectData, + GetEnsAddressQueryKey + > +> + +export type UseEnsAddressReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEnsAddress */ +export function useEnsAddress< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsAddressData, +>( + parameters: UseEnsAddressParameters = {}, +): UseEnsAddressReturnType { + const { name, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getEnsAddressQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(name && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEnsAvatar.test.ts b/packages/react/src/hooks/useEnsAvatar.test.ts new file mode 100644 index 0000000000..d1f558be13 --- /dev/null +++ b/packages/react/src/hooks/useEnsAvatar.test.ts @@ -0,0 +1,50 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEnsAvatar } from './useEnsAvatar.js' + +test('default', async () => { + const { result } = renderHook(() => + useEnsAvatar({ + name: 'wevm.eth', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "https://euc.li/wevm.eth", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensAvatar", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEnsAvatar.ts b/packages/react/src/hooks/useEnsAvatar.ts new file mode 100644 index 0000000000..721653bf07 --- /dev/null +++ b/packages/react/src/hooks/useEnsAvatar.ts @@ -0,0 +1,58 @@ +'use client' + +import type { + Config, + GetEnsAvatarErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsAvatarData, + type GetEnsAvatarOptions, + type GetEnsAvatarQueryFnData, + type GetEnsAvatarQueryKey, + getEnsAvatarQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsAvatarParameters< + config extends Config = Config, + selectData = GetEnsAvatarData, +> = Compute< + GetEnsAvatarOptions & + ConfigParameter & + QueryParameter< + GetEnsAvatarQueryFnData, + GetEnsAvatarErrorType, + selectData, + GetEnsAvatarQueryKey + > +> + +export type UseEnsAvatarReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEnsAvatar */ +export function useEnsAvatar< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsAvatarData, +>( + parameters: UseEnsAvatarParameters = {}, +): UseEnsAvatarReturnType { + const { name, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getEnsAvatarQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(name && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEnsName.test.ts b/packages/react/src/hooks/useEnsName.test.ts new file mode 100644 index 0000000000..8a069b076d --- /dev/null +++ b/packages/react/src/hooks/useEnsName.test.ts @@ -0,0 +1,50 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEnsName } from './useEnsName.js' + +test('default', async () => { + const { result } = renderHook(() => + useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "wevm.eth", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensName", + { + "address": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEnsName.ts b/packages/react/src/hooks/useEnsName.ts new file mode 100644 index 0000000000..ddf88fed9d --- /dev/null +++ b/packages/react/src/hooks/useEnsName.ts @@ -0,0 +1,54 @@ +'use client' + +import type { Config, GetEnsNameErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsNameData, + type GetEnsNameOptions, + type GetEnsNameQueryFnData, + type GetEnsNameQueryKey, + getEnsNameQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsNameParameters< + config extends Config = Config, + selectData = GetEnsNameData, +> = Compute< + GetEnsNameOptions & + ConfigParameter & + QueryParameter< + GetEnsNameQueryFnData, + GetEnsNameErrorType, + selectData, + GetEnsNameQueryKey + > +> + +export type UseEnsNameReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEnsName */ +export function useEnsName< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsNameData, +>( + parameters: UseEnsNameParameters = {}, +): UseEnsNameReturnType { + const { address, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getEnsNameQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEnsResolver.test.ts b/packages/react/src/hooks/useEnsResolver.test.ts new file mode 100644 index 0000000000..f5b89d8f63 --- /dev/null +++ b/packages/react/src/hooks/useEnsResolver.test.ts @@ -0,0 +1,50 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEnsResolver } from './useEnsResolver.js' + +test('default', async () => { + const { result } = renderHook(() => + useEnsResolver({ + name: 'wevm.eth', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensResolver", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEnsResolver.ts b/packages/react/src/hooks/useEnsResolver.ts new file mode 100644 index 0000000000..e883debfa6 --- /dev/null +++ b/packages/react/src/hooks/useEnsResolver.ts @@ -0,0 +1,58 @@ +'use client' + +import type { + Config, + GetEnsResolverErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsResolverData, + type GetEnsResolverOptions, + type GetEnsResolverQueryFnData, + type GetEnsResolverQueryKey, + getEnsResolverQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsResolverParameters< + config extends Config = Config, + selectData = GetEnsResolverData, +> = Compute< + GetEnsResolverOptions & + ConfigParameter & + QueryParameter< + GetEnsResolverQueryFnData, + GetEnsResolverErrorType, + selectData, + GetEnsResolverQueryKey + > +> + +export type UseEnsResolverReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEnsResolver */ +export function useEnsResolver< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsResolverData, +>( + parameters: UseEnsResolverParameters = {}, +): UseEnsResolverReturnType { + const { name, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getEnsResolverQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(name && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEnsText.test.ts b/packages/react/src/hooks/useEnsText.test.ts new file mode 100644 index 0000000000..5e1c4b1d8c --- /dev/null +++ b/packages/react/src/hooks/useEnsText.test.ts @@ -0,0 +1,150 @@ +import { wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEnsText } from './useEnsText.js' + +test('default', async () => { + const { result } = renderHook(() => + useEnsText({ + key: 'com.twitter', + name: 'wevm.eth', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "wevm_dev", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensText", + { + "chainId": 1, + "key": "com.twitter", + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: name: undefined -> defined', async () => { + let name: string | undefined = undefined + + const { result, rerender } = renderHook(() => + useEnsText({ + key: 'com.twitter', + name, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "ensText", + { + "chainId": 1, + "key": "com.twitter", + "name": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + name = 'wevm.eth' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "wevm_dev", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensText", + { + "chainId": 1, + "key": "com.twitter", + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useEnsText()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useEnsText.ts b/packages/react/src/hooks/useEnsText.ts new file mode 100644 index 0000000000..2e65238c4f --- /dev/null +++ b/packages/react/src/hooks/useEnsText.ts @@ -0,0 +1,54 @@ +'use client' + +import type { Config, GetEnsTextErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsTextData, + type GetEnsTextOptions, + type GetEnsTextQueryFnData, + type GetEnsTextQueryKey, + getEnsTextQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsTextParameters< + config extends Config = Config, + selectData = GetEnsTextData, +> = Compute< + GetEnsTextOptions & + ConfigParameter & + QueryParameter< + GetEnsTextQueryFnData, + GetEnsTextErrorType, + selectData, + GetEnsTextQueryKey + > +> + +export type UseEnsTextReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEnsText */ +export function useEnsText< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsTextData, +>( + parameters: UseEnsTextParameters = {}, +): UseEnsTextReturnType { + const { key, name, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getEnsTextQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(key && name && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEstimateFeesPerGas.test-d.ts b/packages/react/src/hooks/useEstimateFeesPerGas.test-d.ts new file mode 100644 index 0000000000..679def0fd6 --- /dev/null +++ b/packages/react/src/hooks/useEstimateFeesPerGas.test-d.ts @@ -0,0 +1,49 @@ +import { expectTypeOf, test } from 'vitest' +import { useEstimateFeesPerGas } from './useEstimateFeesPerGas.js' + +test('types', () => { + const result = useEstimateFeesPerGas() + expectTypeOf(result.data).toMatchTypeOf< + | { + gasPrice?: undefined + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + formatted: { + gasPrice?: undefined + maxFeePerGas: string + maxPriorityFeePerGas: string + } + } + | undefined + >() + + const result2 = useEstimateFeesPerGas({ type: 'legacy' }) + expectTypeOf(result2.data).toMatchTypeOf< + | { + gasPrice: bigint + maxFeePerGas?: undefined + maxPriorityFeePerGas?: undefined + formatted: { + gasPrice: string + maxFeePerGas?: undefined + maxPriorityFeePerGas?: undefined + } + } + | undefined + >() + + const result3 = useEstimateFeesPerGas({ type: 'eip1559' }) + expectTypeOf(result3.data).toMatchTypeOf< + | { + gasPrice?: undefined + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + formatted: { + gasPrice?: undefined + maxFeePerGas: string + maxPriorityFeePerGas: string + } + } + | undefined + >() +}) diff --git a/packages/react/src/hooks/useEstimateFeesPerGas.test.ts b/packages/react/src/hooks/useEstimateFeesPerGas.test.ts new file mode 100644 index 0000000000..5d1859eda9 --- /dev/null +++ b/packages/react/src/hooks/useEstimateFeesPerGas.test.ts @@ -0,0 +1,19 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEstimateFeesPerGas } from './useEstimateFeesPerGas.js' + +test('default', async () => { + const { result } = renderHook(() => useEstimateFeesPerGas()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(Object.keys(result.current.data!)).toMatchInlineSnapshot(` + [ + "formatted", + "gasPrice", + "maxFeePerGas", + "maxPriorityFeePerGas", + ] + `) +}) diff --git a/packages/react/src/hooks/useEstimateFeesPerGas.ts b/packages/react/src/hooks/useEstimateFeesPerGas.ts new file mode 100644 index 0000000000..59dc2aa910 --- /dev/null +++ b/packages/react/src/hooks/useEstimateFeesPerGas.ts @@ -0,0 +1,62 @@ +'use client' + +import type { + Config, + EstimateFeesPerGasErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type EstimateFeesPerGasData, + type EstimateFeesPerGasOptions, + type EstimateFeesPerGasQueryFnData, + type EstimateFeesPerGasQueryKey, + estimateFeesPerGasQueryOptions, +} from '@wagmi/core/query' +import type { FeeValuesType } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEstimateFeesPerGasParameters< + type extends FeeValuesType = FeeValuesType, + config extends Config = Config, + selectData = EstimateFeesPerGasData, +> = Compute< + EstimateFeesPerGasOptions & + ConfigParameter & + QueryParameter< + EstimateFeesPerGasQueryFnData, + EstimateFeesPerGasErrorType, + selectData, + EstimateFeesPerGasQueryKey + > +> + +export type UseEstimateFeesPerGasReturnType< + type extends FeeValuesType = FeeValuesType, + selectData = EstimateFeesPerGasData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEstimateFeesPerGas */ +export function useEstimateFeesPerGas< + config extends Config = ResolvedRegister['config'], + type extends FeeValuesType = 'eip1559', + selectData = EstimateFeesPerGasData, +>( + parameters: UseEstimateFeesPerGasParameters = {}, +): UseEstimateFeesPerGasReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = estimateFeesPerGasQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useEstimateGas.test-d.ts b/packages/react/src/hooks/useEstimateGas.test-d.ts new file mode 100644 index 0000000000..d50618aad2 --- /dev/null +++ b/packages/react/src/hooks/useEstimateGas.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useEstimateGas } from './useEstimateGas.js' + +test('select data', () => { + const result = useEstimateGas({ + query: { + select(data) { + return data.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useEstimateGas.test.ts b/packages/react/src/hooks/useEstimateGas.test.ts new file mode 100644 index 0000000000..0c3ab08324 --- /dev/null +++ b/packages/react/src/hooks/useEstimateGas.test.ts @@ -0,0 +1,139 @@ +import { accounts } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { type Address, parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useEstimateGas } from './useEstimateGas.js' + +test('default', async () => { + const { result } = renderHook(() => + useEstimateGas({ + account: accounts[0], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 21000n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateGas", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "to": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "value": 10000000000000000n, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let account: Address | undefined = undefined + + const { result, rerender } = renderHook(() => useEstimateGas({ account })) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "estimateGas", + { + "account": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + account = accounts[0] + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 53001n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateGas", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEstimateGas.ts b/packages/react/src/hooks/useEstimateGas.ts new file mode 100644 index 0000000000..87c0e19e44 --- /dev/null +++ b/packages/react/src/hooks/useEstimateGas.ts @@ -0,0 +1,70 @@ +'use client' + +import type { + Config, + EstimateGasErrorType, + ResolvedRegister, +} from '@wagmi/core' +import { + type EstimateGasData, + type EstimateGasOptions, + type EstimateGasQueryFnData, + type EstimateGasQueryKey, + estimateGasQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { useConnectorClient } from './useConnectorClient.js' + +export type UseEstimateGasParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = EstimateGasData, +> = EstimateGasOptions & + ConfigParameter & + QueryParameter< + EstimateGasQueryFnData, + EstimateGasErrorType, + selectData, + EstimateGasQueryKey + > + +export type UseEstimateGasReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEstimateGas */ +export function useEstimateGas< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = EstimateGasData, +>( + parameters?: UseEstimateGasParameters, +): UseEstimateGasReturnType + +export function useEstimateGas( + parameters: UseEstimateGasParameters = {}, +): UseEstimateGasReturnType { + const { connector, query = {} } = parameters + + const config = useConfig(parameters) + const { data: connectorClient } = useConnectorClient({ + config, + connector, + query: { enabled: parameters.account === undefined }, + }) + const account = parameters.account ?? connectorClient?.account + const chainId = useChainId({ config }) + + const options = estimateGasQueryOptions(config, { + ...parameters, + account, + chainId: parameters.chainId ?? chainId, + connector, + }) + const enabled = Boolean((account || connector) && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test-d.ts b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test-d.ts new file mode 100644 index 0000000000..c4b1be1c8b --- /dev/null +++ b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test-d.ts @@ -0,0 +1,13 @@ +import { expectTypeOf, test } from 'vitest' +import { useEstimateMaxPriorityFeePerGas } from './useEstimateMaxPriorityFeePerGas.js' + +test('select data', () => { + const result = useEstimateMaxPriorityFeePerGas({ + query: { + select(data) { + return data.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test.ts b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test.ts new file mode 100644 index 0000000000..21c09188e3 --- /dev/null +++ b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.test.ts @@ -0,0 +1,96 @@ +import { chain, testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useEstimateMaxPriorityFeePerGas } from './useEstimateMaxPriorityFeePerGas.js' + +test('default', async () => { + await testClient.mainnet.restart() + + const { result } = renderHook(() => useEstimateMaxPriorityFeePerGas()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('bigint') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateMaxPriorityFeePerGas", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + await testClient.mainnet2.restart() + await testClient.mainnet2.mine({ blocks: 1 }) + + const { result } = renderHook(() => + useEstimateMaxPriorityFeePerGas({ chainId: chain.mainnet2.id }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('bigint') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateMaxPriorityFeePerGas", + { + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.ts b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.ts new file mode 100644 index 0000000000..0f884dba75 --- /dev/null +++ b/packages/react/src/hooks/useEstimateMaxPriorityFeePerGas.ts @@ -0,0 +1,61 @@ +'use client' + +import type { + Config, + EstimateMaxPriorityFeePerGasErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type EstimateMaxPriorityFeePerGasData, + type EstimateMaxPriorityFeePerGasOptions, + type EstimateMaxPriorityFeePerGasQueryFnData, + type EstimateMaxPriorityFeePerGasQueryKey, + estimateMaxPriorityFeePerGasQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEstimateMaxPriorityFeePerGasParameters< + config extends Config = Config, + selectData = EstimateMaxPriorityFeePerGasData, +> = Compute< + EstimateMaxPriorityFeePerGasOptions & + ConfigParameter & + QueryParameter< + EstimateMaxPriorityFeePerGasQueryFnData, + EstimateMaxPriorityFeePerGasErrorType, + selectData, + EstimateMaxPriorityFeePerGasQueryKey + > +> + +export type UseEstimateMaxPriorityFeePerGasReturnType< + selectData = EstimateMaxPriorityFeePerGasData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEstimateMaxPriorityFeePerGas */ +export function useEstimateMaxPriorityFeePerGas< + config extends Config = ResolvedRegister['config'], + selectData = EstimateMaxPriorityFeePerGasData, +>( + parameters: UseEstimateMaxPriorityFeePerGasParameters< + config, + selectData + > = {}, +): UseEstimateMaxPriorityFeePerGasReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = estimateMaxPriorityFeePerGasQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useFeeHistory.test-d.ts b/packages/react/src/hooks/useFeeHistory.test-d.ts new file mode 100644 index 0000000000..855fa893ae --- /dev/null +++ b/packages/react/src/hooks/useFeeHistory.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useFeeHistory } from './useFeeHistory.js' + +test('select data', () => { + const result = useFeeHistory({ + query: { + select(data) { + return data.gasUsedRatio + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useFeeHistory.test.ts b/packages/react/src/hooks/useFeeHistory.test.ts new file mode 100644 index 0000000000..53b6a8c55b --- /dev/null +++ b/packages/react/src/hooks/useFeeHistory.test.ts @@ -0,0 +1,452 @@ +import { chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useFeeHistory } from './useFeeHistory.js' + +test('default', async () => { + const { result } = renderHook(() => + useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75], + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 456, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75], + blockNumber: 18677379n, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "blockNumber": 18677379n, + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockTag', async () => { + const { result } = renderHook(() => + useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75], + blockTag: 'safe', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "blockTag": "safe", + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: blockCount: undefined -> defined', async () => { + let blockCount: number | undefined = undefined + + const { result, rerender } = renderHook(() => + useFeeHistory({ + blockCount, + rewardPercentiles: [25, 75], + }), + ) + + { + const { data, ...rest } = result.current + expect(data).toBeTypeOf('undefined') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "feeHistory", + { + "blockCount": undefined, + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + } + + blockCount = 4 + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: rewardPercentiles: undefined -> defined', async () => { + let rewardPercentiles: number[] | undefined = undefined + + const { result, rerender } = renderHook(() => + useFeeHistory({ + blockCount: 4, + rewardPercentiles, + }), + ) + + { + const { data, ...rest } = result.current + expect(data).toBeTypeOf('undefined') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 1, + "rewardPercentiles": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + } + + rewardPercentiles = [25, 75] + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toMatchObject({ + baseFeePerGas: expect.arrayContaining([expect.any(BigInt)]), + gasUsedRatio: expect.arrayContaining([expect.any(Number)]), + oldestBlock: expect.any(BigInt), + reward: expect.any(Array), + }) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "feeHistory", + { + "blockCount": 4, + "chainId": 1, + "rewardPercentiles": [ + 25, + 75, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useFeeHistory()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useFeeHistory.ts b/packages/react/src/hooks/useFeeHistory.ts new file mode 100644 index 0000000000..0754757b31 --- /dev/null +++ b/packages/react/src/hooks/useFeeHistory.ts @@ -0,0 +1,64 @@ +'use client' + +import type { + Config, + GetFeeHistoryErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetFeeHistoryData, + type GetFeeHistoryOptions, + type GetFeeHistoryQueryFnData, + type GetFeeHistoryQueryKey, + getFeeHistoryQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseFeeHistoryParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetFeeHistoryData, +> = Compute< + GetFeeHistoryOptions & + ConfigParameter & + QueryParameter< + GetFeeHistoryQueryFnData, + GetFeeHistoryErrorType, + selectData, + GetFeeHistoryQueryKey + > +> + +export type UseFeeHistoryReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useFeeHistory */ +export function useFeeHistory< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetFeeHistoryData, +>( + parameters: UseFeeHistoryParameters = {}, +): UseFeeHistoryReturnType { + const { blockCount, rewardPercentiles, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getFeeHistoryQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean( + blockCount && rewardPercentiles && (query.enabled ?? true), + ) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useGasPrice.test-d.ts b/packages/react/src/hooks/useGasPrice.test-d.ts new file mode 100644 index 0000000000..7108993a20 --- /dev/null +++ b/packages/react/src/hooks/useGasPrice.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useGasPrice } from './useGasPrice.js' + +test('select data', () => { + const result = useGasPrice({ + query: { + select(data) { + return data?.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useGasPrice.test.ts b/packages/react/src/hooks/useGasPrice.test.ts new file mode 100644 index 0000000000..5dd97cc96c --- /dev/null +++ b/packages/react/src/hooks/useGasPrice.test.ts @@ -0,0 +1,103 @@ +import { chain, testClient } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useGasPrice } from './useGasPrice.js' + +test('default', async () => { + await testClient.mainnet.restart() + + await testClient.mainnet.setNextBlockBaseFeePerGas({ + baseFeePerGas: 2_000_000_000n, + }) + await testClient.mainnet.mine({ blocks: 1 }) + + const { result } = renderHook(() => useGasPrice()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 2750000000n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "gasPrice", + { + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + await testClient.mainnet2.restart() + + await testClient.mainnet2.setNextBlockBaseFeePerGas({ + baseFeePerGas: 1_000_000_000n, + }) + await testClient.mainnet2.mine({ blocks: 1 }) + + const { result } = renderHook(() => + useGasPrice({ chainId: chain.mainnet2.id }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 1875000000n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "gasPrice", + { + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useGasPrice.ts b/packages/react/src/hooks/useGasPrice.ts new file mode 100644 index 0000000000..dc823dcf64 --- /dev/null +++ b/packages/react/src/hooks/useGasPrice.ts @@ -0,0 +1,62 @@ +'use client' + +import type { + Config, + GetGasPriceErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetGasPriceData, + type GetGasPriceOptions, + type GetGasPriceQueryFnData, + type GetGasPriceQueryKey, + getGasPriceQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseGasPriceParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetGasPriceData, +> = Compute< + GetGasPriceOptions & + ConfigParameter & + QueryParameter< + GetGasPriceQueryFnData, + GetGasPriceErrorType, + selectData, + GetGasPriceQueryKey + > +> + +export type UseGasPriceReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useGasPrice */ +export function useGasPrice< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetGasPriceData, +>( + parameters: UseGasPriceParameters = {}, +): UseGasPriceReturnType { + const { query = {} } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + const options = getGasPriceQueryOptions(config, { + ...parameters, + chainId, + }) + + return useQuery({ ...query, ...options }) +} diff --git a/packages/react/src/hooks/useInfiniteReadContracts.test-d.ts b/packages/react/src/hooks/useInfiniteReadContracts.test-d.ts new file mode 100644 index 0000000000..60b3a2cddd --- /dev/null +++ b/packages/react/src/hooks/useInfiniteReadContracts.test-d.ts @@ -0,0 +1,44 @@ +import { abi } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useInfiniteReadContracts } from './useInfiniteReadContracts.js' + +test('select data', () => { + const result = useInfiniteReadContracts({ + allowFailure: false, + cacheKey: 'foo', + contracts(pageParam) { + expectTypeOf(pageParam).toEqualTypeOf() + return [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ] + }, + query: { + initialPageParam: '0', + getNextPageParam(lastPage, allPages, lastPageParam, allPageParams) { + expectTypeOf(lastPage).toEqualTypeOf<[bigint, string]>() + expectTypeOf(allPages).toEqualTypeOf<[bigint, string][]>() + expectTypeOf(lastPageParam).toEqualTypeOf() + expectTypeOf(allPageParams).toEqualTypeOf() + return lastPageParam + 1 + }, + select(data) { + expectTypeOf(data.pageParams[0]!).toEqualTypeOf() + expectTypeOf(data.pages[0]!).toEqualTypeOf<[bigint, string]>() + return data.pages[0]?.[0]! + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useInfiniteReadContracts.test.ts b/packages/react/src/hooks/useInfiniteReadContracts.test.ts new file mode 100644 index 0000000000..0feb1e0e98 --- /dev/null +++ b/packages/react/src/hooks/useInfiniteReadContracts.test.ts @@ -0,0 +1,91 @@ +import { abi, address } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useInfiniteReadContracts } from './useInfiniteReadContracts.js' + +test( + 'default', + async () => { + const limit = 3 + + const { result } = renderHook(() => + useInfiniteReadContracts({ + cacheKey: 'foo', + contracts(pageParam) { + return [...new Array(limit)].map( + (_, i) => + ({ + address: address.shields, + abi: abi.shields, + functionName: 'tokenURI', + args: [BigInt(pageParam + i + 1)], + }) as const, + ) + }, + query: { + initialPageParam: 0, + getNextPageParam(_lastPage, _allPages, lastPageParam) { + return lastPageParam + limit + }, + select(data) { + const results = [] + for (const page of data.pages) { + for (const response of page) { + if (response.status === 'success') { + const decoded = atob( + response.result.replace(/(^.*base64,)/, ''), + ) + const json = JSON.parse(decoded) as { name: string } + results.push(json.name) + } else results.push('Error fetching shield') + } + } + return results + }, + }, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + expect(result.current.data).toMatchInlineSnapshot(` + [ + "Three Shields on Pink Perfect", + "Three Shields on Sky Perfect", + "Three Shields on Evergreen Perfect", + ] + `) + + await result.current.fetchNextPage() + + await waitFor(() => expect(result.current.hasNextPage).toBeTruthy()) + expect(result.current.data).toMatchInlineSnapshot(` + [ + "Three Shields on Pink Perfect", + "Three Shields on Sky Perfect", + "Three Shields on Evergreen Perfect", + "Three Shields on Hi-Vis Perfect", + "Three Shields on Gray Perfect", + "Everlasting: Tracery on Onyx and Pink Razor Bordure", + ] + `) + + await result.current.fetchNextPage() + + await waitFor(() => expect(result.current.hasNextPage).toBeTruthy()) + expect(result.current.data).toMatchInlineSnapshot(` + [ + "Three Shields on Pink Perfect", + "Three Shields on Sky Perfect", + "Three Shields on Evergreen Perfect", + "Three Shields on Hi-Vis Perfect", + "Three Shields on Gray Perfect", + "Everlasting: Tracery on Onyx and Pink Razor Bordure", + "The Book of Shields on Pink Perfect", + "Menacing: Necklace on Evergreen and Hi-Vis Chief Indented", + "Secured: Telescope and Stars on Ultraviolet and Sky Doppler", + ] + `) + }, + { timeout: 20_000 }, +) diff --git a/packages/react/src/hooks/useInfiniteReadContracts.ts b/packages/react/src/hooks/useInfiniteReadContracts.ts new file mode 100644 index 0000000000..2622f434a6 --- /dev/null +++ b/packages/react/src/hooks/useInfiniteReadContracts.ts @@ -0,0 +1,89 @@ +'use client' + +import type { + Config, + ReadContractsErrorType, + ResolvedRegister, +} from '@wagmi/core' +import { + type InfiniteReadContractsQueryFnData, + type InfiniteReadContractsQueryKey, + infiniteReadContractsQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import type { ContractFunctionParameters } from 'viem' + +import type { + InfiniteReadContractsData, + InfiniteReadContractsOptions, +} from '../exports/query.js' +import type { + ConfigParameter, + InfiniteQueryParameter, +} from '../types/properties.js' +import { + type UseInfiniteQueryParameters, + type UseInfiniteQueryReturnType, + useInfiniteQuery, +} from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseInfiniteContractReadsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + config extends Config = Config, + pageParam = unknown, + selectData = InfiniteReadContractsData, +> = InfiniteReadContractsOptions & + ConfigParameter & + InfiniteQueryParameter< + InfiniteReadContractsQueryFnData, + ReadContractsErrorType, + selectData, + InfiniteReadContractsData, + InfiniteReadContractsQueryKey, + pageParam + > + +export type UseInfiniteContractReadsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + selectData = InfiniteReadContractsData, +> = UseInfiniteQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useInfiniteReadContracts */ +export function useInfiniteReadContracts< + const contracts extends readonly unknown[], + allowFailure extends boolean = true, + config extends Config = ResolvedRegister['config'], + pageParam = unknown, + selectData = InfiniteReadContractsData, +>( + parameters: UseInfiniteContractReadsParameters< + contracts, + allowFailure, + config, + pageParam, + selectData + >, +): UseInfiniteContractReadsReturnType { + const { contracts = [], query } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = infiniteReadContractsQueryOptions(config, { + ...parameters, + chainId, + contracts: contracts as UseInfiniteContractReadsParameters['contracts'], + query: query as UseInfiniteQueryParameters, + }) + + return useInfiniteQuery({ + ...(query as any), + ...options, + initialPageParam: options.initialPageParam, + structuralSharing: query.structuralSharing ?? structuralSharing, + }) +} diff --git a/packages/react/src/hooks/usePrepareTransactionRequest.test-d.ts b/packages/react/src/hooks/usePrepareTransactionRequest.test-d.ts new file mode 100644 index 0000000000..9c39595ab4 --- /dev/null +++ b/packages/react/src/hooks/usePrepareTransactionRequest.test-d.ts @@ -0,0 +1,27 @@ +import { config } from '@wagmi/test' +import type { PrepareTransactionRequestReturnType } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { usePrepareTransactionRequest } from './usePrepareTransactionRequest.js' + +test('select data', () => { + const result = usePrepareTransactionRequest({ + query: { + select(data) { + return data + }, + }, + }) + + expectTypeOf(result.data).toMatchTypeOf< + PrepareTransactionRequestReturnType | undefined + >() +}) + +test('parameters: config', () => { + const result = usePrepareTransactionRequest({ + config, + chainId: 456, + }) + if (result.data) expectTypeOf(result.data.chainId).toEqualTypeOf<456>() +}) diff --git a/packages/react/src/hooks/usePrepareTransactionRequest.test.ts b/packages/react/src/hooks/usePrepareTransactionRequest.test.ts new file mode 100644 index 0000000000..7b90729dbf --- /dev/null +++ b/packages/react/src/hooks/usePrepareTransactionRequest.test.ts @@ -0,0 +1,81 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { usePrepareTransactionRequest } from './usePrepareTransactionRequest.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => + usePrepareTransactionRequest({ + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { + data: { + gas: _gas, + maxFeePerGas: _mfpg, + maxPriorityFeePerGas: _mpfpg, + nonce: _nonce, + ...data + } = {}, + ...rest + } = result.current + + expect(data).toMatchInlineSnapshot(` + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "type": "eip1559", + "value": 1000000000000000000n, + } + `) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "prepareTransactionRequest", + { + "chainId": 1, + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": 1000000000000000000n, + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/usePrepareTransactionRequest.ts b/packages/react/src/hooks/usePrepareTransactionRequest.ts new file mode 100644 index 0000000000..509d85eb08 --- /dev/null +++ b/packages/react/src/hooks/usePrepareTransactionRequest.ts @@ -0,0 +1,102 @@ +'use client' + +import type { + Config, + PrepareTransactionRequestErrorType, + ResolvedRegister, + SelectChains, +} from '@wagmi/core' +import { + type PrepareTransactionRequestData, + type PrepareTransactionRequestOptions, + type PrepareTransactionRequestQueryKey, + prepareTransactionRequestQueryOptions, +} from '@wagmi/core/query' +import type { PrepareTransactionRequestQueryFnData } from '@wagmi/core/query' +import type { PrepareTransactionRequestRequest as viem_PrepareTransactionRequestRequest } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UsePrepareTransactionRequestParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + > = viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, + selectData = PrepareTransactionRequestData, +> = PrepareTransactionRequestOptions & + ConfigParameter & + QueryParameter< + PrepareTransactionRequestQueryFnData, + PrepareTransactionRequestErrorType, + selectData, + PrepareTransactionRequestQueryKey + > + +export type UsePrepareTransactionRequestReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + > = viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, + selectData = PrepareTransactionRequestData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/usePrepareTransactionRequest */ +export function usePrepareTransactionRequest< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + request extends viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + > = viem_PrepareTransactionRequestRequest< + SelectChains[0], + SelectChains[0] + >, + selectData = PrepareTransactionRequestData, +>( + parameters: UsePrepareTransactionRequestParameters< + config, + chainId, + request, + selectData + > = {} as any, +): UsePrepareTransactionRequestReturnType< + config, + chainId, + request, + selectData +> { + const { to, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = prepareTransactionRequestQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + } as PrepareTransactionRequestOptions) + const enabled = Boolean(to && (query.enabled ?? true)) + + return useQuery({ + ...(query as any), + ...options, + enabled, + }) as UsePrepareTransactionRequestReturnType< + config, + chainId, + request, + selectData + > +} diff --git a/packages/react/src/hooks/useProof.test-d.ts b/packages/react/src/hooks/useProof.test-d.ts new file mode 100644 index 0000000000..55f8b15330 --- /dev/null +++ b/packages/react/src/hooks/useProof.test-d.ts @@ -0,0 +1,14 @@ +import type { GetProofReturnType } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { useProof } from './useProof.js' + +test('select data', () => { + const result = useProof({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useProof.test.ts b/packages/react/src/hooks/useProof.test.ts new file mode 100644 index 0000000000..3c3cb51945 --- /dev/null +++ b/packages/react/src/hooks/useProof.test.ts @@ -0,0 +1,163 @@ +import { chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import type { Address } from 'viem' +import { useProof } from './useProof.js' + +test('default', async () => { + const { result } = renderHook(() => + useProof({ + address: '0x4200000000000000000000000000000000000016', + chainId: chain.optimism.id, + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect({ ...result.current, data: null }).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "chainId": 10, + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let address: Address | undefined = undefined + + const { result, rerender } = renderHook(() => + useProof({ + address, + chainId: chain.optimism.id, + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "getProof", + { + "address": undefined, + "chainId": 10, + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + address = '0x4200000000000000000000000000000000000016' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect({ ...result.current, data: null }).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getProof", + { + "address": "0x4200000000000000000000000000000000000016", + "chainId": 10, + "storageKeys": [ + "0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99", + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useProof()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useProof.ts b/packages/react/src/hooks/useProof.ts new file mode 100644 index 0000000000..f473d509aa --- /dev/null +++ b/packages/react/src/hooks/useProof.ts @@ -0,0 +1,56 @@ +'use client' + +import type { Config, GetProofErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetProofData, + type GetProofOptions, + type GetProofQueryKey, + getProofQueryOptions, +} from '@wagmi/core/query' +import type { GetProofQueryFnData } from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseProofParameters< + config extends Config = Config, + selectData = GetProofData, +> = Compute< + GetProofOptions & + ConfigParameter & + QueryParameter< + GetProofQueryFnData, + GetProofErrorType, + selectData, + GetProofQueryKey + > +> + +export type UseProofReturnType = UseQueryReturnType< + selectData, + GetProofErrorType +> + +/** https://wagmi.sh/react/api/hooks/useProof */ +export function useProof< + config extends Config = ResolvedRegister['config'], + selectData = GetProofData, +>( + parameters: UseProofParameters = {}, +): UseProofReturnType { + const { address, storageKeys, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getProofQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && storageKeys && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/usePublicClient.test-d.ts b/packages/react/src/hooks/usePublicClient.test-d.ts new file mode 100644 index 0000000000..5d259e5c4e --- /dev/null +++ b/packages/react/src/hooks/usePublicClient.test-d.ts @@ -0,0 +1,40 @@ +import { chain, config } from '@wagmi/test' +import type { Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { usePublicClient } from './usePublicClient.js' + +test('default', () => { + const client = usePublicClient({ config }) + expectTypeOf(client.chain).toEqualTypeOf<(typeof config)['chains'][number]>() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = usePublicClient({ + config, + chainId: chain.mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.chain).not.toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + { + const client = usePublicClient({ chainId: 123456 }) + if (client) { + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf() + } else { + expectTypeOf(client).toEqualTypeOf() + } + } + + const client = usePublicClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/usePublicClient.test.ts b/packages/react/src/hooks/usePublicClient.test.ts new file mode 100644 index 0000000000..93602746bb --- /dev/null +++ b/packages/react/src/hooks/usePublicClient.test.ts @@ -0,0 +1,30 @@ +import { switchChain } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { Fragment, createElement } from 'react' +import { expect, test } from 'vitest' + +import { usePublicClient } from './usePublicClient.js' + +test('default', async () => { + const { result, rerender } = renderHook(() => usePublicClient()) + + expect(result.current?.chain.id).toEqual(1) + + await switchChain(config, { chainId: 456 }) + rerender() + + expect(result.current?.chain.id).toEqual(456) +}) + +test('parameters: config', () => { + const { result } = renderHook(() => usePublicClient({ config }), { + wrapper: ({ children }) => createElement(Fragment, { children }), + }) + expect(result.current).toBeDefined() +}) + +test('behavior: unconfigured chain', () => { + const { result } = renderHook(() => usePublicClient({ chainId: 123456 })) + expect(result.current).toBeUndefined() +}) diff --git a/packages/react/src/hooks/usePublicClient.ts b/packages/react/src/hooks/usePublicClient.ts new file mode 100644 index 0000000000..5193f170b6 --- /dev/null +++ b/packages/react/src/hooks/usePublicClient.ts @@ -0,0 +1,51 @@ +'use client' + +import { + type Config, + type GetPublicClientParameters, + type GetPublicClientReturnType, + type ResolvedRegister, + getPublicClient, + watchPublicClient, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UsePublicClientParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = Compute< + GetPublicClientParameters & ConfigParameter +> + +export type UsePublicClientReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = GetPublicClientReturnType + +/** https://wagmi.sh/react/api/hooks/usePublicClient */ +export function usePublicClient< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +>( + parameters: UsePublicClientParameters = {}, +): UsePublicClientReturnType { + const config = useConfig(parameters) + + return useSyncExternalStoreWithSelector( + (onChange) => watchPublicClient(config, { onChange }), + () => getPublicClient(config, parameters), + () => getPublicClient(config, parameters), + (x) => x, + (a, b) => a?.uid === b?.uid, + ) as any +} diff --git a/packages/react/src/hooks/useReadContract.test-d.ts b/packages/react/src/hooks/useReadContract.test-d.ts new file mode 100644 index 0000000000..3b57d49a9e --- /dev/null +++ b/packages/react/src/hooks/useReadContract.test-d.ts @@ -0,0 +1,96 @@ +import { abi } from '@wagmi/test' +import type { Address } from 'viem' +import { assertType, expectTypeOf, test } from 'vitest' + +import { + type UseReadContractParameters, + type UseReadContractReturnType, + useReadContract, +} from './useReadContract.js' + +test('select data', () => { + const result = useReadContract({ + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('UseReadContractParameters', () => { + type Result = UseReadContractParameters + expectTypeOf>().toEqualTypeOf<{ + functionName?: + | 'symbol' + | 'name' + | 'allowance' + | 'balanceOf' + | 'decimals' + | 'totalSupply' + | undefined + args?: readonly [Address] | undefined + }>() +}) + +test('UseReadContractReturnType', () => { + type Result = UseReadContractReturnType + expectTypeOf().toEqualTypeOf() +}) + +test('overloads', () => { + const result1 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }) + assertType(result1.data) + + const result2 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: [], + }) + assertType(result2.data) + + const result3 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data) + + const result4 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data) +}) + +test('deployless read (bytecode)', () => { + const result = useReadContract({ + code: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useReadContract.test.ts b/packages/react/src/hooks/useReadContract.test.ts new file mode 100644 index 0000000000..c94ca996ce --- /dev/null +++ b/packages/react/src/hooks/useReadContract.test.ts @@ -0,0 +1,194 @@ +import { QueryClientProvider } from '@tanstack/react-query' +import { abi, address, bytecode, chain, config, wait } from '@wagmi/test' +import { queryClient, renderHook, waitFor } from '@wagmi/test/react' +import { createElement } from 'react' +import { expect, test } from 'vitest' + +import { useReadContract } from './useReadContract.js' + +test('default', async () => { + const { result } = renderHook(() => + useReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 456, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: config', async () => { + const { result } = renderHook( + () => + useReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + config, + }), + { + wrapper: ({ children }) => + createElement(QueryClientProvider, { client: queryClient }, children), + }, + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": 4n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: deployless read (bytecode)', async () => { + const { result } = renderHook(() => + useReadContract({ + abi: abi.wagmiMintExample, + functionName: 'name', + code: bytecode.wagmiMintExample, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchInlineSnapshot(`"wagmi"`) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useReadContract()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useReadContract.ts b/packages/react/src/hooks/useReadContract.ts new file mode 100644 index 0000000000..6eb6d6d64f --- /dev/null +++ b/packages/react/src/hooks/useReadContract.ts @@ -0,0 +1,99 @@ +'use client' + +import type { + Config, + ReadContractErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { UnionCompute } from '@wagmi/core/internal' +import { + type ReadContractData, + type ReadContractOptions, + type ReadContractQueryFnData, + type ReadContractQueryKey, + readContractQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import type { Abi, ContractFunctionArgs, ContractFunctionName, Hex } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseReadContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + selectData = ReadContractData, +> = UnionCompute< + ReadContractOptions & + ConfigParameter & + QueryParameter< + ReadContractQueryFnData, + ReadContractErrorType, + selectData, + ReadContractQueryKey + > +> + +export type UseReadContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + selectData = ReadContractData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useReadContract */ +export function useReadContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractData, +>( + parameters: UseReadContractParameters< + abi, + functionName, + args, + config, + selectData + > = {} as any, +): UseReadContractReturnType { + const { abi, address, functionName, query = {} } = parameters + // @ts-ignore + const code = parameters.code as Hex | undefined + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = readContractQueryOptions( + config, + { ...(parameters as any), chainId: parameters.chainId ?? chainId }, + ) + const enabled = Boolean( + (address || code) && abi && functionName && (query.enabled ?? true), + ) + + return useQuery({ + ...query, + ...options, + enabled, + structuralSharing: query.structuralSharing ?? structuralSharing, + }) +} diff --git a/packages/react/src/hooks/useReadContracts.test-d.ts b/packages/react/src/hooks/useReadContracts.test-d.ts new file mode 100644 index 0000000000..8aa786436a --- /dev/null +++ b/packages/react/src/hooks/useReadContracts.test-d.ts @@ -0,0 +1,93 @@ +import { abi } from '@wagmi/test' +import { assertType, expectTypeOf, test } from 'vitest' + +import { useReadContracts } from './useReadContracts.js' + +test('select data', () => { + const result = useReadContracts({ + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + chainId: 1, + args: ['0x'], + }, + { + address: '0x', + abi: abi.wagmiMintExample, + functionName: 'tokenURI', + args: [123n], + }, + ], + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf<[bigint, string]>() + return data[0] + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('overloads', async () => { + const result1 = useReadContracts({ + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }, + ], + }) + assertType<[number] | undefined>(result1.data) + + const result2 = useReadContracts({ + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: [], + }, + ], + }) + assertType<[number] | undefined>(result2.data) + + const result3 = useReadContracts({ + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }, + ], + }) + assertType<[string] | undefined>(result3.data) + + const result4 = useReadContracts({ + allowFailure: false, + contracts: [ + { + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }, + ], + }) + assertType< + | [ + { + foo: `0x${string}` + bar: `0x${string}` + }, + ] + | undefined + >(result4.data) +}) diff --git a/packages/react/src/hooks/useReadContracts.test.ts b/packages/react/src/hooks/useReadContracts.test.ts new file mode 100644 index 0000000000..e7636b254e --- /dev/null +++ b/packages/react/src/hooks/useReadContracts.test.ts @@ -0,0 +1,262 @@ +import { abi, address, chain } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useReadContracts } from './useReadContracts.js' + +test('default', async () => { + const { result } = renderHook(() => + useReadContracts({ + contracts: [ + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'symbol', + }, + ], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": [ + { + "result": 4n, + "status": "success", + }, + { + "result": "WAGMI", + "status": "success", + }, + ], + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContracts", + { + "chainId": 1, + "contracts": [ + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "functionName": "symbol", + }, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test.skip('multichain', async () => { + const { mainnet, mainnet2, optimism } = chain + const { result } = renderHook(() => + useReadContracts({ + contracts: [ + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet.id, + functionName: 'love', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], + }, + { + abi: abi.wagmigotchi, + address: address.wagmigotchi, + chainId: mainnet2.id, + functionName: 'getAlive', + }, + { + abi: abi.mloot, + address: address.mloot, + chainId: mainnet2.id, + functionName: 'tokenOfOwnerByIndex', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 0n], + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'symbol', + }, + { + abi: abi.erc20, + address: address.optimism.usdc, + chainId: optimism.id, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }, + ], + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": [ + { + "result": 2n, + "status": "success", + }, + { + "result": 1n, + "status": "success", + }, + { + "result": 0n, + "status": "success", + }, + { + "result": false, + "status": "success", + }, + { + "result": 370395n, + "status": "success", + }, + { + "result": "USDC", + "status": "success", + }, + { + "result": 10959340n, + "status": "success", + }, + ], + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "readContracts", + { + "chainId": 1, + "contracts": [ + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0x27a69ffba1e939ddcfecc8c7e0f967b872bac65c", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "args": [ + "0xd2135CfB216b74109775236E36d4b433F1DF507B", + ], + "chainId": 1, + "functionName": "love", + }, + { + "address": "0xecb504d39723b0be0e3a9aa33d646642d1051ee1", + "chainId": 456, + "functionName": "getAlive", + }, + { + "address": "0x1dfe7ca09e99d10835bf73044a23b73fc20623df", + "args": [ + "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e", + 0n, + ], + "chainId": 456, + "functionName": "tokenOfOwnerByIndex", + }, + { + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "chainId": 10, + "functionName": "symbol", + }, + { + "address": "0x7f5c764cbc14f9669b88837ca1490cca17c31607", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 10, + "functionName": "balanceOf", + }, + ], + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useReadContracts.ts b/packages/react/src/hooks/useReadContracts.ts new file mode 100644 index 0000000000..7be786c4a8 --- /dev/null +++ b/packages/react/src/hooks/useReadContracts.ts @@ -0,0 +1,91 @@ +'use client' + +import type { + Config, + ReadContractsErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ReadContractsData, + type ReadContractsOptions, + type ReadContractsQueryFnData, + type ReadContractsQueryKey, + readContractsQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import { useMemo } from 'react' +import type { ContractFunctionParameters } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseReadContractsParameters< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + config extends Config = Config, + selectData = ReadContractsData, +> = Compute< + ReadContractsOptions & + ConfigParameter & + QueryParameter< + ReadContractsQueryFnData, + ReadContractsErrorType, + selectData, + ReadContractsQueryKey + > +> + +export type UseReadContractsReturnType< + contracts extends readonly unknown[] = readonly ContractFunctionParameters[], + allowFailure extends boolean = true, + selectData = ReadContractsData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useReadContracts */ +export function useReadContracts< + const contracts extends readonly unknown[], + allowFailure extends boolean = true, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractsData, +>( + parameters: UseReadContractsParameters< + contracts, + allowFailure, + config, + selectData + > = {}, +): UseReadContractsReturnType { + const { contracts = [], query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = readContractsQueryOptions( + config, + { ...parameters, chainId }, + ) + + const enabled = useMemo(() => { + let isContractsValid = false + for (const contract of contracts) { + const { abi, address, functionName } = + contract as ContractFunctionParameters + if (!abi || !address || !functionName) { + isContractsValid = false + break + } + isContractsValid = true + } + return Boolean(isContractsValid && (query.enabled ?? true)) + }, [contracts, query.enabled]) + + return useQuery({ + ...options, + ...query, + enabled, + structuralSharing: query.structuralSharing ?? structuralSharing, + }) +} diff --git a/packages/react/src/hooks/useReconnect.test-d.ts b/packages/react/src/hooks/useReconnect.test-d.ts new file mode 100644 index 0000000000..424159e469 --- /dev/null +++ b/packages/react/src/hooks/useReconnect.test-d.ts @@ -0,0 +1,154 @@ +import type { + Connector, + CreateConnectorFn, + ReconnectErrorType, +} from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Address } from 'viem' +import { useReconnect } from './useReconnect.js' + +const connectors = [config.connectors[0]!] +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, reconnect, variables } = useReconnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(data).toEqualTypeOf< + { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + >() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: readonly (CreateConnectorFn | Connector)[] | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + reconnect( + { connectors }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(data).toEqualTypeOf< + { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + >() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useReconnect.test.ts b/packages/react/src/hooks/useReconnect.test.ts new file mode 100644 index 0000000000..be783b2440 --- /dev/null +++ b/packages/react/src/hooks/useReconnect.test.ts @@ -0,0 +1,83 @@ +import { mock } from '@wagmi/connectors' +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { afterEach, expect, test } from 'vitest' + +import { useReconnect } from './useReconnect.js' + +const connector = config._internal.connectors.setup( + mock({ + accounts, + features: { reconnect: true }, + }), +) + +afterEach(async () => { + if (config.state.current) await disconnect(config) +}) + +test('default', async () => { + const { result } = renderHook(() => useReconnect()) + + expect(result.current.connectors).toBeDefined() + + result.current.reconnect() + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toStrictEqual([]) +}) + +test('parameters: connectors (Connector)', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useReconnect()) + + expect(result.current.connectors).toBeDefined() + + result.current.reconnect({ connectors: [connector] }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) +}) + +test('parameters: connectors (CreateConnectorFn)', async () => { + const connector = mock({ + accounts, + features: { reconnect: true }, + }) + await connect(config, { connector }) + + const { result } = renderHook(() => useReconnect()) + + expect(result.current.connectors).toBeDefined() + + result.current.reconnect({ connectors: [connector] }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) +}) + +test("behavior: doesn't reconnect if already reconnecting", async () => { + const previousStatus = config.state.status + config.setState((x) => ({ ...x, status: 'reconnecting' })) + const { result } = renderHook(() => useReconnect()) + await expect( + result.current.reconnectAsync({ connectors: [connector] }), + ).resolves.toStrictEqual([]) + config.setState((x) => ({ ...x, status: previousStatus })) +}) diff --git a/packages/react/src/hooks/useReconnect.ts b/packages/react/src/hooks/useReconnect.ts new file mode 100644 index 0000000000..23e6d365e4 --- /dev/null +++ b/packages/react/src/hooks/useReconnect.ts @@ -0,0 +1,67 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Connector, ReconnectErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ReconnectData, + type ReconnectMutate, + type ReconnectMutateAsync, + type ReconnectVariables, + reconnectMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseReconnectParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context + > + | undefined + } +> + +export type UseReconnectReturnType = Compute< + UseMutationReturnType< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context + > & { + connectors: readonly Connector[] + reconnect: ReconnectMutate + reconnectAsync: ReconnectMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useReconnect */ +export function useReconnect( + parameters: UseReconnectParameters = {}, +): UseReconnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = reconnectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: config.connectors, + reconnect: mutate, + reconnectAsync: mutateAsync, + } +} diff --git a/packages/react/src/hooks/useSendCalls.test.ts b/packages/react/src/hooks/useSendCalls.test.ts new file mode 100644 index 0000000000..a088dcf221 --- /dev/null +++ b/packages/react/src/hooks/useSendCalls.test.ts @@ -0,0 +1,44 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useSendCalls } from './useSendCalls.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSendCalls()) + + result.current.sendCalls({ + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatchInlineSnapshot( + ` + { + "id": "0x5dedb5a4ff8968db37459b52b83cbdc92b01c9c709c9cff26e345ef5cf27f92e", + } + `, + ) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useSendCalls.ts b/packages/react/src/hooks/useSendCalls.ts new file mode 100644 index 0000000000..49366cb218 --- /dev/null +++ b/packages/react/src/hooks/useSendCalls.ts @@ -0,0 +1,75 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { Config, ResolvedRegister, SendCallsErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SendCallsData, + type SendCallsMutate, + type SendCallsMutateAsync, + type SendCallsVariables, + sendCallsMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSendCallsParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SendCallsData, + SendCallsErrorType, + SendCallsVariables, + context + > + | undefined + } +> + +export type UseSendCallsReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SendCallsData, + SendCallsErrorType, + SendCallsVariables, + context + > & { + sendCalls: SendCallsMutate + sendCallsAsync: SendCallsMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSendCalls */ +export function useSendCalls< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSendCallsParameters = {}, +): UseSendCallsReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = sendCallsMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSendCallsReturnType + return { + ...result, + sendCalls: mutate as Return['sendCalls'], + sendCallsAsync: mutateAsync as Return['sendCallsAsync'], + } +} diff --git a/packages/react/src/hooks/useSendTransaction.test-d.ts b/packages/react/src/hooks/useSendTransaction.test-d.ts new file mode 100644 index 0000000000..170c1e61e3 --- /dev/null +++ b/packages/react/src/hooks/useSendTransaction.test-d.ts @@ -0,0 +1,78 @@ +import type { SendTransactionErrorType } from '@wagmi/core' +import type { Hash } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useSendTransaction } from './useSendTransaction.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, sendTransaction, variables } = + useSendTransaction({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + sendTransaction( + { to: '0x' }, + { + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useSendTransaction.test.ts b/packages/react/src/hooks/useSendTransaction.test.ts new file mode 100644 index 0000000000..a2e8977e2f --- /dev/null +++ b/packages/react/src/hooks/useSendTransaction.test.ts @@ -0,0 +1,25 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, transactionHashRegex } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useSendTransaction } from './useSendTransaction.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSendTransaction()) + + result.current.sendTransaction({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useSendTransaction.ts b/packages/react/src/hooks/useSendTransaction.ts new file mode 100644 index 0000000000..8f57b1509c --- /dev/null +++ b/packages/react/src/hooks/useSendTransaction.ts @@ -0,0 +1,79 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + ResolvedRegister, + SendTransactionErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SendTransactionData, + type SendTransactionMutate, + type SendTransactionMutateAsync, + type SendTransactionVariables, + sendTransactionMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSendTransactionParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > + | undefined + } +> + +export type UseSendTransactionReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > & { + sendTransaction: SendTransactionMutate + sendTransactionAsync: SendTransactionMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSendTransaction */ +export function useSendTransaction< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSendTransactionParameters = {}, +): UseSendTransactionReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = sendTransactionMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSendTransactionReturnType + return { + ...result, + sendTransaction: mutate as Return['sendTransaction'], + sendTransactionAsync: mutateAsync as Return['sendTransactionAsync'], + } +} diff --git a/packages/react/src/hooks/useShowCallsStatus.ts b/packages/react/src/hooks/useShowCallsStatus.ts new file mode 100644 index 0000000000..82a4dd3909 --- /dev/null +++ b/packages/react/src/hooks/useShowCallsStatus.ts @@ -0,0 +1,76 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + ResolvedRegister, + ShowCallsStatusErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ShowCallsStatusData, + type ShowCallsStatusMutate, + type ShowCallsStatusMutateAsync, + type ShowCallsStatusVariables, + showCallsStatusMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseShowCallsStatusParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables, + context + > + | undefined + } +> + +export type UseShowCallsStatusReturnType = Compute< + UseMutationReturnType< + ShowCallsStatusData, + ShowCallsStatusErrorType, + ShowCallsStatusVariables, + context + > & { + showCallsStatus: ShowCallsStatusMutate + showCallsStatusAsync: ShowCallsStatusMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useShowCallsStatus */ +export function useShowCallsStatus< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseShowCallsStatusParameters = {}, +): UseShowCallsStatusReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = showCallsStatusMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseShowCallsStatusReturnType + return { + ...result, + showCallsStatus: mutate as Return['showCallsStatus'], + showCallsStatusAsync: mutateAsync as Return['showCallsStatusAsync'], + } +} diff --git a/packages/react/src/hooks/useSignMessage.test-d.ts b/packages/react/src/hooks/useSignMessage.test-d.ts new file mode 100644 index 0000000000..706c4a51fe --- /dev/null +++ b/packages/react/src/hooks/useSignMessage.test-d.ts @@ -0,0 +1,62 @@ +import type { SignMessageErrorType } from '@wagmi/core' +import type { SignMessageVariables } from '@wagmi/core/query' +import { expectTypeOf, test } from 'vitest' + +import { useSignMessage } from './useSignMessage.js' + +const message = 'hello world' +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, signMessage, variables } = useSignMessage({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf<`0x${string}`>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + signMessage( + { message }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf<`0x${string}`>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useSignMessage.test.ts b/packages/react/src/hooks/useSignMessage.test.ts new file mode 100644 index 0000000000..aa6dd4a184 --- /dev/null +++ b/packages/react/src/hooks/useSignMessage.test.ts @@ -0,0 +1,43 @@ +import { connect, disconnect, getAccount } from '@wagmi/core' +import { config, privateKey } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { recoverMessageAddress } from 'viem' +import { expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { useSignMessage } from './useSignMessage.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSignMessage()) + + result.current.signMessage({ message: 'foo bar baz' }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: result.current.data!, + }), + ).resolves.toEqual(getAccount(config).address) + + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const { result } = renderHook(() => useSignMessage()) + + const account = privateKeyToAccount(privateKey) + result.current.signMessage({ account, message: 'foo bar baz' }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: result.current.data!, + }), + ).resolves.toEqual(account.address) +}) diff --git a/packages/react/src/hooks/useSignMessage.ts b/packages/react/src/hooks/useSignMessage.ts new file mode 100644 index 0000000000..0bea4bb65f --- /dev/null +++ b/packages/react/src/hooks/useSignMessage.ts @@ -0,0 +1,65 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { SignMessageErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SignMessageData, + type SignMessageMutate, + type SignMessageMutateAsync, + type SignMessageVariables, + signMessageMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSignMessageParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > + | undefined + } +> + +export type UseSignMessageReturnType = Compute< + UseMutationReturnType< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > & { + signMessage: SignMessageMutate + signMessageAsync: SignMessageMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSignMessage */ +export function useSignMessage( + parameters: UseSignMessageParameters = {}, +): UseSignMessageReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = signMessageMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + signMessage: mutate, + signMessageAsync: mutateAsync, + } +} diff --git a/packages/react/src/hooks/useSignTypedData.test-d.ts b/packages/react/src/hooks/useSignTypedData.test-d.ts new file mode 100644 index 0000000000..a51f77cfbb --- /dev/null +++ b/packages/react/src/hooks/useSignTypedData.test-d.ts @@ -0,0 +1,93 @@ +import type { + SignTypedDataErrorType, + SignTypedDataReturnType, +} from '@wagmi/core' +import type { SignTypedDataVariables } from '@wagmi/core/query' +import { typedData } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useSignTypedData } from './useSignTypedData.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, signTypedData, variables } = useSignTypedData({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(context).toEqualTypeOf() + + signTypedData( + { + types: typedData.basic.types, + primaryType: 'Person', + message: { + name: 'Bob', + wallet: '0x', + }, + }, + { + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useSignTypedData.test.ts b/packages/react/src/hooks/useSignTypedData.test.ts new file mode 100644 index 0000000000..3a38daa0da --- /dev/null +++ b/packages/react/src/hooks/useSignTypedData.test.ts @@ -0,0 +1,56 @@ +import { connect, disconnect, getAccount } from '@wagmi/core' +import { config, privateKey, typedData } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { recoverTypedDataAddress } from 'viem' +import { expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { useSignTypedData } from './useSignTypedData.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useSignTypedData()) + + result.current.signTypedData({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature: result.current.data!, + }), + ).resolves.toEqual(getAccount(config).address) + + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const { result } = renderHook(() => useSignTypedData()) + + const account = privateKeyToAccount(privateKey) + result.current.signTypedData({ + account, + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature: result.current.data!, + }), + ).resolves.toEqual(account.address) +}) diff --git a/packages/react/src/hooks/useSignTypedData.ts b/packages/react/src/hooks/useSignTypedData.ts new file mode 100644 index 0000000000..080cd04405 --- /dev/null +++ b/packages/react/src/hooks/useSignTypedData.ts @@ -0,0 +1,66 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { SignTypedDataErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SignTypedDataData, + type SignTypedDataMutate, + type SignTypedDataMutateAsync, + type SignTypedDataVariables, + signTypedDataMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSignTypedDataParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables, + context + > + | undefined + } +> + +export type UseSignTypedDataReturnType = Compute< + UseMutationReturnType< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables, + context + > & { + signTypedData: SignTypedDataMutate + signTypedDataAsync: SignTypedDataMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSignTypedData */ +export function useSignTypedData( + parameters: UseSignTypedDataParameters = {}, +): UseSignTypedDataReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = signTypedDataMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSignTypedDataReturnType + return { + ...result, + signTypedData: mutate as Return['signTypedData'], + signTypedDataAsync: mutateAsync as Return['signTypedDataAsync'], + } +} diff --git a/packages/react/src/hooks/useSimulateContract.test-d.ts b/packages/react/src/hooks/useSimulateContract.test-d.ts new file mode 100644 index 0000000000..8159cdfccf --- /dev/null +++ b/packages/react/src/hooks/useSimulateContract.test-d.ts @@ -0,0 +1,104 @@ +import { abi, type config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { + type UseSimulateContractParameters, + type UseSimulateContractReturnType, + useSimulateContract, +} from './useSimulateContract.js' + +test('default', () => { + const result = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + }) + + expectTypeOf(result.data).toMatchTypeOf< + | { + result: boolean + request: { + chainId?: undefined + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) + +test('select data', () => { + const result = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + query: { + select(data) { + expectTypeOf(data.result).toEqualTypeOf() + return data.request.args + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf< + readonly [Address, Address, bigint] | undefined + >() +}) + +test('UseSimulateContractParameters', () => { + type Result = UseSimulateContractParameters + expectTypeOf().toMatchTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + }>() +}) + +test('UseSimulateContractReturnType', () => { + type Result = UseSimulateContractReturnType< + typeof abi.erc20, + 'transferFrom', + ['0x', '0x', 123n], + typeof config, + 1 + > + expectTypeOf().toMatchTypeOf< + | { + result: boolean + request: { + chainId: number + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) diff --git a/packages/react/src/hooks/useSimulateContract.test.ts b/packages/react/src/hooks/useSimulateContract.test.ts new file mode 100644 index 0000000000..3c785133c9 --- /dev/null +++ b/packages/react/src/hooks/useSimulateContract.test.ts @@ -0,0 +1,95 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useSimulateContract } from './useSimulateContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => + useSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 1, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "simulateContract", + { + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "functionName": "mint", + }, + ], + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useSimulateContract()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useSimulateContract.ts b/packages/react/src/hooks/useSimulateContract.ts new file mode 100644 index 0000000000..e17913fb4f --- /dev/null +++ b/packages/react/src/hooks/useSimulateContract.ts @@ -0,0 +1,117 @@ +'use client' + +import type { + Config, + ResolvedRegister, + SimulateContractErrorType, +} from '@wagmi/core' +import { + type SimulateContractData, + type SimulateContractOptions, + type SimulateContractQueryFnData, + type SimulateContractQueryKey, + simulateContractQueryOptions, +} from '@wagmi/core/query' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { useConnectorClient } from './useConnectorClient.js' + +export type UseSimulateContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +> = SimulateContractOptions & + ConfigParameter & + QueryParameter< + SimulateContractQueryFnData, + SimulateContractErrorType, + selectData, + SimulateContractQueryKey + > + +export type UseSimulateContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useSimulateContract */ +export function useSimulateContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +>( + parameters: UseSimulateContractParameters< + abi, + functionName, + args, + config, + chainId, + selectData + > = {} as any, +): UseSimulateContractReturnType< + abi, + functionName, + args, + config, + chainId, + selectData +> { + const { abi, address, connector, functionName, query = {} } = parameters + + const config = useConfig(parameters) + const { data: connectorClient } = useConnectorClient({ + config, + connector, + query: { enabled: parameters.account === undefined }, + }) + const chainId = useChainId({ config }) + + const options = simulateContractQueryOptions< + config, + abi, + functionName, + args, + chainId + >(config, { + ...parameters, + account: parameters.account ?? connectorClient?.account, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean( + abi && address && functionName && (query.enabled ?? true), + ) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useStorageAt.test-d.ts b/packages/react/src/hooks/useStorageAt.test-d.ts new file mode 100644 index 0000000000..bbc37fa03d --- /dev/null +++ b/packages/react/src/hooks/useStorageAt.test-d.ts @@ -0,0 +1,15 @@ +import { expectTypeOf, test } from 'vitest' + +import type { Hex } from 'viem' +import { useStorageAt } from './useStorageAt.js' + +test('select data', () => { + const result = useStorageAt({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useStorageAt.test.ts b/packages/react/src/hooks/useStorageAt.test.ts new file mode 100644 index 0000000000..9386480c93 --- /dev/null +++ b/packages/react/src/hooks/useStorageAt.test.ts @@ -0,0 +1,299 @@ +import { address, chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import type { Address } from 'viem' +import { useStorageAt } from './useStorageAt.js' + +test('default', async () => { + const { result } = renderHook(() => + useStorageAt({ + address: address.wagmiMintExample, + slot: '0x0', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x7761676d6900000000000000000000000000000000000000000000000000000a", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useStorageAt({ + address: address.wagmiMintExample, + blockNumber: 16280770n, + slot: '0x0', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x7761676d6900000000000000000000000000000000000000000000000000000a", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockNumber": 16280770n, + "chainId": 1, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockTag', async () => { + const { result } = renderHook(() => + useStorageAt({ + address: address.wagmiMintExample, + blockTag: 'safe', + slot: '0x0', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x7761676d6900000000000000000000000000000000000000000000000000000a", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockTag": "safe", + "chainId": 1, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useStorageAt({ + address: address.wagmiMintExample, + chainId: chain.optimism.id, + slot: '0x0', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 10, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let contractAddress: Address | undefined = undefined + + const { result, rerender } = renderHook(() => + useStorageAt({ + address: contractAddress, + slot: '0x0', + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "getStorageAt", + { + "address": undefined, + "chainId": 1, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + contractAddress = address.wagmiMintExample + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + expect(result.current).toMatchInlineSnapshot(` + { + "data": "0x7761676d6900000000000000000000000000000000000000000000000000000a", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getStorageAt", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + "slot": "0x0", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useStorageAt()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useStorageAt.ts b/packages/react/src/hooks/useStorageAt.ts new file mode 100644 index 0000000000..3a58376fd4 --- /dev/null +++ b/packages/react/src/hooks/useStorageAt.ts @@ -0,0 +1,57 @@ +'use client' + +import type { + Config, + GetStorageAtErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetStorageAtData, + type GetStorageAtOptions, + type GetStorageAtQueryKey, + getStorageAtQueryOptions, +} from '@wagmi/core/query' +import type { GetStorageAtQueryFnData } from '@wagmi/core/query' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseStorageAtParameters< + config extends Config = Config, + selectData = GetStorageAtData, +> = Compute< + GetStorageAtOptions & + ConfigParameter & + QueryParameter< + GetStorageAtQueryFnData, + GetStorageAtErrorType, + selectData, + GetStorageAtQueryKey + > +> + +export type UseStorageAtReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useStorageAt */ +export function useStorageAt< + config extends Config = ResolvedRegister['config'], + selectData = GetStorageAtData, +>( + parameters: UseStorageAtParameters = {}, +): UseStorageAtReturnType { + const { address, slot, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getStorageAtQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && slot && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useSwitchAccount.test-d.ts b/packages/react/src/hooks/useSwitchAccount.test-d.ts new file mode 100644 index 0000000000..f7d97355a3 --- /dev/null +++ b/packages/react/src/hooks/useSwitchAccount.test-d.ts @@ -0,0 +1,87 @@ +import type { Connector, SwitchAccountErrorType } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Address } from 'viem' +import { useSwitchAccount } from './useSwitchAccount.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, switchAccount, variables } = useSwitchAccount({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector } | undefined>() + expectTypeOf(context).toEqualTypeOf() + + switchAccount( + { connector }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useSwitchAccount.test.ts b/packages/react/src/hooks/useSwitchAccount.test.ts new file mode 100644 index 0000000000..5461d08213 --- /dev/null +++ b/packages/react/src/hooks/useSwitchAccount.test.ts @@ -0,0 +1,44 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useSwitchAccount } from './useSwitchAccount.js' + +const connector1 = config.connectors[0]! +const connector2 = config.connectors[1]! + +test('default', async () => { + await connect(config, { connector: connector2 }) + await connect(config, { connector: connector1 }) + + const { result } = renderHook(() => ({ + useAccount: useAccount(), + useSwitchAccount: useSwitchAccount(), + })) + + const address1 = result.current.useAccount.address + expect(address1).toBeDefined() + + result.current.useSwitchAccount.switchAccount({ connector: connector2 }) + await waitFor(() => + expect(result.current.useSwitchAccount.isSuccess).toBeTruthy(), + ) + + const address2 = result.current.useAccount.address + expect(address2).toBeDefined() + expect(address1).not.toBe(address2) + + result.current.useSwitchAccount.switchAccount({ connector: connector1 }) + await waitFor(() => + expect(result.current.useSwitchAccount.isSuccess).toBeTruthy(), + ) + + const address3 = result.current.useAccount.address + expect(address3).toBeDefined() + expect(address1).toBe(address3) + + await disconnect(config, { connector: connector1 }) + await disconnect(config, { connector: connector2 }) +}) diff --git a/packages/react/src/hooks/useSwitchAccount.ts b/packages/react/src/hooks/useSwitchAccount.ts new file mode 100644 index 0000000000..e9dc305545 --- /dev/null +++ b/packages/react/src/hooks/useSwitchAccount.ts @@ -0,0 +1,84 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + Connector, + ResolvedRegister, + SwitchAccountErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchAccountData, + type SwitchAccountMutate, + type SwitchAccountMutateAsync, + type SwitchAccountVariables, + switchAccountMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { useConnections } from './useConnections.js' + +export type UseSwitchAccountParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > + | undefined + } +> + +export type UseSwitchAccountReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > & { + connectors: readonly Connector[] + switchAccount: SwitchAccountMutate + switchAccountAsync: SwitchAccountMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSwitchAccount */ +export function useSwitchAccount< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchAccountParameters = {}, +): UseSwitchAccountReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = switchAccountMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: useConnections({ config }).map( + (connection) => connection.connector, + ), + switchAccount: mutate, + switchAccountAsync: mutateAsync, + } +} diff --git a/packages/react/src/hooks/useSwitchChain.test-d.ts b/packages/react/src/hooks/useSwitchChain.test-d.ts new file mode 100644 index 0000000000..07098c7724 --- /dev/null +++ b/packages/react/src/hooks/useSwitchChain.test-d.ts @@ -0,0 +1,118 @@ +import type { Connector, SwitchChainErrorType } from '@wagmi/core' +import type { Chain } from '@wagmi/core/chains' +import type { Compute, ExactPartial } from '@wagmi/core/internal' +import { chain } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { AddEthereumChainParameter } from 'viem' +import { useSwitchChain } from './useSwitchChain.js' + +const chainId = chain.mainnet.id +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { chains, context, data, error, switchChain, variables } = + useSwitchChain({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(data).toEqualTypeOf>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(chains).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + switchChain( + { chainId }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(data).toEqualTypeOf>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useSwitchChain.test.ts b/packages/react/src/hooks/useSwitchChain.test.ts new file mode 100644 index 0000000000..1fe0ca46b2 --- /dev/null +++ b/packages/react/src/hooks/useSwitchChain.test.ts @@ -0,0 +1,114 @@ +import { connect, disconnect } from '@wagmi/core' +import { chain, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useSwitchChain } from './useSwitchChain.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => ({ + useAccount: useAccount(), + useSwitchChain: useSwitchChain(), + })) + + const chainId1 = result.current.useAccount.chainId + expect(chainId1).toBeDefined() + + result.current.useSwitchChain.switchChain({ chainId: chain.mainnet2.id }) + await waitFor(() => + expect(result.current.useSwitchChain.isSuccess).toBeTruthy(), + ) + + const chainId2 = result.current.useAccount.chainId + expect(chainId2).toBeDefined() + expect(chainId1).not.toBe(chainId2) + + result.current.useSwitchChain.switchChain({ chainId: chain.mainnet.id }) + await waitFor(() => + expect(result.current.useSwitchChain.isSuccess).toBeTruthy(), + ) + + const chainId3 = result.current.useAccount.chainId + expect(chainId3).toBeDefined() + expect(chainId1).toBe(chainId3) + + await disconnect(config, { connector }) +}) + +test('behavior: chains updates', () => { + const { result, rerender } = renderHook(() => useSwitchChain()) + + const chains = result.current.chains + expect( + result.current.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + { + "id": 10, + "name": "OP Mainnet", + }, + ] + `) + + config._internal.chains.setState([chain.mainnet, chain.mainnet2]) + rerender() + + expect( + result.current.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + ] + `) + + config._internal.chains.setState(chains) + rerender() + + expect( + result.current.chains.map(({ id, name }) => ({ + id, + name, + })), + ).toMatchInlineSnapshot(` + [ + { + "id": 1, + "name": "Ethereum", + }, + { + "id": 456, + "name": "Ethereum", + }, + { + "id": 10, + "name": "OP Mainnet", + }, + ] + `) +}) diff --git a/packages/react/src/hooks/useSwitchChain.ts b/packages/react/src/hooks/useSwitchChain.ts new file mode 100644 index 0000000000..97ecf4ce9e --- /dev/null +++ b/packages/react/src/hooks/useSwitchChain.ts @@ -0,0 +1,82 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + ResolvedRegister, + SwitchChainErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchChainData, + type SwitchChainMutate, + type SwitchChainMutateAsync, + type SwitchChainVariables, + switchChainMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useChains } from './useChains.js' +import { useConfig } from './useConfig.js' + +export type UseSwitchChainParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > + | undefined + } +> + +export type UseSwitchChainReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > & { + chains: config['chains'] + switchChain: SwitchChainMutate + switchChainAsync: SwitchChainMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useSwitchChain */ +export function useSwitchChain< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchChainParameters = {}, +): UseSwitchChainReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = switchChainMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSwitchChainReturnType + return { + ...result, + chains: useChains({ config }) as unknown as config['chains'], + switchChain: mutate as Return['switchChain'], + switchChainAsync: mutateAsync as Return['switchChainAsync'], + } +} diff --git a/packages/react/src/hooks/useSyncExternalStoreWithTracked.test.tsx b/packages/react/src/hooks/useSyncExternalStoreWithTracked.test.tsx new file mode 100644 index 0000000000..e0b1e71a94 --- /dev/null +++ b/packages/react/src/hooks/useSyncExternalStoreWithTracked.test.tsx @@ -0,0 +1,275 @@ +import { fireEvent, screen } from '@testing-library/react' +import { act, cleanup, render, renderHook } from '@wagmi/test/react' +import React from 'react' +import * as ReactDOM from 'react-dom' +import { afterEach, expect, test } from 'vitest' + +import { useSyncExternalStoreWithTracked } from './useSyncExternalStoreWithTracked.js' + +function createExternalStore(initialState: state) { + const listeners = new Set<() => void>() + let currentState = initialState + return { + set(updater: (state: state) => state) { + currentState = updater(currentState) + ReactDOM.unstable_batchedUpdates(() => { + for (const listener of listeners) { + listener() + } + }) + }, + subscribe(listener: () => void) { + listeners.add(listener) + return () => listeners.delete(listener) + }, + getState() { + return currentState + }, + } +} + +function useExternalStore( + store: ReturnType, + cb: (state: any) => void, +) { + const state = useSyncExternalStoreWithTracked( + store.subscribe, + store.getState, + store.getState, + ) + cb(state) + return state as any +} + +afterEach(() => { + cleanup() +}) + +test('rerenders only when the tracked value changes', async () => { + const externalStore = createExternalStore({ + foo: 'bar', + gm: 'wagmi', + isGonnaMakeIt: false, + }) + + const renders: any[] = [] + + renderHook(() => { + const { gm } = useExternalStore(externalStore, (state) => { + renders.push(state) + }) + + return gm + }) + + act(() => { + externalStore.set((x) => ({ ...x, foo: 'baz', isGonnaMakeIt: true })) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": false, + }, + ] + `) + + act(() => { + externalStore.set((x) => ({ ...x, gm: 'ngmi' })) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": false, + }, + { + "foo": "baz", + "gm": "ngmi", + "isGonnaMakeIt": true, + }, + ] + `) +}) + +test('rerenders when all values are being tracked', async () => { + const externalStore = createExternalStore({ + foo: 'bar', + gm: 'wagmi', + isGonnaMakeIt: false, + }) + + const renders: any[] = [] + + renderHook(() => { + const { foo, gm, isGonnaMakeIt } = useExternalStore( + externalStore, + (state) => { + renders.push(state) + }, + ) + + return { + foo, + gm, + isGonnaMakeIt, + } + }) + + act(() => { + externalStore.set((x) => ({ ...x, isGonnaMakeIt: true })) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": false, + }, + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": true, + }, + ] + `) +}) + +test('rerenders when no values are being tracked', async () => { + const externalStore = createExternalStore({ + foo: 'bar', + gm: 'wagmi', + isGonnaMakeIt: false, + }) + + const renders: any[] = [] + + renderHook(() => { + useExternalStore(externalStore, (state) => { + renders.push(state) + }) + }) + + act(() => { + externalStore.set((x) => ({ ...x, isGonnaMakeIt: true })) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": false, + }, + { + "foo": "bar", + "gm": "wagmi", + "isGonnaMakeIt": true, + }, + ] + `) +}) + +test('store object reference is stable across rerenders', async () => { + const externalStore = createExternalStore({ + foo: 'bar', + gm: 'wagmi', + isGonnaMakeIt: false, + }) + + let childRenderCount = 0 + const MemoComponent = React.memo((props: { store: any }) => { + childRenderCount++ + return
{props.store.isGonnaMakeIt}
+ }) + + const renders: any[] = [] + + function Test() { + const store = useExternalStore(externalStore, (state) => { + renders.push(state) + }) + const [, rerender] = React.useState(0) + + return ( + <> + + + + ) + } + + render() + + const forceRerenderBtn = screen.getByRole('button') + expect(childRenderCount).toBe(1) + expect(renders.length).toBe(1) + + // updating parent state, child should not rerender + fireEvent.click(forceRerenderBtn) + expect(childRenderCount).toBe(1) + expect(renders.length).toBe(2) + + // child and parent both rerender when store changes + act(() => { + externalStore.set((x) => ({ ...x, isGonnaMakeIt: true })) + }) + expect(childRenderCount).toBe(2) + expect(renders.length).toBe(3) +}) + +test('array', async () => { + const externalStore = createExternalStore(['foo']) + + const renders: any[] = [] + + renderHook(() => { + const array = useExternalStore(externalStore, (state) => { + renders.push(state) + }) + + return array + }) + + act(() => { + externalStore.set((x) => [...x, 'bar']) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + [ + "foo", + ], + [ + "foo", + "bar", + ], + ] + `) + + act(() => { + externalStore.set((x) => [...x, 'baz']) + }) + + expect(renders).toMatchInlineSnapshot(` + [ + [ + "foo", + ], + [ + "foo", + "bar", + ], + [ + "foo", + "bar", + "baz", + ], + ] + `) +}) diff --git a/packages/react/src/hooks/useSyncExternalStoreWithTracked.ts b/packages/react/src/hooks/useSyncExternalStoreWithTracked.ts new file mode 100644 index 0000000000..4e372a556d --- /dev/null +++ b/packages/react/src/hooks/useSyncExternalStoreWithTracked.ts @@ -0,0 +1,67 @@ +'use client' + +import { deepEqual } from '@wagmi/core/internal' +import { useMemo, useRef } from 'react' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js' + +const isPlainObject = (obj: unknown) => + typeof obj === 'object' && !Array.isArray(obj) + +export function useSyncExternalStoreWithTracked< + snapshot extends selection, + selection = snapshot, +>( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => snapshot, + getServerSnapshot: undefined | null | (() => snapshot) = getSnapshot, + isEqual: (a: selection, b: selection) => boolean = deepEqual, +) { + const trackedKeys = useRef([]) + const result = useSyncExternalStoreWithSelector( + subscribe, + getSnapshot, + getServerSnapshot, + (x) => x, + (a, b) => { + if (isPlainObject(a) && isPlainObject(b) && trackedKeys.current.length) { + for (const key of trackedKeys.current) { + const equal = isEqual( + (a as { [_a: string]: any })[key], + (b as { [_b: string]: any })[key], + ) + if (!equal) return false + } + return true + } + return isEqual(a, b) + }, + ) + + return useMemo(() => { + if (isPlainObject(result)) { + const trackedResult = { ...result } + let properties = {} + for (const [key, value] of Object.entries( + trackedResult as { [key: string]: any }, + )) { + properties = { + ...properties, + [key]: { + configurable: false, + enumerable: true, + get: () => { + if (!trackedKeys.current.includes(key)) { + trackedKeys.current.push(key) + } + return value + }, + }, + } + } + Object.defineProperties(trackedResult, properties) + return trackedResult + } + + return result + }, [result]) +} diff --git a/packages/react/src/hooks/useToken.test-d.ts b/packages/react/src/hooks/useToken.test-d.ts new file mode 100644 index 0000000000..2018951919 --- /dev/null +++ b/packages/react/src/hooks/useToken.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useToken } from './useToken.js' + +test('select data', () => { + const result = useToken({ + query: { + select(data) { + return data?.name + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useToken.test.ts b/packages/react/src/hooks/useToken.test.ts new file mode 100644 index 0000000000..6023797532 --- /dev/null +++ b/packages/react/src/hooks/useToken.test.ts @@ -0,0 +1,59 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useToken } from './useToken.js' + +test('default', async () => { + const { result } = renderHook(() => + useToken({ + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "decimals": 18, + "name": "Uniswap", + "symbol": "UNI", + "totalSupply": { + "formatted": "1000000000", + "value": 1000000000000000000000000000n, + }, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "token", + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useToken.ts b/packages/react/src/hooks/useToken.ts new file mode 100644 index 0000000000..1d43912f72 --- /dev/null +++ b/packages/react/src/hooks/useToken.ts @@ -0,0 +1,60 @@ +'use client' + +import type { Config, GetTokenErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetTokenData, + type GetTokenOptions, + type GetTokenQueryFnData, + type GetTokenQueryKey, + getTokenQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTokenParameters< + config extends Config = Config, + selectData = GetTokenData, +> = Compute< + GetTokenOptions & + ConfigParameter & + QueryParameter< + GetTokenQueryFnData, + GetTokenErrorType, + selectData, + GetTokenQueryKey + > +> + +export type UseTokenReturnType = UseQueryReturnType< + selectData, + GetTokenErrorType +> + +/** + * @deprecated + * + * https://wagmi.sh/react/api/hooks/useToken + */ +export function useToken< + config extends Config = ResolvedRegister['config'], + selectData = GetTokenData, +>( + parameters: UseTokenParameters = {}, +): UseTokenReturnType { + const { address, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getTokenQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useTransaction.test-d.ts b/packages/react/src/hooks/useTransaction.test-d.ts new file mode 100644 index 0000000000..211efa7f6c --- /dev/null +++ b/packages/react/src/hooks/useTransaction.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransaction } from './useTransaction.js' + +test('select data', () => { + const result = useTransaction({ + query: { + select(data) { + return data?.nonce + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useTransaction.test.ts b/packages/react/src/hooks/useTransaction.test.ts new file mode 100644 index 0000000000..190cffb1fe --- /dev/null +++ b/packages/react/src/hooks/useTransaction.test.ts @@ -0,0 +1,72 @@ +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useTransaction } from './useTransaction.js' + +test('default', async () => { + const { result } = renderHook(() => + useTransaction({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "accessList": [], + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "chainId": 1, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gas": 21000n, + "gasPrice": 9371645552n, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "input": "0x", + "maxFeePerGas": 13644824566n, + "maxPriorityFeePerGas": 1500000000n, + "nonce": 86, + "r": "0x40174f9a38df876c1a7ce2587848819d4082ccd6d67a88aa5cabe59bf594e14f", + "s": "0x7c0c82f62a8a5a9b0e9cf30a54a72fdae8fc54b5b79ddafef0acd30e94e83872", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionIndex": 144, + "type": "eip1559", + "typeHex": "0x2", + "v": 0n, + "value": 100000000000000000n, + "yParity": 0, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transaction", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) diff --git a/packages/react/src/hooks/useTransaction.ts b/packages/react/src/hooks/useTransaction.ts new file mode 100644 index 0000000000..6cc920e873 --- /dev/null +++ b/packages/react/src/hooks/useTransaction.ts @@ -0,0 +1,72 @@ +'use client' + +import type { + Config, + GetTransactionErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetTransactionData, + type GetTransactionOptions, + type GetTransactionQueryFnData, + type GetTransactionQueryKey, + getTransactionQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +> = Compute< + GetTransactionOptions & + ConfigParameter & + QueryParameter< + GetTransactionQueryFnData, + GetTransactionErrorType, + selectData, + GetTransactionQueryKey + > +> + +export type UseTransactionReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useTransaction */ +export function useTransaction< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +>( + parameters: UseTransactionParameters = {}, +): UseTransactionReturnType { + const { blockHash, blockNumber, blockTag, hash, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getTransactionQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean( + !(blockHash && blockNumber && blockTag && hash) && (query.enabled ?? true), + ) + + return useQuery({ + ...(query as any), + ...options, + enabled, + }) as UseTransactionReturnType +} diff --git a/packages/react/src/hooks/useTransactionConfirmations.test-d.ts b/packages/react/src/hooks/useTransactionConfirmations.test-d.ts new file mode 100644 index 0000000000..e2bbcb4266 --- /dev/null +++ b/packages/react/src/hooks/useTransactionConfirmations.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransactionConfirmations } from './useTransactionConfirmations.js' + +test('select data', () => { + const result = useTransactionConfirmations({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useTransactionConfirmations.test.ts b/packages/react/src/hooks/useTransactionConfirmations.test.ts new file mode 100644 index 0000000000..4f4a79c251 --- /dev/null +++ b/packages/react/src/hooks/useTransactionConfirmations.test.ts @@ -0,0 +1,215 @@ +import { config, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import type { Hash } from 'viem' +import { expect, test } from 'vitest' + +import { getTransactionReceipt } from '@wagmi/core' +import { useTransactionConfirmations } from './useTransactionConfirmations.js' + +test('default', async () => { + const { result } = renderHook(() => + useTransactionConfirmations({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('bigint') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionConfirmations", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: transactionReceipt', async () => { + const transactionReceipt = await getTransactionReceipt(config, { + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }) + + const { result } = renderHook(() => + useTransactionConfirmations({ + transactionReceipt, + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('bigint') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionConfirmations", + { + "chainId": 1, + "transactionReceipt": { + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "contractAddress": null, + "cumulativeGasUsed": 12949744n, + "effectiveGasPrice": 9371645552n, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionHash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "transactionIndex": 144, + "type": "eip1559", + }, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: hash: undefined -> defined', async () => { + let hash: Hash | undefined = undefined + + const { result, rerender } = renderHook(() => + useTransactionConfirmations({ + hash, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "transactionConfirmations", + { + "chainId": 1, + "hash": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + hash = '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('bigint') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionConfirmations", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useTransactionConfirmations()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useTransactionConfirmations.ts b/packages/react/src/hooks/useTransactionConfirmations.ts new file mode 100644 index 0000000000..c8e9ddec3f --- /dev/null +++ b/packages/react/src/hooks/useTransactionConfirmations.ts @@ -0,0 +1,66 @@ +'use client' + +import type { + Config, + GetTransactionConfirmationsErrorType, + ResolvedRegister, +} from '@wagmi/core' +import { + type GetTransactionConfirmationsData, + type GetTransactionConfirmationsOptions, + type GetTransactionConfirmationsQueryFnData, + type GetTransactionConfirmationsQueryKey, + getTransactionConfirmationsQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionConfirmationsParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = GetTransactionConfirmationsData, +> = GetTransactionConfirmationsOptions & + ConfigParameter & + QueryParameter< + GetTransactionConfirmationsQueryFnData, + GetTransactionConfirmationsErrorType, + selectData, + GetTransactionConfirmationsQueryKey + > + +export type UseTransactionConfirmationsReturnType< + selectData = GetTransactionConfirmationsData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useTransactionConfirmations */ +export function useTransactionConfirmations< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = GetTransactionConfirmationsData, +>( + parameters: UseTransactionConfirmationsParameters< + config, + chainId, + selectData + > = {} as any, +): UseTransactionConfirmationsReturnType { + const { hash, transactionReceipt, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getTransactionConfirmationsQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean( + !(hash && transactionReceipt) && + (hash || transactionReceipt) && + (query.enabled ?? true), + ) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useTransactionCount.test-d.ts b/packages/react/src/hooks/useTransactionCount.test-d.ts new file mode 100644 index 0000000000..069d10b9cd --- /dev/null +++ b/packages/react/src/hooks/useTransactionCount.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransactionCount } from './useTransactionCount.js' + +test('select data', () => { + const result = useTransactionCount({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useTransactionCount.test.ts b/packages/react/src/hooks/useTransactionCount.test.ts new file mode 100644 index 0000000000..13a241ea88 --- /dev/null +++ b/packages/react/src/hooks/useTransactionCount.test.ts @@ -0,0 +1,238 @@ +import { accounts, chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import type { Address } from 'viem' +import { expect, test } from 'vitest' + +import { useTransactionCount } from './useTransactionCount.js' + +const address = accounts[0] + +test('default', async () => { + const { result } = renderHook(() => useTransactionCount({ address })) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useTransactionCount({ address, chainId: chain.mainnet2.id }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useTransactionCount({ address, blockNumber: 13677382n }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 13677382n, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + let address: Address | undefined = undefined + + const { result, rerender } = renderHook(() => + useTransactionCount({ address }), + ) + + { + const { data, ...rest } = result.current + expect(data).toBeTypeOf('undefined') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "transactionCount", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + } + + address = accounts[0] + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, ...rest } = result.current + expect(data).toBeTypeOf('number') + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transactionCount", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useTransactionCount()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useTransactionCount.ts b/packages/react/src/hooks/useTransactionCount.ts new file mode 100644 index 0000000000..341536aea7 --- /dev/null +++ b/packages/react/src/hooks/useTransactionCount.ts @@ -0,0 +1,59 @@ +'use client' + +import type { + Config, + GetTransactionCountErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import type { GetTransactionCountQueryFnData } from '@wagmi/core/query' +import { + type GetTransactionCountData, + type GetTransactionCountOptions, + type GetTransactionCountQueryKey, + getTransactionCountQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionCountParameters< + config extends Config = Config, + selectData = GetTransactionCountData, +> = Compute< + GetTransactionCountOptions & + ConfigParameter & + QueryParameter< + GetTransactionCountQueryFnData, + GetTransactionCountErrorType, + selectData, + GetTransactionCountQueryKey + > +> + +export type UseTransactionCountReturnType< + selectData = GetTransactionCountData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useTransactionCount */ +export function useTransactionCount< + config extends Config = ResolvedRegister['config'], + selectData = GetTransactionCountData, +>( + parameters: UseTransactionCountParameters = {}, +): UseTransactionCountReturnType { + const { address, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getTransactionCountQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useTransactionReceipt.test-d.ts b/packages/react/src/hooks/useTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..af2785934a --- /dev/null +++ b/packages/react/src/hooks/useTransactionReceipt.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransactionReceipt } from './useTransactionReceipt.js' + +test('select data', () => { + const result = useTransactionReceipt({ + query: { + select(data) { + return data?.blockNumber + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useTransactionReceipt.test.ts b/packages/react/src/hooks/useTransactionReceipt.test.ts new file mode 100644 index 0000000000..fd2e24d76b --- /dev/null +++ b/packages/react/src/hooks/useTransactionReceipt.test.ts @@ -0,0 +1,237 @@ +import { chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import type { Hash } from 'viem' +import { expect, test } from 'vitest' +import { useTransactionReceipt } from './useTransactionReceipt.js' + +test('default', async () => { + const { result } = renderHook(() => + useTransactionReceipt({ + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 1, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useTransactionReceipt({ + chainId: chain.mainnet2.id, + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 456, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: hash: undefined -> defined', async () => { + let hash: Hash | undefined = undefined + + const { result, rerender } = renderHook(() => + useTransactionReceipt({ + hash, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 1, + "hash": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + hash = '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 1, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useTransactionReceipt()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useTransactionReceipt.ts b/packages/react/src/hooks/useTransactionReceipt.ts new file mode 100644 index 0000000000..f29ab6204e --- /dev/null +++ b/packages/react/src/hooks/useTransactionReceipt.ts @@ -0,0 +1,69 @@ +'use client' + +import type { + Config, + GetTransactionReceiptErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetTransactionReceiptData, + type GetTransactionReceiptOptions, + type GetTransactionReceiptQueryKey, + getTransactionReceiptQueryOptions, +} from '@wagmi/core/query' +import type { GetTransactionReceiptQueryFnData } from '@wagmi/core/query' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +> = Compute< + GetTransactionReceiptOptions & + ConfigParameter & + QueryParameter< + GetTransactionReceiptQueryFnData, + GetTransactionReceiptErrorType, + selectData, + GetTransactionReceiptQueryKey + > +> + +export type UseTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useTransactionReceipt */ +export function useTransactionReceipt< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +>( + parameters: UseTransactionReceiptParameters = {}, +): UseTransactionReceiptReturnType { + const { hash, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = getTransactionReceiptQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(hash && (query.enabled ?? true)) + + return useQuery({ + ...(query as any), + ...options, + enabled, + }) as UseTransactionReceiptReturnType +} diff --git a/packages/react/src/hooks/useVerifyMessage.test-d.ts b/packages/react/src/hooks/useVerifyMessage.test-d.ts new file mode 100644 index 0000000000..5a9ad7e257 --- /dev/null +++ b/packages/react/src/hooks/useVerifyMessage.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useVerifyMessage } from './useVerifyMessage.js' + +test('select data', () => { + const result = useVerifyMessage({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useVerifyMessage.test.ts b/packages/react/src/hooks/useVerifyMessage.test.ts new file mode 100644 index 0000000000..244b6331f7 --- /dev/null +++ b/packages/react/src/hooks/useVerifyMessage.test.ts @@ -0,0 +1,318 @@ +import { accounts, chain, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import type { Hex } from 'viem' +import { useVerifyMessage } from './useVerifyMessage.js' + +const address = accounts[0] + +test('default', async () => { + const { result } = renderHook(() => + useVerifyMessage({ + address, + message: 'This is a test message for viem!', + signature: + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "message": "This is a test message for viem!", + "signature": "0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: chainId', async () => { + const { result } = renderHook(() => + useVerifyMessage({ + chainId: chain.mainnet2.id, + address, + message: 'This is a test message for viem!', + signature: + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 456, + "message": "This is a test message for viem!", + "signature": "0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockNumber', async () => { + const { result } = renderHook(() => + useVerifyMessage({ + blockNumber: 12345678n, + address, + message: 'This is a test message for viem!', + signature: + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockNumber": 12345678n, + "chainId": 1, + "message": "This is a test message for viem!", + "signature": "0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('parameters: blockTag', async () => { + const { result } = renderHook(() => + useVerifyMessage({ + blockTag: 'pending', + address, + message: 'This is a test message for viem!', + signature: + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "blockTag": "pending", + "chainId": 1, + "message": "This is a test message for viem!", + "signature": "0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: signature: undefined -> defined', async () => { + let signature: Hex | undefined = undefined + + const { result, rerender } = renderHook(() => + useVerifyMessage({ + address, + message: 'This is a test message for viem!', + signature, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "message": "This is a test message for viem!", + "signature": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + signature = + '0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyMessage", + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "message": "This is a test message for viem!", + "signature": "0xc4c7f2820177020d66d5fd00d084cdd3f575a868c059c29a2d7f23398d04819709a14f83d98b446dda539ca5dcb87d75aa3340eb15e66d67606850622a3420f61b", + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useVerifyMessage()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useVerifyMessage.ts b/packages/react/src/hooks/useVerifyMessage.ts new file mode 100644 index 0000000000..ae8bb6df21 --- /dev/null +++ b/packages/react/src/hooks/useVerifyMessage.ts @@ -0,0 +1,59 @@ +'use client' + +import type { + Config, + ResolvedRegister, + VerifyMessageErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type VerifyMessageData, + type VerifyMessageOptions, + type VerifyMessageQueryKey, + verifyMessageQueryOptions, +} from '@wagmi/core/query' +import type { VerifyMessageQueryFnData } from '@wagmi/core/query' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseVerifyMessageParameters< + config extends Config = Config, + selectData = VerifyMessageData, +> = Compute< + VerifyMessageOptions & + ConfigParameter & + QueryParameter< + VerifyMessageQueryFnData, + VerifyMessageErrorType, + selectData, + VerifyMessageQueryKey + > +> + +export type UseVerifyMessageReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useVerifyMessage */ +export function useVerifyMessage< + config extends Config = ResolvedRegister['config'], + selectData = VerifyMessageData, +>( + parameters: UseVerifyMessageParameters = {}, +): UseVerifyMessageReturnType { + const { address, message, signature, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = verifyMessageQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean( + address && message && signature && (query.enabled ?? true), + ) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useVerifyTypedData.test-d.ts b/packages/react/src/hooks/useVerifyTypedData.test-d.ts new file mode 100644 index 0000000000..91f875e0b6 --- /dev/null +++ b/packages/react/src/hooks/useVerifyTypedData.test-d.ts @@ -0,0 +1,40 @@ +import type { typedData } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Address } from 'viem' +import { + type UseVerifyTypedDataParameters, + useVerifyTypedData, +} from './useVerifyTypedData.js' + +test('select data', () => { + const result = useVerifyTypedData({ + query: { + select(data) { + return data + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) + +test('UseReadContractParameters', () => { + type Result = UseVerifyTypedDataParameters< + typeof typedData.basic.types, + 'Mail' + > + expectTypeOf>().toEqualTypeOf<{ + primaryType?: 'Mail' | 'Person' + message?: { + from: { + name: string + wallet: Address + } + to: { + name: string + wallet: Address + } + contents: string + } + }>() +}) diff --git a/packages/react/src/hooks/useVerifyTypedData.test.ts b/packages/react/src/hooks/useVerifyTypedData.test.ts new file mode 100644 index 0000000000..d57331743f --- /dev/null +++ b/packages/react/src/hooks/useVerifyTypedData.test.ts @@ -0,0 +1,481 @@ +import { typedData, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import type { Hex } from 'viem' +import { expect, test } from 'vitest' + +import { useVerifyTypedData } from './useVerifyTypedData.js' + +const smartAccountAddress = '0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145' +const notDeployedAddress = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' + +test('valid signature', async () => { + const { result } = renderHook(() => + useVerifyTypedData({ + ...typedData.basic, + primaryType: 'Mail', + address: smartAccountAddress, + signature: + '0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyTypedData", + { + "address": "0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145", + "chainId": 1, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('invalid signature', async () => { + const { result } = renderHook(() => + useVerifyTypedData({ + ...typedData.basic, + primaryType: 'Mail', + address: smartAccountAddress, + signature: '0xdead', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": false, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyTypedData", + { + "address": "0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145", + "chainId": 1, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0xdead", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('account not deployed', async () => { + const { result } = renderHook(() => + useVerifyTypedData({ + ...typedData.basic, + primaryType: 'Mail', + address: notDeployedAddress, + signature: + '0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": false, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyTypedData", + { + "address": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "chainId": 1, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: signature: undefined -> defined', async () => { + let signature: Hex | undefined = undefined + + const { result, rerender } = renderHook(() => + useVerifyTypedData({ + ...typedData.basic, + primaryType: 'Mail', + address: smartAccountAddress, + signature, + }), + ) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "verifyTypedData", + { + "address": "0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145", + "chainId": 1, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": undefined, + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) + + signature = + '0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c' + rerender() + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": true, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "verifyTypedData", + { + "address": "0x3FCf42e10CC70Fe75A62EB3aDD6D305Aa840d145", + "chainId": 1, + "domain": { + "chainId": 1, + "name": "Ether Mail", + "verifyingContract": "0x0000000000000000000000000000000000000000", + "version": "1", + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + }, + "primaryType": "Mail", + "signature": "0x79d756d805073dc97b7bc885b0d56ddf319a2599530fe1e178c2a7de5be88980068d24f20a79b318ea0a84d33ae06f93db77e4235e5d9eeb8b1d7a63922ada3e1c", + "types": { + "Mail": [ + { + "name": "from", + "type": "Person", + }, + { + "name": "to", + "type": "Person", + }, + { + "name": "contents", + "type": "string", + }, + ], + "Person": [ + { + "name": "name", + "type": "string", + }, + { + "name": "wallet", + "type": "address", + }, + ], + }, + }, + ], + "refetch": [Function], + "status": "success", + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const { result } = renderHook(() => useVerifyTypedData()) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useVerifyTypedData.ts b/packages/react/src/hooks/useVerifyTypedData.ts new file mode 100644 index 0000000000..b02d1a8510 --- /dev/null +++ b/packages/react/src/hooks/useVerifyTypedData.ts @@ -0,0 +1,81 @@ +'use client' + +import type { + Config, + ResolvedRegister, + VerifyTypedDataErrorType, +} from '@wagmi/core' +import { + type VerifyTypedDataData, + type VerifyTypedDataOptions, + type VerifyTypedDataQueryKey, + verifyTypedDataQueryOptions, +} from '@wagmi/core/query' +import type { VerifyTypedDataQueryFnData } from '@wagmi/core/query' +import type { TypedData } from 'viem' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseVerifyTypedDataParameters< + typedData extends TypedData | Record = TypedData, + primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData, + config extends Config = Config, + selectData = VerifyTypedDataData, +> = VerifyTypedDataOptions & + ConfigParameter & + QueryParameter< + VerifyTypedDataQueryFnData, + VerifyTypedDataErrorType, + selectData, + VerifyTypedDataQueryKey + > + +export type UseVerifyTypedDataReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useVerifyTypedData */ +export function useVerifyTypedData< + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', + config extends Config = ResolvedRegister['config'], + selectData = VerifyTypedDataData, +>( + parameters: UseVerifyTypedDataParameters< + typedData, + primaryType, + config, + selectData + > = {} as any, +): UseVerifyTypedDataReturnType { + const { + address, + message, + primaryType, + signature, + types, + query = {}, + } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = verifyTypedDataQueryOptions( + config, + { + ...parameters, + chainId: parameters.chainId ?? chainId, + }, + ) + const enabled = Boolean( + address && + message && + primaryType && + signature && + types && + (query.enabled ?? true), + ) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useWaitForCallsStatus.test.ts b/packages/react/src/hooks/useWaitForCallsStatus.test.ts new file mode 100644 index 0000000000..96f5a2a7e4 --- /dev/null +++ b/packages/react/src/hooks/useWaitForCallsStatus.test.ts @@ -0,0 +1,101 @@ +import { connect, disconnect } from '@wagmi/core' +import { accounts, config, testClient, wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useSendCalls } from './useSendCalls.js' +import { useWaitForCallsStatus } from './useWaitForCallsStatus.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const useSendCalls_render = renderHook(() => useSendCalls()) + const useWaitForCallsStatus_render = renderHook(() => + useWaitForCallsStatus({ id: useSendCalls_render.result.current.data?.id }), + ) + + useSendCalls_render.result.current.sendCalls({ + calls: [ + { + data: '0xdeadbeef', + to: accounts[1], + value: parseEther('1'), + }, + { + to: accounts[2], + value: parseEther('2'), + }, + { + to: accounts[3], + value: parseEther('3'), + }, + ], + }) + await waitFor(() => + expect(useSendCalls_render.result.current.isSuccess).toBeTruthy(), + ) + + expect(useWaitForCallsStatus_render.result.current.fetchStatus).toBe('idle') + useWaitForCallsStatus_render.rerender() + expect(useWaitForCallsStatus_render.result.current.fetchStatus).toBe( + 'fetching', + ) + + await Promise.all([ + waitFor(() => + expect( + useWaitForCallsStatus_render.result.current.isSuccess, + ).toBeTruthy(), + ), + (async () => { + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + })(), + ]) + + expect(useWaitForCallsStatus_render.result.current.data?.status).toBe( + 'success', + ) + expect( + useWaitForCallsStatus_render.result.current.data?.receipts?.map((x) => ({ + ...x, + blockHash: undefined, + })), + ).toMatchInlineSnapshot( + ` + [ + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21064n, + "logs": [], + "status": "success", + "transactionHash": "0x13c53b2d4d9da424835525349cd66e553330f323d6fb19458b801ae1f7989a41", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0xd8397b3e82b061c26a0c2093f1ceca0c3662a512614f7d6370349e89d0eea007", + }, + { + "blockHash": undefined, + "blockNumber": 19258214n, + "gasUsed": 21000n, + "logs": [], + "status": "success", + "transactionHash": "0x4d26e346593d9ea265bb164b115e89aa92df43b0b8778ac75d4ad28e2a22b101", + }, + ] + `, + ) + + await testClient.mainnet.mine({ blocks: 1 }) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useWaitForCallsStatus.ts b/packages/react/src/hooks/useWaitForCallsStatus.ts new file mode 100644 index 0000000000..7c428c9405 --- /dev/null +++ b/packages/react/src/hooks/useWaitForCallsStatus.ts @@ -0,0 +1,54 @@ +'use client' + +import type { + Config, + ResolvedRegister, + WaitForCallsStatusErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WaitForCallsStatusData, + type WaitForCallsStatusOptions, + type WaitForCallsStatusQueryFnData, + type WaitForCallsStatusQueryKey, + waitForCallsStatusQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseWaitForCallsStatusParameters< + config extends Config = Config, + selectData = WaitForCallsStatusData, +> = Compute< + WaitForCallsStatusOptions & + ConfigParameter & + QueryParameter< + WaitForCallsStatusQueryFnData, + WaitForCallsStatusErrorType, + selectData, + WaitForCallsStatusQueryKey + > +> + +export type UseWaitForCallsStatusReturnType< + selectData = WaitForCallsStatusData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useWaitForCallsStatus */ +export function useWaitForCallsStatus< + config extends Config = ResolvedRegister['config'], + selectData = WaitForCallsStatusData, +>( + parameters: UseWaitForCallsStatusParameters, +): UseWaitForCallsStatusReturnType { + const { id, query = {} } = parameters + + const config = useConfig(parameters) + + const options = waitForCallsStatusQueryOptions(config, parameters) + const enabled = Boolean(id && (query.enabled ?? true)) + + return useQuery({ ...query, ...options, enabled }) +} diff --git a/packages/react/src/hooks/useWaitForTransactionReceipt.test-d.ts b/packages/react/src/hooks/useWaitForTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..2d3bd28445 --- /dev/null +++ b/packages/react/src/hooks/useWaitForTransactionReceipt.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useWaitForTransactionReceipt } from './useWaitForTransactionReceipt.js' + +test('select data', () => { + const result = useWaitForTransactionReceipt({ + query: { + select(data) { + return data?.blockNumber + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf() +}) diff --git a/packages/react/src/hooks/useWaitForTransactionReceipt.test.ts b/packages/react/src/hooks/useWaitForTransactionReceipt.test.ts new file mode 100644 index 0000000000..484d25087f --- /dev/null +++ b/packages/react/src/hooks/useWaitForTransactionReceipt.test.ts @@ -0,0 +1,77 @@ +import { wait } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' +import { useWaitForTransactionReceipt } from './useWaitForTransactionReceipt.js' + +test('default', async () => { + const { result } = renderHook(() => + useWaitForTransactionReceipt({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }), + ) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result).toMatchInlineSnapshot(` + { + "current": { + "data": { + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "chainId": 1, + "contractAddress": null, + "cumulativeGasUsed": 12949744n, + "effectiveGasPrice": 9371645552n, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionHash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "transactionIndex": 144, + "type": "eip1559", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "waitForTransactionReceipt", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + }, + } + `) +}) + +test('disabled when hash is undefined', async () => { + const { result } = renderHook(() => + useWaitForTransactionReceipt({ hash: undefined }), + ) + + await wait(100) + await waitFor(() => expect(result.current.isPending).toBeTruthy()) +}) diff --git a/packages/react/src/hooks/useWaitForTransactionReceipt.ts b/packages/react/src/hooks/useWaitForTransactionReceipt.ts new file mode 100644 index 0000000000..c07d1639bc --- /dev/null +++ b/packages/react/src/hooks/useWaitForTransactionReceipt.ts @@ -0,0 +1,74 @@ +'use client' + +import type { + Config, + ResolvedRegister, + WaitForTransactionReceiptErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WaitForTransactionReceiptData, + type WaitForTransactionReceiptOptions, + type WaitForTransactionReceiptQueryFnData, + type WaitForTransactionReceiptQueryKey, + waitForTransactionReceiptQueryOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWaitForTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = Compute< + WaitForTransactionReceiptOptions & + ConfigParameter & + QueryParameter< + WaitForTransactionReceiptQueryFnData, + WaitForTransactionReceiptErrorType, + selectData, + WaitForTransactionReceiptQueryKey + > +> + +export type UseWaitForTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useWaitForTransactionReceipt */ +export function useWaitForTransactionReceipt< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +>( + parameters: UseWaitForTransactionReceiptParameters< + config, + chainId, + selectData + > = {}, +): UseWaitForTransactionReceiptReturnType { + const { hash, query = {} } = parameters + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const options = waitForTransactionReceiptQueryOptions(config, { + ...parameters, + chainId: parameters.chainId ?? chainId, + }) + const enabled = Boolean(hash && (query.enabled ?? true)) + + return useQuery({ + ...(query as any), + ...options, + enabled, + }) as UseWaitForTransactionReceiptReturnType +} diff --git a/packages/react/src/hooks/useWalletClient.test-d.ts b/packages/react/src/hooks/useWalletClient.test-d.ts new file mode 100644 index 0000000000..c2ea48bcb1 --- /dev/null +++ b/packages/react/src/hooks/useWalletClient.test-d.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useWalletClient } from './useWalletClient.js' + +test('parameters: config', async () => { + const client = useWalletClient({ config }) + expectTypeOf(client.data?.chain?.id!).toEqualTypeOf<1 | 456 | 10>() + + const client2 = useWalletClient({ config, chainId: 1 }) + expectTypeOf(client2.data?.chain?.id!).toEqualTypeOf<1>() +}) diff --git a/packages/react/src/hooks/useWalletClient.test.tsx b/packages/react/src/hooks/useWalletClient.test.tsx new file mode 100644 index 0000000000..40fdd5850a --- /dev/null +++ b/packages/react/src/hooks/useWalletClient.test.tsx @@ -0,0 +1,222 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, wait } from '@wagmi/test' +import { render, renderHook, waitFor } from '@wagmi/test/react' +import * as React from 'react' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useConnect } from './useConnect.js' +import { useDisconnect } from './useDisconnect.js' +import { useSwitchChain } from './useSwitchChain.js' +import { useWalletClient } from './useWalletClient.js' + +// Almost identical implementation to `useConnectorClient` (except for return type) +// Should update both in tandem + +const connector = config.connectors[0]! + +test('default', async () => { + const { result } = renderHook(() => useWalletClient()) + + await waitFor(() => expect(result.current.isPending).toBeTruthy()) + + expect(result.current).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "walletClient", + { + "chainId": 1, + "connectorUid": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + } + `) +}) + +test('behavior: connected on mount', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useWalletClient()) + + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + const { data, queryKey: _, ...rest } = result.current + expect(data).toMatchObject( + expect.objectContaining({ + account: expect.any(Object), + chain: expect.any(Object), + }), + ) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": true, + "refetch": [Function], + "status": "success", + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: connect and disconnect', async () => { + const { result } = renderHook(() => ({ + useConnect: useConnect(), + useWalletClient: useWalletClient(), + useDisconnect: useDisconnect(), + })) + + expect(result.current.useWalletClient.data).not.toBeDefined() + + result.current.useConnect.connect({ + connector: result.current.useConnect.connectors[0]!, + }) + + await waitFor(() => expect(result.current.useWalletClient.data).toBeDefined()) + + result.current.useDisconnect.disconnect() + + await waitFor(() => + expect(result.current.useWalletClient.data).not.toBeDefined(), + ) +}) + +test('behavior: switch chains', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => ({ + useWalletClient: useWalletClient(), + useSwitchChain: useSwitchChain(), + })) + + expect(result.current.useWalletClient.data).not.toBeDefined() + + await waitFor(() => expect(result.current.useWalletClient.data).toBeDefined()) + + result.current.useSwitchChain.switchChain({ chainId: 456 }) + await waitFor(() => { + expect(result.current.useSwitchChain.isSuccess).toBeTruthy() + result.current.useSwitchChain.reset() + }) + expect(result.current.useWalletClient.data?.chain.id).toEqual(456) + + result.current.useSwitchChain.switchChain({ chainId: 1 }) + await waitFor(() => + expect(result.current.useSwitchChain.isSuccess).toBeTruthy(), + ) + expect(result.current.useWalletClient.data?.chain.id).toEqual(1) + + await disconnect(config, { connector }) +}) + +test('behavior: re-render does not invalidate query', async () => { + const { getByTestId } = render() + + getByTestId('connect').click() + await waitFor(() => { + expect(getByTestId('address').innerText).toContain( + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + ) + expect(getByTestId('client').innerText).toBeTruthy() + + expect(getByTestId('child-client').innerText).toBeTruthy() + expect(getByTestId('render-count').innerText).toEqual('1') + }) + + const initialClient = getByTestId('child-client').innerText + + getByTestId('rerender').click() + await waitFor(() => { + expect(getByTestId('render-count').innerText).toEqual('2') + }) + await wait(200) + + expect(getByTestId('child-client').innerText).toEqual(initialClient) +}) + +function Parent() { + const [renderCount, setRenderCount] = React.useState(1) + + const { connectors, connect } = useConnect() + const { address } = useAccount() + const { data } = useWalletClient() + + return ( + <> +
{address}
+
{data?.uid}
+ + + + + + ) +} + +function Child(props: { + renderCount: number +}) { + const { renderCount } = props + const { data } = useWalletClient() + return ( +
+ {data?.uid} + {renderCount} +
+ ) +} diff --git a/packages/react/src/hooks/useWalletClient.ts b/packages/react/src/hooks/useWalletClient.ts new file mode 100644 index 0000000000..24a3bccdda --- /dev/null +++ b/packages/react/src/hooks/useWalletClient.ts @@ -0,0 +1,116 @@ +'use client' + +// Almost identical implementation to `useConnectorClient` (except for return type) +// Should update both in tandem + +import { useQueryClient } from '@tanstack/react-query' +import type { + Config, + GetWalletClientErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute, Omit } from '@wagmi/core/internal' +import { + type GetWalletClientData, + type GetWalletClientOptions, + type GetWalletClientQueryFnData, + type GetWalletClientQueryKey, + getWalletClientQueryOptions, +} from '@wagmi/core/query' +import { useEffect, useRef } from 'react' + +import type { ConfigParameter } from '../types/properties.js' +import { + type UseQueryParameters, + type UseQueryReturnType, + useQuery, +} from '../utils/query.js' +import { useAccount } from './useAccount.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWalletClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetWalletClientData, +> = Compute< + GetWalletClientOptions & + ConfigParameter & { + query?: + | Compute< + Omit< + UseQueryParameters< + GetWalletClientQueryFnData, + GetWalletClientErrorType, + selectData, + GetWalletClientQueryKey + >, + 'gcTime' | 'staleTime' + > + > + | undefined + } +> + +export type UseWalletClientReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetWalletClientData, +> = UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useWalletClient */ +export function useWalletClient< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetWalletClientData, +>( + parameters: UseWalletClientParameters = {}, +): UseWalletClientReturnType { + const { query = {}, ...rest } = parameters + + const config = useConfig(rest) + const queryClient = useQueryClient() + const { address, connector, status } = useAccount({ config }) + const chainId = useChainId({ config }) + const activeConnector = parameters.connector ?? connector + + const { queryKey, ...options } = getWalletClientQueryOptions( + config, + { + ...parameters, + chainId: parameters.chainId ?? chainId, + connector: parameters.connector ?? connector, + }, + ) + const enabled = Boolean( + (status === 'connected' || + (status === 'reconnecting' && activeConnector?.getProvider)) && + (query.enabled ?? true), + ) + + const addressRef = useRef(address) + // biome-ignore lint/correctness/useExhaustiveDependencies: `queryKey` not required + useEffect(() => { + const previousAddress = addressRef.current + if (!address && previousAddress) { + // remove when account is disconnected + queryClient.removeQueries({ queryKey }) + addressRef.current = undefined + } else if (address !== previousAddress) { + // invalidate when address changes + queryClient.invalidateQueries({ queryKey }) + addressRef.current = address + } + }, [address, queryClient]) + + return useQuery({ + ...query, + ...options, + queryKey, + enabled, + staleTime: Number.POSITIVE_INFINITY, + } as any) as UseWalletClientReturnType +} diff --git a/packages/react/src/hooks/useWatchAsset.test-d.ts b/packages/react/src/hooks/useWatchAsset.test-d.ts new file mode 100644 index 0000000000..0b7258c583 --- /dev/null +++ b/packages/react/src/hooks/useWatchAsset.test-d.ts @@ -0,0 +1,66 @@ +import type { WatchAssetErrorType } from '@wagmi/core' +import type { WatchAssetVariables } from '@wagmi/core/query' +import { expectTypeOf, test } from 'vitest' + +import { useWatchAsset } from './useWatchAsset.js' + +const tokenInfo = { + address: '0x0000000000000000000000000000000000000000', + symbol: 'NULL', + decimals: 18, +} +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, watchAsset, variables } = useWatchAsset({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + watchAsset( + { type: 'ERC20', options: tokenInfo }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/react/src/hooks/useWatchAsset.test.ts b/packages/react/src/hooks/useWatchAsset.test.ts new file mode 100644 index 0000000000..989b0323c8 --- /dev/null +++ b/packages/react/src/hooks/useWatchAsset.test.ts @@ -0,0 +1,27 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useWatchAsset } from './useWatchAsset.js' + +const connector = config.connectors[0]! + +const tokenInfo = { + address: '0x0000000000000000000000000000000000000000', + symbol: 'NULL', + decimals: 18, +} + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useWatchAsset()) + + result.current.watchAsset({ type: 'ERC20', options: tokenInfo }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toEqual(true) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useWatchAsset.ts b/packages/react/src/hooks/useWatchAsset.ts new file mode 100644 index 0000000000..cda4d6b0be --- /dev/null +++ b/packages/react/src/hooks/useWatchAsset.ts @@ -0,0 +1,65 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { WatchAssetErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WatchAssetData, + type WatchAssetMutate, + type WatchAssetMutateAsync, + type WatchAssetVariables, + watchAssetMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseWatchAssetParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + WatchAssetData, + WatchAssetErrorType, + WatchAssetVariables, + context + > + | undefined + } +> + +export type UseWatchAssetReturnType = Compute< + UseMutationReturnType< + WatchAssetData, + WatchAssetErrorType, + WatchAssetVariables, + context + > & { + watchAsset: WatchAssetMutate + watchAssetAsync: WatchAssetMutateAsync + } +> + +/** https://wagmi.sh/react/api/hooks/useWatchAsset */ +export function useWatchAsset( + parameters: UseWatchAssetParameters = {}, +): UseWatchAssetReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = watchAssetMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + watchAsset: mutate, + watchAssetAsync: mutateAsync, + } +} diff --git a/packages/react/src/hooks/useWatchBlockNumber.test-d.ts b/packages/react/src/hooks/useWatchBlockNumber.test-d.ts new file mode 100644 index 0000000000..0d669725bc --- /dev/null +++ b/packages/react/src/hooks/useWatchBlockNumber.test-d.ts @@ -0,0 +1,71 @@ +import { createConfig } from '@wagmi/core' +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { + type UseWatchBlockNumberParameters, + useWatchBlockNumber, +} from './useWatchBlockNumber.js' + +test('default', () => { + useWatchBlockNumber({ + poll: false, + onBlockNumber() {}, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = UseWatchBlockNumberParameters< + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + poll: false, + onBlockNumber() {}, + }) + + type Result2 = UseWatchBlockNumberParameters + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + chainId: mainnet.id, + poll: true, + onBlockNumber() {}, + }) + useWatchBlockNumber({ + config, + chainId: mainnet.id, + // @ts-expect-error + poll: false, + onBlockNumber() {}, + }) + + type Result3 = UseWatchBlockNumberParameters< + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + chainId: optimism.id, + poll: true, + onBlockNumber() {}, + }) + useWatchBlockNumber({ + config, + chainId: optimism.id, + poll: false, + onBlockNumber() {}, + }) +}) diff --git a/packages/react/src/hooks/useWatchBlockNumber.test.ts b/packages/react/src/hooks/useWatchBlockNumber.test.ts new file mode 100644 index 0000000000..ecb900b357 --- /dev/null +++ b/packages/react/src/hooks/useWatchBlockNumber.test.ts @@ -0,0 +1,28 @@ +import { testClient, wait } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useWatchBlockNumber } from './useWatchBlockNumber.js' + +test('default', async () => { + const blockNumbers: bigint[] = [] + renderHook(() => + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + blockNumbers.push(blockNumber) + }, + }), + ) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blockNumbers.length).toBe(3) + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) +}) diff --git a/packages/react/src/hooks/useWatchBlockNumber.ts b/packages/react/src/hooks/useWatchBlockNumber.ts new file mode 100644 index 0000000000..33ddac48a6 --- /dev/null +++ b/packages/react/src/hooks/useWatchBlockNumber.ts @@ -0,0 +1,65 @@ +'use client' + +import { + type Config, + type ResolvedRegister, + type WatchBlockNumberParameters, + watchBlockNumber, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import { useEffect } from 'react' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = UnionCompute< + UnionExactPartial> & + ConfigParameter & + EnabledParameter +> + +export type UseWatchBlockNumberReturnType = void + +/** https://wagmi.sh/react/api/hooks/useWatchBlockNumber */ +export function useWatchBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters: UseWatchBlockNumberParameters = {} as any, +): UseWatchBlockNumberReturnType { + const { enabled = true, onBlockNumber, config: _, ...rest } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + // TODO(react@19): cleanup + // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` changes every render so only including properties in dependency array + useEffect(() => { + if (!enabled) return + if (!onBlockNumber) return + return watchBlockNumber(config, { + ...(rest as any), + chainId, + onBlockNumber, + }) + }, [ + chainId, + config, + enabled, + onBlockNumber, + /// + rest.onError, + rest.emitMissed, + rest.emitOnBegin, + rest.poll, + rest.pollingInterval, + rest.syncConnectedChain, + ]) +} diff --git a/packages/react/src/hooks/useWatchBlocks.test-d.ts b/packages/react/src/hooks/useWatchBlocks.test-d.ts new file mode 100644 index 0000000000..2051bfb66f --- /dev/null +++ b/packages/react/src/hooks/useWatchBlocks.test-d.ts @@ -0,0 +1,73 @@ +import { createConfig } from '@wagmi/core' +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { + type UseWatchBlocksParameters, + useWatchBlocks, +} from './useWatchBlocks.js' + +test('default', () => { + useWatchBlocks({ + poll: false, + onBlock() {}, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = UseWatchBlocksParameters< + false, + 'latest', + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchBlocks({ + config, + poll: false, + onBlock() {}, + }) + + type Result2 = UseWatchBlocksParameters< + false, + 'latest', + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + useWatchBlocks({ + config, + chainId: mainnet.id, + poll: true, + onBlock() {}, + }) + + type Result3 = UseWatchBlocksParameters< + false, + 'latest', + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchBlocks({ + config, + chainId: optimism.id, + poll: true, + onBlock() {}, + }) + useWatchBlocks({ + config, + chainId: optimism.id, + poll: false, + onBlock() {}, + }) +}) diff --git a/packages/react/src/hooks/useWatchBlocks.test.ts b/packages/react/src/hooks/useWatchBlocks.test.ts new file mode 100644 index 0000000000..039c718834 --- /dev/null +++ b/packages/react/src/hooks/useWatchBlocks.test.ts @@ -0,0 +1,31 @@ +import { testClient, wait } from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import type { Block } from 'viem' +import { expect, test } from 'vitest' + +import { useWatchBlocks } from './useWatchBlocks.js' + +test('default', async () => { + const blocks: Block[] = [] + renderHook(() => + useWatchBlocks({ + onBlock(block) { + blocks.push(block) + }, + }), + ) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blocks.length).toBe(3) + expect(blocks.map((block) => block.number! - blocks[0]!.number!)).toEqual([ + 0n, + 1n, + 2n, + ]) +}) diff --git a/packages/react/src/hooks/useWatchBlocks.ts b/packages/react/src/hooks/useWatchBlocks.ts new file mode 100644 index 0000000000..466929c841 --- /dev/null +++ b/packages/react/src/hooks/useWatchBlocks.ts @@ -0,0 +1,79 @@ +'use client' + +import { + type Config, + type ResolvedRegister, + type WatchBlocksParameters, + watchBlocks, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import { useEffect } from 'react' +import type { BlockTag } from 'viem' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchBlocksParameters< + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = UnionCompute< + UnionExactPartial< + WatchBlocksParameters + > & + ConfigParameter & + EnabledParameter +> + +export type UseWatchBlocksReturnType = void + +/** https://wagmi.sh/react/hooks/useWatchBlocks */ +export function useWatchBlocks< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + includeTransactions extends boolean = false, + blockTag extends BlockTag = 'latest', +>( + parameters: UseWatchBlocksParameters< + includeTransactions, + blockTag, + config, + chainId + > = {} as any, +): UseWatchBlocksReturnType { + const { enabled = true, onBlock, config: _, ...rest } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + // TODO(react@19): cleanup + // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` changes every render so only including properties in dependency array + useEffect(() => { + if (!enabled) return + if (!onBlock) return + return watchBlocks(config, { + ...(rest as any), + chainId, + onBlock, + }) + }, [ + chainId, + config, + enabled, + onBlock, + /// + rest.blockTag, + rest.emitMissed, + rest.emitOnBegin, + rest.includeTransactions, + rest.onError, + rest.poll, + rest.pollingInterval, + rest.syncConnectedChain, + ]) +} diff --git a/packages/react/src/hooks/useWatchContractEvent.test-d.ts b/packages/react/src/hooks/useWatchContractEvent.test-d.ts new file mode 100644 index 0000000000..b1d74de450 --- /dev/null +++ b/packages/react/src/hooks/useWatchContractEvent.test-d.ts @@ -0,0 +1,128 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { mainnet, optimism } from '@wagmi/core/chains' +import { abi } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { + type UseWatchContractEventParameters, + useWatchContractEvent, +} from './useWatchContractEvent.js' + +test('default', () => { + useWatchContractEvent({ + address: '0x', + abi: abi.erc20, + eventName: 'Transfer', + poll: false, + args: { + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) + +test('behavior: no eventName', () => { + useWatchContractEvent({ + address: '0x', + abi: abi.erc20, + args: { + // TODO: Figure out why this is not working + // @ts-ignore + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer' | 'Approval'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf< + | Record + | readonly unknown[] + | { + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + } + | { + owner?: `0x${string}` | undefined + spender?: `0x${string}` | undefined + value?: bigint | undefined + } + >() + }, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = UseWatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + type Result2 = UseWatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + chainId: mainnet.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + type Result3 = UseWatchContractEventParameters< + typeof abi.erc20, + 'Transfer' | 'Approval', + true, + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + chainId: optimism.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + useWatchContractEvent({ + config, + chainId: optimism.id, + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) +}) diff --git a/packages/react/src/hooks/useWatchContractEvent.test.ts b/packages/react/src/hooks/useWatchContractEvent.test.ts new file mode 100644 index 0000000000..e6ad2aa1dc --- /dev/null +++ b/packages/react/src/hooks/useWatchContractEvent.test.ts @@ -0,0 +1,86 @@ +import { connect, disconnect, getBalance, writeContract } from '@wagmi/core' +import { + abi, + accounts, + address, + config, + testClient, + transactionHashRegex, + wait, +} from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { http, createWalletClient, parseEther } from 'viem' +import type { WatchEventOnLogsParameter } from 'viem/actions' +import { expect, test } from 'vitest' + +import { useWatchContractEvent } from './useWatchContractEvent.js' + +const connector = config.connectors[0]! + +test('default', async () => { + const data = await connect(config, { connector }) + const connectedAddress = data.accounts[0] + + // impersonate usdc holder account and transfer usdc to connected account + await testClient.mainnet.impersonateAccount({ address: address.usdcHolder }) + await testClient.mainnet.setBalance({ + address: address.usdcHolder, + value: 10000000000000000000000n, + }) + await createWalletClient({ + account: address.usdcHolder, + chain: testClient.mainnet.chain, + transport: http(), + }).writeContract({ + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [connectedAddress, parseEther('10', 'gwei')], + }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet.stopImpersonatingAccount({ + address: address.usdcHolder, + }) + + const balance = await getBalance(config, { + address: connectedAddress, + token: address.usdc, + }) + expect(balance.value).toBeGreaterThan(0n) + + // start watching transfer events + let logs: WatchEventOnLogsParameter = [] + renderHook(() => + useWatchContractEvent({ + address: address.usdc, + abi: abi.erc20, + eventName: 'Transfer', + onLogs(next) { + logs = logs.concat(next) + }, + }), + ) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[1], parseEther('1', 'gwei')], + }) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[3], parseEther('1', 'gwei')], + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(1000) // wait for events to be emitted + + expect(logs.length).toBe(2) + expect(logs[0]?.transactionHash).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useWatchContractEvent.ts b/packages/react/src/hooks/useWatchContractEvent.ts new file mode 100644 index 0000000000..0d7710df2a --- /dev/null +++ b/packages/react/src/hooks/useWatchContractEvent.ts @@ -0,0 +1,85 @@ +'use client' + +import { + type Config, + type ResolvedRegister, + type WatchContractEventParameters, + watchContractEvent, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import { useEffect } from 'react' +import type { Abi, ContractEventName } from 'viem' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchContractEventParameters< + abi extends Abi | readonly unknown[] = Abi, + eventName extends ContractEventName = ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = UnionCompute< + UnionExactPartial< + WatchContractEventParameters + > & + ConfigParameter & + EnabledParameter +> + +export type UseWatchContractEventReturnType = void + +/** https://wagmi.sh/react/api/hooks/useWatchContractEvent */ +export function useWatchContractEvent< + const abi extends Abi | readonly unknown[], + eventName extends ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters: UseWatchContractEventParameters< + abi, + eventName, + strict, + config, + chainId + > = {} as any, +): UseWatchContractEventReturnType { + const { enabled = true, onLogs, config: _, ...rest } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + // TODO(react@19): cleanup + // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` changes every render so only including properties in dependency array + useEffect(() => { + if (!enabled) return + if (!onLogs) return + return watchContractEvent(config, { + ...(rest as any), + chainId, + onLogs, + }) + }, [ + chainId, + config, + enabled, + onLogs, + /// + rest.abi, + rest.address, + rest.args, + rest.batch, + rest.eventName, + rest.fromBlock, + rest.onError, + rest.poll, + rest.pollingInterval, + rest.strict, + rest.syncConnectedChain, + ]) +} diff --git a/packages/react/src/hooks/useWatchPendingTransactions.test-d.ts b/packages/react/src/hooks/useWatchPendingTransactions.test-d.ts new file mode 100644 index 0000000000..56ccc49cbf --- /dev/null +++ b/packages/react/src/hooks/useWatchPendingTransactions.test-d.ts @@ -0,0 +1,67 @@ +import { createConfig } from '@wagmi/core' +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import { + type UseWatchPendingTransactionsParameters, + useWatchPendingTransactions, +} from './useWatchPendingTransactions.js' + +test('default', () => { + useWatchPendingTransactions({ + poll: false, + onTransactions() {}, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = UseWatchPendingTransactionsParameters< + typeof config, + typeof mainnet.id | typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchPendingTransactions({ + config, + poll: false, + onTransactions() {}, + }) + + type Result2 = UseWatchPendingTransactionsParameters< + typeof config, + typeof mainnet.id + > + expectTypeOf().toEqualTypeOf() + useWatchPendingTransactions({ + config, + chainId: mainnet.id, + poll: true, + onTransactions() {}, + }) + + type Result3 = UseWatchPendingTransactionsParameters< + typeof config, + typeof optimism.id + > + expectTypeOf().toEqualTypeOf() + useWatchPendingTransactions({ + config, + chainId: optimism.id, + poll: true, + onTransactions() {}, + }) + useWatchPendingTransactions({ + config, + chainId: optimism.id, + poll: false, + onTransactions() {}, + }) +}) diff --git a/packages/react/src/hooks/useWatchPendingTransactions.test.ts b/packages/react/src/hooks/useWatchPendingTransactions.test.ts new file mode 100644 index 0000000000..e1981f3fa1 --- /dev/null +++ b/packages/react/src/hooks/useWatchPendingTransactions.test.ts @@ -0,0 +1,49 @@ +import { connect, disconnect, sendTransaction } from '@wagmi/core' +import { + accounts, + config, + testClient, + transactionHashRegex, + wait, +} from '@wagmi/test' +import { renderHook } from '@wagmi/test/react' +import { parseEther } from 'viem' +import type { OnTransactionsParameter } from 'viem/actions' +import { expect, test } from 'vitest' + +import { useWatchPendingTransactions } from './useWatchPendingTransactions.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + let transactions: OnTransactionsParameter = [] + renderHook(() => + useWatchPendingTransactions({ + onTransactions(next) { + transactions = [...transactions, ...next] + }, + }), + ) + await wait(1000) + + await sendTransaction(config, { + to: accounts[1], + value: parseEther('1'), + }) + await wait(200) + + await sendTransaction(config, { + to: accounts[3], + value: parseEther('1'), + }) + await wait(200) + + await testClient.mainnet.mine({ blocks: 1 }) + + expect(transactions.length).toBe(2) + expect(transactions[0]).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useWatchPendingTransactions.ts b/packages/react/src/hooks/useWatchPendingTransactions.ts new file mode 100644 index 0000000000..7461ffcfb4 --- /dev/null +++ b/packages/react/src/hooks/useWatchPendingTransactions.ts @@ -0,0 +1,67 @@ +'use client' + +import { + type Config, + type ResolvedRegister, + type WatchPendingTransactionsParameters, + watchPendingTransactions, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import { useEffect } from 'react' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchPendingTransactionsParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = UnionCompute< + UnionExactPartial> & + ConfigParameter & + EnabledParameter +> + +export type UseWatchPendingTransactionsReturnType = void + +/** https://wagmi.sh/react/api/hooks/useWatchPendingTransactions */ +export function useWatchPendingTransactions< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters: UseWatchPendingTransactionsParameters< + config, + chainId + > = {} as any, +): UseWatchPendingTransactionsReturnType { + const { enabled = true, onTransactions, config: _, ...rest } = parameters + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + const chainId = parameters.chainId ?? configChainId + + // TODO(react@19): cleanup + // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` changes every render so only including properties in dependency array + useEffect(() => { + if (!enabled) return + if (!onTransactions) return + return watchPendingTransactions(config, { + ...(rest as any), + chainId, + onTransactions, + }) + }, [ + chainId, + config, + enabled, + onTransactions, + /// + rest.batch, + rest.onError, + rest.poll, + rest.pollingInterval, + rest.syncConnectedChain, + ]) +} diff --git a/packages/react/src/hooks/useWriteContract.test-d.ts b/packages/react/src/hooks/useWriteContract.test-d.ts new file mode 100644 index 0000000000..344c11255b --- /dev/null +++ b/packages/react/src/hooks/useWriteContract.test-d.ts @@ -0,0 +1,185 @@ +import { http, type WriteContractErrorType, createConfig } from '@wagmi/core' +import { base } from '@wagmi/core/chains' +import { abi } from '@wagmi/test' +import type { Abi, Address, Hash } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useSimulateContract } from './useSimulateContract.js' +import { useWriteContract } from './useWriteContract.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { + context, + data, + error, + writeContract: write, + variables, + } = useWriteContract({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + }, + }) + + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + + write( + { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }, + { + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables.functionName).toEqualTypeOf<'transferFrom'>() + expectTypeOf(variables.args).toEqualTypeOf< + readonly [Address, Address, bigint] + >() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + }, + ) +}) + +test('useSimulateContract', () => { + const { data } = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + const { writeContract } = useWriteContract() + + const request = data?.request + if (request) writeContract(request) +}) + +// https://github.com/wevm/wagmi/issues/3981 +test('gh#3981', () => { + const config = createConfig({ + chains: [base], + transports: { + [base.id]: http(), + }, + }) + + const abi = [ + { + type: 'function', + name: 'example1', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'payable', + }, + { + type: 'function', + name: 'example2', + inputs: [ + { name: 'exampleName', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ] as const + + const { writeContract } = useWriteContract({ config }) + writeContract({ + abi, + address: '0x...', + functionName: 'example1', + args: ['0x...'], + value: 123n, + }) + writeContract({ + abi, + address: '0x...', + functionName: 'example2', + args: ['0x...'], + // @ts-expect-error + value: 123n, + }) +}) diff --git a/packages/react/src/hooks/useWriteContract.test.ts b/packages/react/src/hooks/useWriteContract.test.ts new file mode 100644 index 0000000000..16ef870c2b --- /dev/null +++ b/packages/react/src/hooks/useWriteContract.test.ts @@ -0,0 +1,25 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config } from '@wagmi/test' +import { renderHook, waitFor } from '@wagmi/test/react' +import { expect, test } from 'vitest' + +import { useWriteContract } from './useWriteContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const { result } = renderHook(() => useWriteContract()) + + result.current.writeContract({ + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }) + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()) + + expect(result.current.data).toBeDefined() + + await disconnect(config, { connector }) +}) diff --git a/packages/react/src/hooks/useWriteContract.ts b/packages/react/src/hooks/useWriteContract.ts new file mode 100644 index 0000000000..b07e856c93 --- /dev/null +++ b/packages/react/src/hooks/useWriteContract.ts @@ -0,0 +1,87 @@ +'use client' + +import { useMutation } from '@tanstack/react-query' +import type { + Config, + ResolvedRegister, + WriteContractErrorType, +} from '@wagmi/core' +import { + type WriteContractData, + type WriteContractMutate, + type WriteContractMutateAsync, + type WriteContractVariables, + writeContractMutationOptions, +} from '@wagmi/core/query' +import type { Abi } from 'viem' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseWriteContractParameters< + config extends Config = Config, + context = unknown, +> = ConfigParameter & { + mutation?: + | UseMutationParameters< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context + > + | undefined +} + +export type UseWriteContractReturnType< + config extends Config = Config, + context = unknown, +> = UseMutationReturnType< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context +> & { + writeContract: WriteContractMutate + writeContractAsync: WriteContractMutateAsync +} + +/** https://wagmi.sh/react/api/hooks/useWriteContract */ +export function useWriteContract< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteContractParameters = {}, +): UseWriteContractReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = writeContractMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseWriteContractReturnType + return { + ...result, + writeContract: mutate as Return['writeContract'], + writeContractAsync: mutateAsync as Return['writeContractAsync'], + } +} diff --git a/packages/react/src/hydrate.ts b/packages/react/src/hydrate.ts new file mode 100644 index 0000000000..b185929c22 --- /dev/null +++ b/packages/react/src/hydrate.ts @@ -0,0 +1,36 @@ +'use client' + +import { type ResolvedRegister, type State, hydrate } from '@wagmi/core' +import { type ReactElement, useEffect, useRef } from 'react' + +export type HydrateProps = { + config: ResolvedRegister['config'] + initialState?: State | undefined + reconnectOnMount?: boolean | undefined +} + +export function Hydrate(parameters: React.PropsWithChildren) { + const { children, config, initialState, reconnectOnMount = true } = parameters + + const { onMount } = hydrate(config, { + initialState, + reconnectOnMount, + }) + + // Hydrate for non-SSR + if (!config._internal.ssr) onMount() + + // Hydrate for SSR + const active = useRef(true) + // biome-ignore lint/correctness/useExhaustiveDependencies: `queryKey` not required + useEffect(() => { + if (!active.current) return + if (!config._internal.ssr) return + onMount() + return () => { + active.current = false + } + }, []) + + return children as ReactElement +} diff --git a/packages/react/src/types/properties.ts b/packages/react/src/types/properties.ts new file mode 100644 index 0000000000..7d903faf0a --- /dev/null +++ b/packages/react/src/types/properties.ts @@ -0,0 +1,51 @@ +import type { DefaultError, QueryKey } from '@tanstack/react-query' +import type { Config } from '@wagmi/core' +import type { Omit } from '@wagmi/core/internal' + +import type { + UseInfiniteQueryParameters, + UseQueryParameters, +} from '../utils/query.js' + +export type EnabledParameter = { + enabled?: boolean | undefined +} + +export type ConfigParameter = { + config?: Config | config | undefined +} + +export type QueryParameter< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = { + query?: + | Omit< + UseQueryParameters, + 'queryFn' | 'queryHash' | 'queryKey' | 'queryKeyHashFn' | 'throwOnError' + > + | undefined +} + +export type InfiniteQueryParameter< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryData = queryFnData, + queryKey extends QueryKey = QueryKey, + pageParam = unknown, +> = { + query: Omit< + UseInfiniteQueryParameters< + queryFnData, + error, + data, + queryData, + queryKey, + pageParam + >, + 'queryFn' | 'queryHash' | 'queryKey' | 'queryKeyHashFn' | 'throwOnError' + > +} diff --git a/packages/react/src/utils/getVersion.test.ts b/packages/react/src/utils/getVersion.test.ts new file mode 100644 index 0000000000..e8368facea --- /dev/null +++ b/packages/react/src/utils/getVersion.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +import { getVersion } from './getVersion.js' + +test('default', () => { + expect(getVersion()).toMatchInlineSnapshot(`"wagmi@x.y.z"`) +}) diff --git a/packages/react/src/utils/getVersion.ts b/packages/react/src/utils/getVersion.ts new file mode 100644 index 0000000000..1a8a9437b3 --- /dev/null +++ b/packages/react/src/utils/getVersion.ts @@ -0,0 +1,3 @@ +import { version } from '../version.js' + +export const getVersion = () => `wagmi@${version}` diff --git a/packages/react/src/utils/query.ts b/packages/react/src/utils/query.ts new file mode 100644 index 0000000000..3aafb16123 --- /dev/null +++ b/packages/react/src/utils/query.ts @@ -0,0 +1,145 @@ +import { + type DefaultError, + type QueryKey, + type UseInfiniteQueryOptions, + type UseInfiniteQueryResult, + type UseMutationOptions, + type UseMutationResult, + type UseQueryOptions, + type UseQueryResult, + useInfiniteQuery as tanstack_useInfiniteQuery, + useQuery as tanstack_useQuery, + useMutation, +} from '@tanstack/react-query' +import type { + Compute, + ExactPartial, + Omit, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { hashFn } from '@wagmi/core/query' + +export type UseMutationParameters< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + Omit< + UseMutationOptions, context>, + 'mutationFn' | 'mutationKey' | 'throwOnError' + > +> + +export type UseMutationReturnType< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + UnionStrictOmit< + UseMutationResult, + 'mutate' | 'mutateAsync' + > +> + +export { useMutation } + +//////////////////////////////////////////////////////////////////////////////// + +export type UseQueryParameters< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = Compute< + ExactPartial< + Omit, 'initialData'> + > & { + // Fix `initialData` type + initialData?: + | UseQueryOptions['initialData'] + | undefined + } +> + +export type UseQueryReturnType = Compute< + UseQueryResult & { + queryKey: QueryKey + } +> + +// Adding some basic customization. +// Ideally we don't have this function, but `import('@tanstack/react-query').useQuery` currently has some quirks where it is super hard to +// pass down the inferred `initialData` type because of it's discriminated overload in the on `useQuery`. +export function useQuery( + parameters: UseQueryParameters & { + queryKey: QueryKey + }, +): UseQueryReturnType { + const result = tanstack_useQuery({ + ...(parameters as any), + queryKeyHashFn: hashFn, // for bigint support + }) as UseQueryReturnType + result.queryKey = parameters.queryKey + return result +} + +//////////////////////////////////////////////////////////////////////////////// + +export type UseInfiniteQueryParameters< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryData = queryFnData, + queryKey extends QueryKey = QueryKey, + pageParam = unknown, +> = Compute< + Omit< + UseInfiniteQueryOptions< + queryFnData, + error, + data, + queryData, + queryKey, + pageParam + >, + 'initialData' + > & { + // Fix `initialData` type + initialData?: + | UseInfiniteQueryOptions< + queryFnData, + error, + data, + queryKey + >['initialData'] + | undefined + } +> + +export type UseInfiniteQueryReturnType< + data = unknown, + error = DefaultError, +> = UseInfiniteQueryResult & { + queryKey: QueryKey +} + +// Adding some basic customization. +export function useInfiniteQuery< + queryFnData, + error, + data, + queryKey extends QueryKey, +>( + parameters: UseInfiniteQueryParameters & { + queryKey: QueryKey + }, +): UseInfiniteQueryReturnType { + const result = tanstack_useInfiniteQuery({ + ...(parameters as any), + queryKeyHashFn: hashFn, // for bigint support + }) as UseInfiniteQueryReturnType + result.queryKey = parameters.queryKey + return result +} diff --git a/packages/react/src/version.ts b/packages/react/src/version.ts new file mode 100644 index 0000000000..0ec209c466 --- /dev/null +++ b/packages/react/src/version.ts @@ -0,0 +1 @@ +export const version = '2.15.4' diff --git a/packages/react/test/setup.ts b/packages/react/test/setup.ts new file mode 100644 index 0000000000..5c0dcc071d --- /dev/null +++ b/packages/react/test/setup.ts @@ -0,0 +1,8 @@ +import { vi } from 'vitest' + +// Make dates stable across runs +Date.now = vi.fn(() => new Date(Date.UTC(2023, 1, 1)).valueOf()) + +vi.mock('../src/version.ts', () => { + return { version: 'x.y.z' } +}) diff --git a/packages/react/tsconfig.build.json b/packages/react/tsconfig.build.json new file mode 100644 index 0000000000..fbed2b1036 --- /dev/null +++ b/packages/react/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000000..1b247fdd49 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "jsx": "preserve" + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx"], + "exclude": [] +} diff --git a/packages/register-tests/react/package.json b/packages/register-tests/react/package.json new file mode 100644 index 0000000000..769092f959 --- /dev/null +++ b/packages/register-tests/react/package.json @@ -0,0 +1,16 @@ +{ + "name": "react-register", + "private": true, + "type": "module", + "scripts": { + "check:types": "tsc --noEmit" + }, + "dependencies": { + "@tanstack/react-query": "catalog:", + "react": "catalog:", + "wagmi": "workspace:*" + }, + "devDependencies": { + "@types/react": "catalog:" + } +} diff --git a/packages/register-tests/react/src/config.ts b/packages/register-tests/react/src/config.ts new file mode 100644 index 0000000000..5772212c1b --- /dev/null +++ b/packages/register-tests/react/src/config.ts @@ -0,0 +1,26 @@ +import { http } from 'viem' +import { createConfig, mock } from 'wagmi' +import { celo, mainnet, optimism, zkSync } from 'wagmi/chains' + +export const config = createConfig({ + chains: [celo, mainnet, optimism, zkSync], + connectors: [mock({ accounts: ['0x'] })], + transports: { + [celo.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zkSync.id]: http(), + }, +}) + +export type ChainId = + | typeof celo.id + | typeof mainnet.id + | typeof optimism.id + | typeof zkSync.id + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} diff --git a/packages/register-tests/react/src/createUseSimulateContract.test-d.ts b/packages/register-tests/react/src/createUseSimulateContract.test-d.ts new file mode 100644 index 0000000000..b469ccd024 --- /dev/null +++ b/packages/register-tests/react/src/createUseSimulateContract.test-d.ts @@ -0,0 +1,39 @@ +import { abi, config as testConfig } from '@wagmi/test' +import { test } from 'vitest' +import { celo, mainnet, optimism } from 'wagmi/chains' +import { createUseSimulateContract } from 'wagmi/codegen' + +const useSimulateErc20 = createUseSimulateContract({ + abi: abi.erc20, +}) + +test('chain formatters', () => { + useSimulateErc20({ + feeCurrency: '0x', + }) + + useSimulateErc20({ + chainId: celo.id, + feeCurrency: '0x', + }) + + useSimulateErc20({ + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + useSimulateErc20({ + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + useSimulateErc20({ + config: testConfig, + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/src/createUseWriteContract.test-d.ts b/packages/register-tests/react/src/createUseWriteContract.test-d.ts new file mode 100644 index 0000000000..0b6c538130 --- /dev/null +++ b/packages/register-tests/react/src/createUseWriteContract.test-d.ts @@ -0,0 +1,66 @@ +import { abi, config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { celo, mainnet, optimism } from 'wagmi/chains' +import { createUseWriteContract } from 'wagmi/codegen' + +const useWriteErc20 = createUseWriteContract({ + abi: abi.erc20, +}) + +test('chain formatters', () => { + const { writeContract } = useWriteErc20() + const shared = { + address: '0x', + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + } as const + + writeContract({ + ...shared, + feeCurrency: '0x', + }) + + type Result = Parameters< + typeof writeContract< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof celo.id + > + >[0] + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + writeContract({ + ...shared, + chainId: celo.id, + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + const { writeContract } = useWriteErc20({ config }) + + writeContract({ + address: '0x', + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/src/useAccount.test-d.ts b/packages/register-tests/react/src/useAccount.test-d.ts new file mode 100644 index 0000000000..be563933a6 --- /dev/null +++ b/packages/register-tests/react/src/useAccount.test-d.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { useAccount } from 'wagmi' + +import type { ChainId } from './config.js' + +test('default', () => { + const result = useAccount() + if (result.chain) expectTypeOf(result.chain.id).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useAccount({ config }) + if (result.chain) expectTypeOf(result.chain.id).toEqualTypeOf<1 | 10 | 456>() +}) diff --git a/packages/register-tests/react/src/useBlock.test-d.ts b/packages/register-tests/react/src/useBlock.test-d.ts new file mode 100644 index 0000000000..5f583a63e6 --- /dev/null +++ b/packages/register-tests/react/src/useBlock.test-d.ts @@ -0,0 +1,27 @@ +import type { Hex } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { useBlock } from 'wagmi' +import { celo } from 'wagmi/chains' + +test('chain formatters', () => { + const result = useBlock() + + if (result.data) expectTypeOf(result.data.difficulty).toEqualTypeOf() + + if (result.data?.chainId === celo.id) { + expectTypeOf(result.data.difficulty).toEqualTypeOf() + expectTypeOf(result.data.gasLimit).toEqualTypeOf() + expectTypeOf(result.data.mixHash).toEqualTypeOf() + expectTypeOf(result.data.nonce).toEqualTypeOf<`0x${string}`>() + expectTypeOf(result.data.uncles).toEqualTypeOf() + } + + const result2 = useBlock({ chainId: celo.id }) + if (result2.data) { + expectTypeOf(result2.data.difficulty).toEqualTypeOf() + expectTypeOf(result2.data.gasLimit).toEqualTypeOf() + expectTypeOf(result2.data.mixHash).toEqualTypeOf() + expectTypeOf(result2.data.nonce).toEqualTypeOf<`0x${string}`>() + expectTypeOf(result2.data.uncles).toEqualTypeOf() + } +}) diff --git a/packages/register-tests/react/src/useChainId.test-d.ts b/packages/register-tests/react/src/useChainId.test-d.ts new file mode 100644 index 0000000000..d1a8e035d6 --- /dev/null +++ b/packages/register-tests/react/src/useChainId.test-d.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { useChainId } from 'wagmi' + +import type { ChainId } from './config.js' + +test('default', async () => { + const chainId = useChainId() + expectTypeOf(chainId).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const chainId = useChainId({ config }) + expectTypeOf(chainId).toEqualTypeOf<1 | 456 | 10>() +}) diff --git a/packages/register-tests/react/src/useChains.test-d.ts b/packages/register-tests/react/src/useChains.test-d.ts new file mode 100644 index 0000000000..c40fd5e1d2 --- /dev/null +++ b/packages/register-tests/react/src/useChains.test-d.ts @@ -0,0 +1,11 @@ +import { expectTypeOf, test } from 'vitest' +import { useChains } from 'wagmi' +import type { Chain, celo, optimism } from 'wagmi/chains' + +test('default', () => { + const chains = useChains() + + expectTypeOf(chains[0]).toEqualTypeOf() + expectTypeOf(chains[2]).toEqualTypeOf() + expectTypeOf(chains[5]).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useClient.test-d.ts b/packages/register-tests/react/src/useClient.test-d.ts new file mode 100644 index 0000000000..0ce810b31c --- /dev/null +++ b/packages/register-tests/react/src/useClient.test-d.ts @@ -0,0 +1,38 @@ +import { type chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { useClient } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +import type { ChainId } from './config.js' + +test('default', () => { + const client = useClient() + expectTypeOf(client.chain.id).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: config', () => { + const client = useClient({ config }) + expectTypeOf(client.chain.id).toEqualTypeOf< + (typeof config)['chains'][number]['id'] + >() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = useClient({ + config, + chainId: mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + const client = useClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useConfig.test-d.ts b/packages/register-tests/react/src/useConfig.test-d.ts new file mode 100644 index 0000000000..25eb5246de --- /dev/null +++ b/packages/register-tests/react/src/useConfig.test-d.ts @@ -0,0 +1,17 @@ +import { config as testConfig } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { type Config, useConfig } from 'wagmi' + +import type { config } from './config.js' + +test('default', async () => { + const result = useConfig() + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useConfig({ config: testConfig }) + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useConnect.test-d.ts b/packages/register-tests/react/src/useConnect.test-d.ts new file mode 100644 index 0000000000..2386dca7e9 --- /dev/null +++ b/packages/register-tests/react/src/useConnect.test-d.ts @@ -0,0 +1,13 @@ +import { expectTypeOf, test } from 'vitest' +import { useConnect } from 'wagmi' + +test('infers connect parameters', () => { + const { connect, connectors, variables } = useConnect() + const connector = connectors[0]! + + expectTypeOf(variables?.foo).toEqualTypeOf() + connect({ + connector, + foo: 'bar', + }) +}) diff --git a/packages/register-tests/react/src/usePrepareTransactionRequest.test-d.ts b/packages/register-tests/react/src/usePrepareTransactionRequest.test-d.ts new file mode 100644 index 0000000000..460408515c --- /dev/null +++ b/packages/register-tests/react/src/usePrepareTransactionRequest.test-d.ts @@ -0,0 +1,42 @@ +import { config as testConfig } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { usePrepareTransactionRequest } from 'wagmi' +import { celo, mainnet, optimism } from 'wagmi/chains' + +test('chain formatters', () => { + const { data } = usePrepareTransactionRequest({ + feeCurrency: '0x', + }) + if (data && data.chainId === celo.id) { + expectTypeOf(data.feeCurrency).toEqualTypeOf<`0x${string}` | undefined>() + } + + const { data: data2 } = usePrepareTransactionRequest({ + chainId: celo.id, + feeCurrency: '0x', + }) + if (data2) { + expectTypeOf(data2.chainId).toEqualTypeOf(celo.id) + expectTypeOf(data2.feeCurrency).toEqualTypeOf<`0x${string}` | undefined>() + } + + usePrepareTransactionRequest({ + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + usePrepareTransactionRequest({ + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + usePrepareTransactionRequest({ + config: testConfig, + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/src/usePublicClient.ts b/packages/register-tests/react/src/usePublicClient.ts new file mode 100644 index 0000000000..55c6967ab2 --- /dev/null +++ b/packages/register-tests/react/src/usePublicClient.ts @@ -0,0 +1,38 @@ +import { type chain, config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { usePublicClient } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +import type { ChainId } from './config.js' + +test('default', () => { + const client = usePublicClient() + expectTypeOf(client.chain.id).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: config', () => { + const client = usePublicClient({ config }) + expectTypeOf(client.chain.id).toEqualTypeOf< + (typeof config)['chains'][number]['id'] + >() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = usePublicClient({ + config, + chainId: mainnet.id, + }) + expectTypeOf(client.chain).toEqualTypeOf() + expectTypeOf(client.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + const client = usePublicClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useReadContract.test-d.ts b/packages/register-tests/react/src/useReadContract.test-d.ts new file mode 100644 index 0000000000..02ca83ed64 --- /dev/null +++ b/packages/register-tests/react/src/useReadContract.test-d.ts @@ -0,0 +1,24 @@ +import type { abi } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' +import type { useReadContract } from 'wagmi' + +import type { ChainId } from './config.js' + +test('UseReadContractParameters', () => { + type Result = NonNullable< + Parameters>[0] + > + expectTypeOf().toMatchTypeOf<{ + functionName?: + | 'symbol' + | 'name' + | 'allowance' + | 'balanceOf' + | 'decimals' + | 'totalSupply' + | undefined + args?: readonly [Address] | undefined + chainId?: ChainId | undefined + }>() +}) diff --git a/packages/register-tests/react/src/useReadContracts.test-d.ts b/packages/register-tests/react/src/useReadContracts.test-d.ts new file mode 100644 index 0000000000..bb719b7c49 --- /dev/null +++ b/packages/register-tests/react/src/useReadContracts.test-d.ts @@ -0,0 +1,45 @@ +import type { abi } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' +import type { useReadContracts } from 'wagmi' + +import type { ChainId } from './config.js' + +test('UseReadContractsParameters', () => { + type Result = NonNullable< + Parameters< + typeof useReadContracts< + [ + { + abi: typeof abi.erc20 + functionName: 'balanceOf' + address: Address + args: readonly [Address] + }, + ] + > + >[0] + >['contracts'] + expectTypeOf().toMatchTypeOf< + | readonly [ + { + abi?: typeof abi.erc20 | undefined + functionName?: + | 'approve' + | 'symbol' + | 'name' + | 'allowance' + | 'balanceOf' + | 'decimals' + | 'totalSupply' + | 'transfer' + | 'transferFrom' + | undefined + address?: Address | undefined + args?: readonly [Address] | undefined + chainId?: ChainId | undefined + }, + ] + | undefined + >() +}) diff --git a/packages/register-tests/react/src/useSendTransaction.test-d.ts b/packages/register-tests/react/src/useSendTransaction.test-d.ts new file mode 100644 index 0000000000..60839f5023 --- /dev/null +++ b/packages/register-tests/react/src/useSendTransaction.test-d.ts @@ -0,0 +1,55 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { useSendTransaction } from 'wagmi' +import { celo, mainnet, optimism } from 'wagmi/chains' +import type { ChainId } from './config.js' + +test('chain formatters', () => { + const { sendTransaction } = useSendTransaction() + + sendTransaction( + { + to: '0x', + feeCurrency: '0x', + }, + { + onSuccess(_data, variables) { + expectTypeOf(variables.chainId).toEqualTypeOf() + }, + }, + ) + + type Result = Parameters>[0] + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + sendTransaction({ + chainId: celo.id, + to: '0x', + feeCurrency: '0x', + }) + + sendTransaction({ + chainId: mainnet.id, + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) + + sendTransaction({ + chainId: optimism.id, + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + const { sendTransaction } = useSendTransaction({ config }) + + sendTransaction({ + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/src/useSimulateContract.test-d.ts b/packages/register-tests/react/src/useSimulateContract.test-d.ts new file mode 100644 index 0000000000..96c75d76e8 --- /dev/null +++ b/packages/register-tests/react/src/useSimulateContract.test-d.ts @@ -0,0 +1,94 @@ +import { type abi, config as testConfig } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { type UseSimulateContractParameters, useSimulateContract } from 'wagmi' +import type { SimulateContractParameters } from 'wagmi/actions' +import { celo, mainnet, optimism } from 'wagmi/chains' +import type { SimulateContractOptions } from 'wagmi/query' + +import type { ChainId, config } from './config.js' + +test('chain formatters', () => { + const { data } = useSimulateContract({ + feeCurrency: '0x', + }) + if (data && data.chainId === celo.id) { + expectTypeOf(data.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + const { data: data2 } = useSimulateContract({ + chainId: celo.id, + feeCurrency: '0x', + }) + if (data2) { + expectTypeOf(data2.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + useSimulateContract({ + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + useSimulateContract({ + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('UseSimulateContractParameters', () => { + type Result = UseSimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config + > + + expectTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + chainId?: ChainId | undefined + }>().toMatchTypeOf() + + type Result2 = UseSimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + + type Result3 = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toEqualTypeOf() + type Result4 = SimulateContractOptions< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toEqualTypeOf() +}) + +test('parameters: config', async () => { + useSimulateContract({ + config: testConfig, + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/src/useSwitchChain.test-d.ts b/packages/register-tests/react/src/useSwitchChain.test-d.ts new file mode 100644 index 0000000000..2d02557b85 --- /dev/null +++ b/packages/register-tests/react/src/useSwitchChain.test-d.ts @@ -0,0 +1,25 @@ +import { expectTypeOf, test } from 'vitest' +import { useSwitchChain } from 'wagmi' +import { type celo, mainnet, type optimism } from 'wagmi/chains' + +import type { ChainId, config } from './config.js' + +test('default', () => { + const { chains, switchChain } = useSwitchChain() + + expectTypeOf(chains).toEqualTypeOf<(typeof config)['chains']>() + expectTypeOf(chains[0]).toEqualTypeOf() + expectTypeOf(chains[2]).toEqualTypeOf() + + switchChain( + { chainId: 1 }, + { + onSuccess(data) { + expectTypeOf(data).toEqualTypeOf(mainnet) + }, + }, + ) + + type Result = Parameters[0] + expectTypeOf().toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useTransaction.test-d.ts b/packages/register-tests/react/src/useTransaction.test-d.ts new file mode 100644 index 0000000000..135b99bb2d --- /dev/null +++ b/packages/register-tests/react/src/useTransaction.test-d.ts @@ -0,0 +1,23 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' +import { useTransaction } from 'wagmi' +import { celo } from 'wagmi/chains' + +test('chain formatters', () => { + const result = useTransaction() + if (result.data?.chainId === celo.id) { + expectTypeOf(result.data.feeCurrency).toEqualTypeOf<`0x${string}` | null>() + } + + const result2 = useTransaction({ chainId: celo.id }) + expectTypeOf(result2.data?.feeCurrency).toEqualTypeOf< + `0x${string}` | null | undefined + >() +}) + +test('parameters: config', async () => { + const result = useTransaction({ config }) + + if (result.data && 'feeCurrency' in result.data) + expectTypeOf(result.data.feeCurrency).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useTransactionConfirmations.test-d.ts b/packages/register-tests/react/src/useTransactionConfirmations.test-d.ts new file mode 100644 index 0000000000..fe2445db21 --- /dev/null +++ b/packages/register-tests/react/src/useTransactionConfirmations.test-d.ts @@ -0,0 +1,66 @@ +import { config as testConfig } from '@wagmi/test' +import { test } from 'vitest' +import { useTransactionConfirmations } from 'wagmi' +import { mainnet, zkSync } from 'wagmi/chains' + +const transactionReceipt = { + blockHash: '0x', + blockNumber: 1n, + contractAddress: '0x', + cumulativeGasUsed: 1n, + effectiveGasPrice: 1n, + from: '0x', + gasUsed: 1n, + logsBloom: '0x', + status: 'success', + to: '0x', + transactionHash: '0x', + transactionIndex: 1, + type: 'eip1559', +} as const + +test('chain formatters', async () => { + useTransactionConfirmations({ + transactionReceipt: { + ...transactionReceipt, + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) + + useTransactionConfirmations({ + chainId: zkSync.id, + transactionReceipt: { + ...transactionReceipt, + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) + + useTransactionConfirmations({ + chainId: mainnet.id, + transactionReceipt: { + ...transactionReceipt, + // @ts-expect-error + l1BatchNumber: 1n, + l1BatchTxIndex: 1n, + logs: [], + l2ToL1Logs: [], + }, + }) +}) + +test('parameters: config', async () => { + useTransactionConfirmations({ + config: testConfig, + transactionReceipt: { + ...transactionReceipt, + // @ts-expect-error + l1BatchNumber: 1n, + }, + }) +}) diff --git a/packages/register-tests/react/src/useTransactionReceipt.test-d.ts b/packages/register-tests/react/src/useTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..1bbd8183da --- /dev/null +++ b/packages/register-tests/react/src/useTransactionReceipt.test-d.ts @@ -0,0 +1,31 @@ +import { config } from '@wagmi/test' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' +import { useTransactionReceipt } from 'wagmi' +import { zkSync } from 'wagmi/chains' + +test('chain formatters', () => { + const result = useTransactionReceipt() + + if (result.data?.chainId === zkSync.id) { + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.data.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.data.logs).toEqualTypeOf() + expectTypeOf(result.data.l2ToL1Logs).toEqualTypeOf() + } + + const result2 = useTransactionReceipt({ chainId: zkSync.id }) + if (result2.data) { + expectTypeOf(result2.data.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result2.data.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result2.data.logs).toEqualTypeOf() + expectTypeOf(result2.data.l2ToL1Logs).toEqualTypeOf() + } +}) + +test('parameters: config', async () => { + const result = useTransactionReceipt({ config }) + + if (result.data && 'l1BatchNumber' in result.data) + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useWaitForTransactionReceipt.ts b/packages/register-tests/react/src/useWaitForTransactionReceipt.ts new file mode 100644 index 0000000000..0d2501f500 --- /dev/null +++ b/packages/register-tests/react/src/useWaitForTransactionReceipt.ts @@ -0,0 +1,31 @@ +import { config } from '@wagmi/test' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' +import { useWaitForTransactionReceipt } from 'wagmi' +import { zkSync } from 'wagmi/chains' + +test('chain formatters', () => { + const result = useWaitForTransactionReceipt() + + if (result.data?.chainId === zkSync.id) { + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.data.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result.data.logs).toEqualTypeOf() + expectTypeOf(result.data.l2ToL1Logs).toEqualTypeOf() + } + + const result2 = useWaitForTransactionReceipt({ chainId: zkSync.id }) + if (result2.data) { + expectTypeOf(result2.data.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result2.data.l1BatchTxIndex).toEqualTypeOf() + expectTypeOf(result2.data.logs).toEqualTypeOf() + expectTypeOf(result2.data.l2ToL1Logs).toEqualTypeOf() + } +}) + +test('parameters: config', async () => { + const result = useWaitForTransactionReceipt({ config }) + + if (result.data && 'l1BatchNumber' in result.data) + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() +}) diff --git a/packages/register-tests/react/src/useWriteContract.test-d.ts b/packages/register-tests/react/src/useWriteContract.test-d.ts new file mode 100644 index 0000000000..c499ff8f37 --- /dev/null +++ b/packages/register-tests/react/src/useWriteContract.test-d.ts @@ -0,0 +1,65 @@ +import { abi, config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' +import { useWriteContract } from 'wagmi' +import { celo, mainnet, optimism } from 'wagmi/chains' + +test('chain formatters', () => { + const { writeContract } = useWriteContract() + + const shared = { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + } as const + + writeContract({ + ...shared, + feeCurrency: '0x', + }) + + type Result = Parameters< + typeof writeContract< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof celo.id + > + >[0] + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + writeContract({ + ...shared, + chainId: celo.id, + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + const { writeContract } = useWriteContract({ config }) + + writeContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/react/tsconfig.json b/packages/register-tests/react/tsconfig.json new file mode 100644 index 0000000000..77a211dbbb --- /dev/null +++ b/packages/register-tests/react/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/register-tests/vue/package.json b/packages/register-tests/vue/package.json new file mode 100644 index 0000000000..fc41697d7f --- /dev/null +++ b/packages/register-tests/vue/package.json @@ -0,0 +1,13 @@ +{ + "name": "vue-register", + "private": true, + "type": "module", + "scripts": { + "check:types": "tsc --noEmit" + }, + "dependencies": { + "@tanstack/vue-query": "catalog:", + "@wagmi/vue": "workspace:*", + "vue": "catalog:" + } +} diff --git a/packages/register-tests/vue/src/config.ts b/packages/register-tests/vue/src/config.ts new file mode 100644 index 0000000000..fd13c5a6be --- /dev/null +++ b/packages/register-tests/vue/src/config.ts @@ -0,0 +1,26 @@ +import { createConfig, mock } from '@wagmi/vue' +import { celo, mainnet, optimism, zkSync } from '@wagmi/vue/chains' +import { http } from 'viem' + +export const config = createConfig({ + chains: [celo, mainnet, optimism, zkSync], + connectors: [mock({ accounts: ['0x'] })], + transports: { + [celo.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zkSync.id]: http(), + }, +}) + +export type ChainId = + | typeof celo.id + | typeof mainnet.id + | typeof optimism.id + | typeof zkSync.id + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/packages/register-tests/vue/src/useAccount.test-d.ts b/packages/register-tests/vue/src/useAccount.test-d.ts new file mode 100644 index 0000000000..6471bf4550 --- /dev/null +++ b/packages/register-tests/vue/src/useAccount.test-d.ts @@ -0,0 +1,17 @@ +import { config } from '@wagmi/test' +import { useAccount } from '@wagmi/vue' +import { expectTypeOf, test } from 'vitest' + +import type { ChainId } from './config.js' + +test('default', () => { + const result = useAccount() + if (result.chain.value) + expectTypeOf(result.chain.value.id).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useAccount({ config }) + if (result.chain.value) + expectTypeOf(result.chain.value.id).toEqualTypeOf<1 | 10 | 456>() +}) diff --git a/packages/register-tests/vue/src/useChainId.test-d.ts b/packages/register-tests/vue/src/useChainId.test-d.ts new file mode 100644 index 0000000000..ffeddcb6ba --- /dev/null +++ b/packages/register-tests/vue/src/useChainId.test-d.ts @@ -0,0 +1,15 @@ +import { config } from '@wagmi/test' +import { useChainId } from '@wagmi/vue' +import { expectTypeOf, test } from 'vitest' + +import type { ChainId } from './config.js' + +test('default', async () => { + const chainId = useChainId() + expectTypeOf(chainId.value).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const chainId = useChainId({ config }) + expectTypeOf(chainId.value).toEqualTypeOf<1 | 456 | 10>() +}) diff --git a/packages/register-tests/vue/src/useChains.test-d.ts b/packages/register-tests/vue/src/useChains.test-d.ts new file mode 100644 index 0000000000..d1bf0eb151 --- /dev/null +++ b/packages/register-tests/vue/src/useChains.test-d.ts @@ -0,0 +1,11 @@ +import { useChains } from '@wagmi/vue' +import type { Chain, celo, optimism } from '@wagmi/vue/chains' +import { expectTypeOf, test } from 'vitest' + +test('default', () => { + const chains = useChains() + + expectTypeOf(chains.value[0]).toEqualTypeOf() + expectTypeOf(chains.value[2]).toEqualTypeOf() + expectTypeOf(chains.value[5]).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useClient.test-d.ts b/packages/register-tests/vue/src/useClient.test-d.ts new file mode 100644 index 0000000000..99cea8d3f2 --- /dev/null +++ b/packages/register-tests/vue/src/useClient.test-d.ts @@ -0,0 +1,38 @@ +import { type chain, config } from '@wagmi/test' +import { useClient } from '@wagmi/vue' +import { mainnet } from '@wagmi/vue/chains' +import { expectTypeOf, test } from 'vitest' + +import type { ChainId } from './config.js' + +test('default', () => { + const client = useClient() + expectTypeOf(client.value.chain.id).toEqualTypeOf() + expectTypeOf(client.value.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: config', () => { + const client = useClient({ config }) + expectTypeOf(client.value.chain.id).toEqualTypeOf< + (typeof config)['chains'][number]['id'] + >() + expectTypeOf(client.value.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = useClient({ + config, + chainId: mainnet.id, + }) + expectTypeOf(client.value.chain).toEqualTypeOf() + expectTypeOf(client.value.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + const client = useClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client.value).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useConfig.test-d.ts b/packages/register-tests/vue/src/useConfig.test-d.ts new file mode 100644 index 0000000000..c65f70c214 --- /dev/null +++ b/packages/register-tests/vue/src/useConfig.test-d.ts @@ -0,0 +1,17 @@ +import { config as testConfig } from '@wagmi/test' +import { type Config, useConfig } from '@wagmi/vue' +import { expectTypeOf, test } from 'vitest' + +import type { config } from './config.js' + +test('default', async () => { + const result = useConfig() + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useConfig({ config: testConfig }) + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useConnect.test-d.ts b/packages/register-tests/vue/src/useConnect.test-d.ts new file mode 100644 index 0000000000..128834ff6d --- /dev/null +++ b/packages/register-tests/vue/src/useConnect.test-d.ts @@ -0,0 +1,13 @@ +import { useConnect } from '@wagmi/vue' +import { expectTypeOf, test } from 'vitest' + +test('infers connect parameters', () => { + const { connect, connectors, variables } = useConnect() + const connector = connectors[0]! + + expectTypeOf(variables.value?.foo).toEqualTypeOf() + connect({ + connector, + foo: 'bar', + }) +}) diff --git a/packages/register-tests/vue/src/useReadContract.test-d.ts b/packages/register-tests/vue/src/useReadContract.test-d.ts new file mode 100644 index 0000000000..c2a1a6386a --- /dev/null +++ b/packages/register-tests/vue/src/useReadContract.test-d.ts @@ -0,0 +1,30 @@ +import type { abi } from '@wagmi/test' +import type { useReadContract } from '@wagmi/vue' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import type { DeepUnwrapRef } from '../../../vue/src/types/ref.js' +import type { ChainId } from './config.js' + +test('UseReadContractParameters', () => { + type Result = DeepUnwrapRef< + NonNullable< + Parameters< + typeof useReadContract + >[0] + > + > + + expectTypeOf().toMatchTypeOf<{ + functionName?: + | 'symbol' + | 'name' + | 'allowance' + | 'balanceOf' + | 'decimals' + | 'totalSupply' + | undefined + args?: readonly [Address] | undefined + chainId?: ChainId | undefined + }>() +}) diff --git a/packages/register-tests/vue/src/useSendTransaction.test-d.ts b/packages/register-tests/vue/src/useSendTransaction.test-d.ts new file mode 100644 index 0000000000..c49a8ef386 --- /dev/null +++ b/packages/register-tests/vue/src/useSendTransaction.test-d.ts @@ -0,0 +1,55 @@ +import { config } from '@wagmi/test' +import { useSendTransaction } from '@wagmi/vue' +import { celo, mainnet, optimism } from '@wagmi/vue/chains' +import { expectTypeOf, test } from 'vitest' +import type { ChainId } from './config.js' + +test('chain formatters', () => { + const { sendTransaction } = useSendTransaction() + + sendTransaction( + { + to: '0x', + feeCurrency: '0x', + }, + { + onSuccess(_data, variables) { + expectTypeOf(variables.chainId).toEqualTypeOf() + }, + }, + ) + + type Result = Parameters>[0] + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + sendTransaction({ + chainId: celo.id, + to: '0x', + feeCurrency: '0x', + }) + + sendTransaction({ + chainId: mainnet.id, + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) + + sendTransaction({ + chainId: optimism.id, + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + const { sendTransaction } = useSendTransaction({ config }) + + sendTransaction({ + to: '0x', + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/vue/src/useSimulateContract.test-d.ts b/packages/register-tests/vue/src/useSimulateContract.test-d.ts new file mode 100644 index 0000000000..2a2b65c4d5 --- /dev/null +++ b/packages/register-tests/vue/src/useSimulateContract.test-d.ts @@ -0,0 +1,86 @@ +import { type abi, config as testConfig } from '@wagmi/test' +import { + type UseSimulateContractParameters, + useSimulateContract, +} from '@wagmi/vue' +import type { SimulateContractParameters } from '@wagmi/vue/actions' +import { celo, mainnet, optimism } from '@wagmi/vue/chains' +import type { SimulateContractOptions } from '@wagmi/vue/query' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import type { ChainId, config } from './config.js' + +test('chain formatters', () => { + const { data } = useSimulateContract({ + feeCurrency: '0x', + }) + if (data.value && data.value.chainId === celo.id) { + expectTypeOf(data.value.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + const { data: data2 } = useSimulateContract({ + chainId: celo.id, + feeCurrency: '0x', + }) + if (data2.value) { + expectTypeOf(data2.value.request.feeCurrency).toEqualTypeOf< + `0x${string}` | undefined + >() + } + + useSimulateContract({ + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + useSimulateContract({ + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('UseSimulateContractParameters', () => { + type Result = UseSimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config + > + + expectTypeOf<{ + functionName?: 'approve' | 'transfer' | 'transferFrom' | undefined + args?: readonly [Address, Address, bigint] | undefined + chainId?: ChainId | undefined + }>().toMatchTypeOf() + + type Result2 = SimulateContractParameters< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toEqualTypeOf() + + type Result3 = SimulateContractOptions< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof config, + typeof celo.id + > + expectTypeOf().toEqualTypeOf() +}) + +test('parameters: config', async () => { + useSimulateContract({ + config: testConfig, + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/vue/src/useSwitchChain.test-d.ts b/packages/register-tests/vue/src/useSwitchChain.test-d.ts new file mode 100644 index 0000000000..cf0153b3b7 --- /dev/null +++ b/packages/register-tests/vue/src/useSwitchChain.test-d.ts @@ -0,0 +1,27 @@ +import { useSwitchChain } from '@wagmi/vue' +import { type celo, mainnet, type optimism } from '@wagmi/vue/chains' +import { expectTypeOf, test } from 'vitest' + +import type { ChainId, config } from './config.js' + +test('default', () => { + const switchChain = useSwitchChain() + + const chains = switchChain.chains.value + + expectTypeOf(chains).toEqualTypeOf<(typeof config)['chains']>() + expectTypeOf(chains[0]).toEqualTypeOf() + expectTypeOf(chains[2]).toEqualTypeOf() + + switchChain.switchChain( + { chainId: 1 }, + { + onSuccess(data) { + expectTypeOf(data).toEqualTypeOf(mainnet) + }, + }, + ) + + type Result = Parameters<(typeof switchChain)['switchChain']>[0] + expectTypeOf().toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useTransaction.test-d.ts b/packages/register-tests/vue/src/useTransaction.test-d.ts new file mode 100644 index 0000000000..7bc262c38f --- /dev/null +++ b/packages/register-tests/vue/src/useTransaction.test-d.ts @@ -0,0 +1,25 @@ +import { config } from '@wagmi/test' +import { useTransaction } from '@wagmi/vue' +import { celo } from '@wagmi/vue/chains' +import { expectTypeOf, test } from 'vitest' + +test('chain formatters', () => { + const result = useTransaction() + if (result.data?.value?.chainId === celo.id) { + expectTypeOf(result.data.value.feeCurrency).toEqualTypeOf< + `0x${string}` | null + >() + } + + const result2 = useTransaction({ chainId: celo.id }) + expectTypeOf(result2.data?.value?.feeCurrency).toEqualTypeOf< + `0x${string}` | null | undefined + >() +}) + +test('parameters: config', async () => { + const result = useTransaction({ config }) + + if (result.data && 'feeCurrency' in result.data) + expectTypeOf(result.data.feeCurrency).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useTransactionReceipt.test-d.ts b/packages/register-tests/vue/src/useTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..c202d82dbb --- /dev/null +++ b/packages/register-tests/vue/src/useTransactionReceipt.test-d.ts @@ -0,0 +1,41 @@ +import { config } from '@wagmi/test' +import { useTransactionReceipt } from '@wagmi/vue' +import { zkSync } from '@wagmi/vue/chains' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' + +test('chain formatters', () => { + const result = useTransactionReceipt() + + if (result.data?.value?.chainId === zkSync.id) { + expectTypeOf(result.data.value.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.data.value.l1BatchTxIndex).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result.data.value.logs).toEqualTypeOf() + expectTypeOf(result.data.value.l2ToL1Logs).toEqualTypeOf< + ZkSyncL2ToL1Log[] + >() + } + + const result2 = useTransactionReceipt({ chainId: zkSync.id }) + if (result2.data.value) { + expectTypeOf(result2.data.value.l1BatchNumber).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result2.data.value.l1BatchTxIndex).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result2.data.value.logs).toEqualTypeOf() + expectTypeOf(result2.data.value.l2ToL1Logs).toEqualTypeOf< + ZkSyncL2ToL1Log[] + >() + } +}) + +test('parameters: config', async () => { + const result = useTransactionReceipt({ config }) + + if (result.data && 'l1BatchNumber' in result.data) + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useWaitForTransaction.test-d.ts b/packages/register-tests/vue/src/useWaitForTransaction.test-d.ts new file mode 100644 index 0000000000..e0286617c1 --- /dev/null +++ b/packages/register-tests/vue/src/useWaitForTransaction.test-d.ts @@ -0,0 +1,41 @@ +import { config } from '@wagmi/test' +import { useWaitForTransactionReceipt } from '@wagmi/vue' +import { zkSync } from '@wagmi/vue/chains' +import type { ZkSyncL2ToL1Log, ZkSyncLog } from 'viem/zksync' +import { expectTypeOf, test } from 'vitest' + +test('chain formatters', () => { + const result = useWaitForTransactionReceipt() + + if (result.data?.value?.chainId === zkSync.id) { + expectTypeOf(result.data.value.l1BatchNumber).toEqualTypeOf() + expectTypeOf(result.data.value.l1BatchTxIndex).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result.data.value.logs).toEqualTypeOf() + expectTypeOf(result.data.value.l2ToL1Logs).toEqualTypeOf< + ZkSyncL2ToL1Log[] + >() + } + + const result2 = useWaitForTransactionReceipt({ chainId: zkSync.id }) + if (result2.data.value) { + expectTypeOf(result2.data.value.l1BatchNumber).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result2.data.value.l1BatchTxIndex).toEqualTypeOf< + bigint | null + >() + expectTypeOf(result2.data.value.logs).toEqualTypeOf() + expectTypeOf(result2.data.value.l2ToL1Logs).toEqualTypeOf< + ZkSyncL2ToL1Log[] + >() + } +}) + +test('parameters: config', async () => { + const result = useWaitForTransactionReceipt({ config }) + + if (result.data && 'l1BatchNumber' in result.data) + expectTypeOf(result.data.l1BatchNumber).toEqualTypeOf() +}) diff --git a/packages/register-tests/vue/src/useWriteContract.test-d.ts b/packages/register-tests/vue/src/useWriteContract.test-d.ts new file mode 100644 index 0000000000..ec4f38430c --- /dev/null +++ b/packages/register-tests/vue/src/useWriteContract.test-d.ts @@ -0,0 +1,65 @@ +import { abi, config } from '@wagmi/test' +import { useWriteContract } from '@wagmi/vue' +import { celo, mainnet, optimism } from '@wagmi/vue/chains' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +test('chain formatters', () => { + const { writeContract } = useWriteContract() + + const shared = { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + } as const + + writeContract({ + ...shared, + feeCurrency: '0x', + }) + + type Result = Parameters< + typeof writeContract< + typeof abi.erc20, + 'transferFrom', + [Address, Address, bigint], + typeof celo.id + > + >[0] + expectTypeOf().toEqualTypeOf< + `0x${string}` | undefined + >() + writeContract({ + ...shared, + chainId: celo.id, + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: mainnet.id, + // @ts-expect-error + feeCurrency: '0x', + }) + + writeContract({ + ...shared, + chainId: optimism.id, + // @ts-expect-error + feeCurrency: '0x', + }) +}) + +test('parameters: config', async () => { + const { writeContract } = useWriteContract({ config }) + + writeContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + // @ts-expect-error + feeCurrency: '0x', + }) +}) diff --git a/packages/register-tests/vue/tsconfig.json b/packages/register-tests/vue/tsconfig.json new file mode 100644 index 0000000000..77a211dbbb --- /dev/null +++ b/packages/register-tests/vue/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/services/README.md b/packages/services/README.md deleted file mode 100644 index a6c67631d2..0000000000 --- a/packages/services/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# packages/services - -This folder contains clients to Sequence backend/infrastructure services. diff --git a/packages/services/api/CHANGELOG.md b/packages/services/api/CHANGELOG.md deleted file mode 100644 index de944ea36e..0000000000 --- a/packages/services/api/CHANGELOG.md +++ /dev/null @@ -1,2323 +0,0 @@ -# @0xsequence/api - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet - -## 2.0.12 - -### Patch Changes - -- api: update bindings - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints - -## 2.0.0 - -### Major Changes - -- ethers v6 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api - -## 1.10.9 - -### Patch Changes - -- waas minor update - -## 1.10.8 - -### Patch Changes - -- update metadata bindings - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia - -## 1.10.3 - -### Patch Changes - -- typing fix - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants - -## 1.9.36 - -### Patch Changes - -- guard: export client - -## 1.9.35 - -### Patch Changes - -- guard: update bindings - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email - -## 1.9.33 - -### Patch Changes - -- waas: umd build - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes - -## 1.9.30 - -### Patch Changes - -- update - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore - -## 1.9.23 - -### Patch Changes - -- update api client bindings - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings - -## 1.9.21 - -### Patch Changes - -- api client bindings - -## 1.9.20 - -### Patch Changes - -- api client bindings update - -## 1.9.19 - -### Patch Changes - -- waas update - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client - -## 1.9.8 - -### Patch Changes - -- waas client update - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer - -## 1.9.6 - -### Patch Changes - -- waas package update - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia - -## 1.9.1 - -### Patch Changes - -- analytics fix - -## 1.9.0 - -### Minor Changes - -- waas release - -## 1.8.8 - -### Patch Changes - -- update metadata bindings - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested - -## 1.8.1 - -### Patch Changes - -- update to analytics provider - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks - -## 1.6.3 - -### Patch Changes - -- network list update - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods - -## 1.4.2 - -### Patch Changes - -- guard: update bindings - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic - -## 1.4.0 - -### Minor Changes - -- project access key support - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions - -## 1.1.11 - -### Patch Changes - -- add homeverse configs - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object - -## 0.43.28 - -### Patch Changes - -- update api bindings - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM - -## 0.43.22 - -### Patch Changes - -- add zkevm chain - -## 0.43.21 - -### Patch Changes - -- api: update client bindings - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings - -## 0.43.19 - -### Patch Changes - -- session proof update - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods - -## 0.43.14 - -### Patch Changes - -- bump - -## 0.43.13 - -### Patch Changes - -- update rpc bindings - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter - -## 0.43.10 - -### Patch Changes - -- various improvements - -## 0.43.9 - -### Patch Changes - -- update deps - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings - -## 0.42.6 - -### Patch Changes - -- api bindings update - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options - -## 0.42.3 - -### Patch Changes - -- update api bindings - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' - -## 0.41.3 - -### Patch Changes - -- api bindings update - -## 0.41.2 - -### Patch Changes - -- api bindings update - -## 0.41.1 - -### Patch Changes - -- update default networks - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain - -## 0.40.5 - -### Patch Changes - -- api: update bindings - -## 0.40.4 - -### Patch Changes - -- add unreal transport - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option - -## 0.39.4 - -### Patch Changes - -- api: update client bindings - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider - -## 0.39.2 - -### Patch Changes - -- update umd name - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) - -## 0.36.7 - -### Patch Changes - -- fix missing break - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation - -## 0.35.10 - -### Patch Changes - -- upgrade deps - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -## 0.33.1 - -### Patch Changes - -- update bindings - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -## 0.29.9 - -### Patch Changes - -- update client - -## 0.29.8 - -### Patch Changes - -- update api - -## 0.29.4 - -### Patch Changes - -- api: update rpc bindings - -## 0.29.1 - -### Patch Changes - -- metadata: ContractInfo.decimals is now optional, i.e. may be undefined - - api: new APIs for user storage and isUsingGoogleMail - -## 0.29.0 - -### Minor Changes - -- major architectural changes in Sequence design - - only one API instance, API is no longer a per-chain service - - separate per-chain indexer service, API no longer handles indexing - - single contract metadata service, API no longer serves metadata - - chaind package has been removed, indexer and metadata packages have been added - - stronger typing with new explicit ChainId type - - multicall fixes and improvements - - forbid "wait" transactions in sendTransactionBatch calls - -## 0.28.0 - -### Minor Changes - -- extension provider - -## 0.27.0 - -### Minor Changes - -- Add requireFreshSigner lib to sessions - -## 0.25.1 - -### Patch Changes - -- Fix build typescrypt issue - -## 0.25.0 - -### Minor Changes - -- 10c8af8: Add estimator package - Fix multicall few calls bug - -## 0.24.0 - -### Minor Changes - -- pass wallet config and nonce to GetMetaTxnNetworkFeeOptions - -## 0.23.0 - -### Minor Changes - -- - relayer: offer variety of gas fee options from the relayer service" - -## 0.22.2 - -### Patch Changes - -- e1c109e: Fix authProof on expired sessions - -## 0.22.1 - -### Patch Changes - -- transport session cache - -## 0.22.0 - -### Minor Changes - -- e667b65: Expose all relayer options on networks - -## 0.21.5 - -### Patch Changes - -- Give priority to metaTxnId returned by relayer - -## 0.21.4 - -### Patch Changes - -- Add has enough signers method - -## 0.21.3 - -### Patch Changes - -- add window session cache - -## 0.21.2 - -### Patch Changes - -- exception handlind in relayer - -## 0.21.0 - -### Minor Changes - -- - fix gas estimation on wallets with large number of signers - - update to session handling and wallet config construction upon auth - -## 0.20.0 - -### Minor Changes - -- revert JWT request piggybacking - -## 0.19.3 - -### Patch Changes - -- jwtAuth visibility, package version sync - -## 0.19.0 - -### Minor Changes - -- - provider, improve dapp / wallet transport io - -## 0.18.0 - -### Minor Changes - -- relayer improvements and pending transaction handling - -## 0.17.0 - -### Minor Changes - -- ArcadeumAPIClient no longer exposes jwtAuth - -## 0.16.1 - -### Patch Changes - -- api: add legacy types for bw compat - -## 0.16.0 - -### Minor Changes - -- relayer as its own service separate from chaind - -## 0.15.1 - -### Patch Changes - -- update api clients - -## 0.15.0 - -### Patch Changes - -- - update chaind and api bindings - - replace EstimateMetaTxnGasReceipt with UpdateMetaTxnGasLimits and GetMetaTxnNetworkFeeOptions - -## 0.14.3 - -### Patch Changes - -- Fix 0xSequence relayer dependencies - -## 0.14.2 - -### Patch Changes - -- Add debug logs to rpc-relayer - -## 0.14.1 - -### Patch Changes - -- update api client - -## 0.14.0 - -### Minor Changes - -- update sequence utils finder which includes optimization - -## 0.13.0 - -### Minor Changes - -- Update SequenceUtils deployed contract - -## 0.12.1 - -### Patch Changes - -- npm bump - -## 0.12.0 - -### Minor Changes - -- provider: improvements to window transport - -## 0.11.4 - -### Patch Changes - -- update api client - -## 0.11.3 - -### Patch Changes - -- improve openWindow state options handling - -## 0.11.2 - -### Patch Changes - -- Fix multicall proxy scopes - -## 0.11.1 - -### Patch Changes - -- Add support for dynamic and nested signatures - -## 0.11.0 - -### Minor Changes - -- Update wallet context to 1.7 contracts - -## 0.10.9 - -### Patch Changes - -- add support for public addresses as signers in session.open - -## 0.10.8 - -### Patch Changes - -- Multicall production configuration - -## 0.10.7 - -### Patch Changes - -- allow provider transport to force disconnect - -## 0.10.6 - -### Patch Changes - -- - fix getWalletState method - -## 0.10.5 - -### Patch Changes - -- update relayer gas refund options - -## 0.10.4 - -### Patch Changes - -- Update api proto - -## 0.10.3 - -### Patch Changes - -- Fix loading config cross-chain - -## 0.10.2 - -### Patch Changes - -- - message digest fix - -## 0.10.1 - -### Patch Changes - -- upgrade deps - -## 0.10.0 - -### Minor Changes - -- Deployed new contracts with ERC1271 signer support - -## 0.9.6 - -### Patch Changes - -- Update ABIs for latest sequence contracts - -## 0.9.5 - -### Patch Changes - -- Implemented session class - -## 0.9.3 - -### Patch Changes - -- - minor improvements - -## 0.9.2 - -### Patch Changes - -- - Update api client - -## 0.9.1 - -### Patch Changes - -- - patch bump - -## 0.9.0 - -### Minor Changes - -- - provider transport hardening - -## 0.8.5 - -### Patch Changes - -- - use latest wallet-contracts - -## 0.8.4 - -### Patch Changes - -- - minor improvements, name updates and comments - -## 0.8.3 - -### Patch Changes - -- - refinements - - - normalize signer address in config - - - provider: getWalletState() method to WalletProvider - -## 0.8.2 - -### Patch Changes - -- - field rename and ethauth dependency bump - -## 0.8.1 - -### Patch Changes - -- - variety of optimizations - -## 0.8.0 - -### Minor Changes - -- - changeset fix - -## 0.7.0 - -### Patch Changes - -- 6f11ed7: sequence.js, init release diff --git a/packages/services/api/README.md b/packages/services/api/README.md deleted file mode 100644 index 1e3d3fdd34..0000000000 --- a/packages/services/api/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/api - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/api/eslint.config.js b/packages/services/api/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/api/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/api/package.json b/packages/services/api/package.json deleted file mode 100644 index 8d2f87a228..0000000000 --- a/packages/services/api/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/api", - "version": "3.0.5", - "description": "api sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/api", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@repo/eslint-config": "workspace:^", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/api/src/api.gen.ts b/packages/services/api/src/api.gen.ts deleted file mode 100644 index 139fa541ae..0000000000 --- a/packages/services/api/src/api.gen.ts +++ /dev/null @@ -1,4091 +0,0 @@ -/* eslint-disable */ -// sequence-api v0.4.0 3c15fa79614e43a5321cd2ac0c080e80af291bd1 -// -- -// Code generated by Webrpc-gen@v0.31.0 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=api.ridl -target=typescript -client -out=./clients/api.gen.ts - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.4.0' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '3c15fa79614e43a5321cd2ac0c080e80af291bd1' - -// -// Client interface -// - -export interface APIClient { - /** - * - * Runtime - * - */ - ping(headers?: object, signal?: AbortSignal): Promise - - version(headers?: object, signal?: AbortSignal): Promise - - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - - clock(headers?: object, signal?: AbortSignal): Promise - - getSequenceContext(headers?: object, signal?: AbortSignal): Promise - - /** - * - * Auth - * - * TODO: rename 'ewtString' arg to 'ethauthProof' - */ - getAuthToken(req: GetAuthTokenRequest, headers?: object, signal?: AbortSignal): Promise - - getAuthToken2(req: GetAuthToken2Request, headers?: object, signal?: AbortSignal): Promise - - sendPasswordlessLink( - req: SendPasswordlessLinkRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - registerPublicKey( - req: RegisterPublicKeyRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getPublicKey(req: GetPublicKeyRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * - * Contacts / Friends - * - */ - friendList(req: FriendListRequest, headers?: object, signal?: AbortSignal): Promise - - getFriendByAddress( - req: GetFriendByAddressRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - searchFriends(req: SearchFriendsRequest, headers?: object, signal?: AbortSignal): Promise - - addFriend(req: AddFriendRequest, headers?: object, signal?: AbortSignal): Promise - - updateFriendNickname( - req: UpdateFriendNicknameRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeFriend(req: RemoveFriendRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * - * Chain-Utils - * - */ - contractCall(req: ContractCallRequest, headers?: object, signal?: AbortSignal): Promise - - decodeContractCall( - req: DecodeContractCallRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - lookupContractCallSelectors( - req: LookupContractCallSelectorsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * User Storage - * - */ - userStorageFetch( - req: UserStorageFetchRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - userStorageSave(req: UserStorageSaveRequest, headers?: object, signal?: AbortSignal): Promise - - userStorageDelete( - req: UserStorageDeleteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - userStorageFetchAll( - req: UserStorageFetchAllRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Wallet utils - * - */ - getMoonpayLink(req: GetMoonpayLinkRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * - IsUsingGoogleMail(domain: string) => (yes: bool) - */ - resolveENSAddress( - req: ResolveENSAddressRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * TODO: we can add walletContext optional in the future when we need it - * NOTE: chainId can be either a number or canonical name - */ - isValidSignature( - req: IsValidSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - isValidMessageSignature( - req: IsValidMessageSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - isValidTypedDataSignature( - req: IsValidTypedDataSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - isValidETHAuthProof( - req: IsValidETHAuthProofRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getOnRampURL(req: GetOnRampURLRequest, headers?: object, signal?: AbortSignal): Promise - - transakGetCountries(headers?: object, signal?: AbortSignal): Promise - - transakGetCryptoCurrencies(headers?: object, signal?: AbortSignal): Promise - - transakGetFiatCurrencies(headers?: object, signal?: AbortSignal): Promise - - transakGetPrice(req: TransakGetPriceRequest, headers?: object, signal?: AbortSignal): Promise - - transakGetSupportedNFTCheckoutChains( - headers?: object, - signal?: AbortSignal, - ): Promise - - transakGetWidgetURL( - req: TransakGetWidgetURLRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Price Feed - * - */ - getCoinPrices(req: GetCoinPricesRequest, headers?: object, signal?: AbortSignal): Promise - - getCollectiblePrices( - req: GetCollectiblePricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Price Feed utils - * - */ - getExchangeRate(req: GetExchangeRateRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * - * Util / misc - * - */ - memoryStore(req: MemoryStoreRequest, headers?: object, signal?: AbortSignal): Promise - - memoryLoad(req: MemoryLoadRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * - * Legacy - * - */ - getInviteInfo(headers?: object, signal?: AbortSignal): Promise - - /** - * NOTE: we're still using this from SW-API to Sequence-API to claim invite code - */ - isValidAccessCode( - req: IsValidAccessCodeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - internalClaimAccessCode( - req: InternalClaimAccessCodeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Utils - */ - blockNumberAtTime( - req: BlockNumberAtTimeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Paper - * TODO: deprecate in the future - * - */ - paperSessionSecret( - req: PaperSessionSecretRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - paperSessionSecret2( - req: PaperSessionSecret2Request, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Linked wallets (v0 -- simple support) - * - */ - linkWallet(req: LinkWalletRequest, headers?: object, signal?: AbortSignal): Promise - - getLinkedWallets( - req: GetLinkedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeLinkedWallet( - req: RemoveLinkedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * NOTE: these methods are deprecated, please do not use them. We may resurface them in the future, but just wanted - * to be clear, they are not necessary for our linked wallets. - */ - generateWaaSVerificationURL( - req: GenerateWaaSVerificationURLRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - validateWaaSVerificationNonce( - req: ValidateWaaSVerificationNonceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * - * WaaS child wallet adoption - * - */ - listAdoptedWallets( - req: ListAdoptedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getLifiChains(headers?: object, signal?: AbortSignal): Promise - - getLifiTokens(req: GetLifiTokensRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * All parameters except `params` are deprecated. - * Use only the `params` object to pass values. - */ - getLifiSwapRoutes( - req: GetLifiSwapRoutesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getLifiSwapQuote( - req: GetLifiSwapQuoteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Inventory, payments and management - * - */ - listCurrencyGroups(headers?: object, signal?: AbortSignal): Promise - - addOffchainInventory( - req: AddOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getOffchainInventory( - req: GetOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listOffchainInventories( - req: ListOffchainInventoriesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - updateOffchainInventory( - req: UpdateOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteOffchainInventory( - req: DeleteOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - requestOffchainPayment( - req: RequestOffchainPaymentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listOffchainPayments( - req: ListOffchainPaymentsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Packs - * - */ - savePack(req: SavePackRequest, headers?: object, signal?: AbortSignal): Promise - - getPack(req: GetPackRequest, headers?: object, signal?: AbortSignal): Promise - - getPackIds(req: GetPackIdsRequest, headers?: object, signal?: AbortSignal): Promise - - deletePack(req: DeletePackRequest, headers?: object, signal?: AbortSignal): Promise - - updatePackContent( - req: UpdatePackContentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getRevealTxData(req: GetRevealTxDataRequest, headers?: object, signal?: AbortSignal): Promise - - checkoutOptionsPrimary( - req: CheckoutOptionsPrimaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - checkoutOptionsSecondary( - req: CheckoutOptionsSecondaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - checkoutOptionsGetTransakContractID( - req: CheckoutOptionsGetTransakContractIDRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - fortePayCreateIntent( - req: FortePayCreateIntentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - fortePayGetPaymentStatuses( - req: FortePayGetPaymentStatusesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -// -// Schema types -// - -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', -} - -export enum GetLifiSwapRouteDirection { - to = 'to', - from = 'from', -} - -export enum TokenType { - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', -} - -export enum TransakBuySell { - UNKNOWN = 'UNKNOWN', - BUY = 'BUY', - SELL = 'SELL', -} - -export enum TradeType { - EXACT_INPUT = 'EXACT_INPUT', - EXACT_OUTPUT = 'EXACT_OUTPUT', -} - -export enum CheckoutOptionCrypto { - none = 'none', - partially = 'partially', - all = 'all', -} - -export enum CheckoutOptionNFTCheckoutProvider { - unknown = 'unknown', - transak = 'transak', -} - -export enum CheckoutOptionOnRampProvider { - unknown = 'unknown', - transak = 'transak', -} - -export enum CheckoutOptionSwapProvider { - unknown = 'unknown', - lifi = 'lifi', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - checks: RuntimeChecks - numTxnsRelayed: { [key: string]: NumTxnsRelayed } -} - -export interface NumTxnsRelayed { - chainID: number - prev: number - current: number - period: number -} - -export interface RuntimeChecks {} - -export interface SequenceContext { - factory: string - mainModule: string - mainModuleUpgradable: string - guestModule: string - utils: string -} - -export interface PublicKey { - id: string - x: string - y: string -} - -export interface User { - address: string - username: string - avatar: string - bio: string - location: string - locale: string - backup?: boolean - backupConfirmed?: boolean - maxInvites?: number - updatedAt?: string - createdAt?: string -} - -export interface WalletBackup { - accountAddress: string - secretHash: string - encryptedWallet: string - userConfirmed: boolean - updatedAt?: string - createdAt?: string -} - -export interface Friend { - id: number - userAddress: string - friendAddress: string - nickname: string - user?: User - createdAt?: string -} - -export interface MetaTxn { - id: string - chainId: string - walletAddress: string - contract: string - input: string -} - -export interface MetaTxnReceipt { - metaTxID: string - status: string - txnReceipt?: string - revertReason?: string -} - -export interface InviteCode { - usesLeft: number - ownerAccount: string - email?: string - url: string - createdAt?: string - expiresAt?: string -} - -export interface InviteCodeAccount { - claimedByUserAddress: string - claimedAt?: string -} - -export interface InviteInfo { - expiryInHours: number - max: number - invites: Array -} - -export interface ContractCall { - signature: string - function: string - args: Array -} - -export interface TupleComponent { - name?: string - type: string - value: any -} - -export interface UserStorage { - userAddress: string - key: string - value: any -} - -export interface Token { - chainId: number - contractAddress: string - tokenId?: string -} - -export interface Price { - value: number - currency: string -} - -export interface TokenPrice { - token: Token - price?: Price - price24hChange?: Price - price24hVol?: Price - floorPrice: Price - buyPrice: Price - sellPrice: Price - updatedAt: string -} - -export interface ExchangeRate { - name: string - symbol: string - value: number - vsCurrency: string - currencyType: string -} - -export interface LinkedWallet { - id: number - walletType?: string - walletAddress: string - linkedWalletAddress: string - createdAt?: string -} - -export interface Page { - pageSize?: number - page?: number - totalRecords?: number - column?: string - before?: any - after?: any - sort?: Array - more?: boolean -} - -export interface SortBy { - column: string - order: SortOrder -} - -export interface LifiToken { - chainId: number - address: string - symbol: string - name: string - decimals: number - priceUsd: number - price?: string - coinKey: string - logoUri: string -} - -export interface GetLifiSwapRouteParams { - direction: GetLifiSwapRouteDirection - chainId: number - walletAddress: string - tokenAddress: string - tokenAmount: string -} - -export interface LifiSwapRoute { - fromChainId: number - toChainId: number - fromTokens: Array - toTokens: Array -} - -export interface GetLifiSwapQuoteParams { - chainId: number - walletAddress: string - fromTokenAddress: string - toTokenAddress: string - fromTokenAmount?: string - toTokenAmount?: string - includeApprove: boolean - slippageBps: number -} - -export interface LifiSwapQuote { - currencyAddress: string - currencyBalance: string - price: string - maxPrice: string - to: string - transactionData: string - transactionValue: string - approveData: string - amount: string - amountMin: string -} - -export interface CurrencyGroup { - name: string - tokens: Array -} - -export interface CurrencyGroupToken { - chainId: number - tokenAddress: string -} - -export interface OffchainInventory { - id: number - projectId: number - chainId: number - externalProductId: string - paymentTokenAddress: string - paymentTokenType: TokenType - paymentTokenId: number - paymentAmount: number - paymentRecipient: string - chainedCallAddress?: string - chainedCallData?: string - allowCrossChainPayments?: boolean - callbackURL?: string - createdAt: string - deletedAt?: string -} - -export interface OffchainPayment { - id: number - offchainInventoryId: number - productRecipient: string - paymentChainId: number - paymentTokenAddress: string - expiration: string - createdAt: string - completedAt?: string - processedAt?: string -} - -export interface PaymentResponse { - paymentId: number - offchainInventoryId: number - chainId: number - externalProductId: string - paymentTokenAddress: string - paymentTokenType: TokenType - paymentTokenId: number - paymentTotal: number - expiration: string - signature: string - txTo: string - txData: string -} - -export interface AdoptedChildWallet { - address: string -} - -export interface Pack { - id: number - chainId: number - projectId: number - contractAddress: string - packId: string - content: Array - createdAt?: string -} - -export interface PackContent { - tokenAddresses: Array - isERC721: Array - tokenIds: Array> - amounts: Array> -} - -export interface TransakCountry { - alpha2: string - alpha3: string - isAllowed: boolean - isLightKycAllowed: boolean - name: string - currencyCode: string - supportedDocuments: Array - partners: Array - states: Array -} - -export interface TransakPartner { - name: string - isCardPayment: boolean - currencyCode: string -} - -export interface TransakState { - code: string - name: string - isAllowed: boolean -} - -export interface TransakCryptoCurrency { - id: string - coinID: string - address: string - addressAdditionalData: any - createdAt: string - decimals: number - image: TransakCryptoCurrencyImage - isAllowed: boolean - isPopular: boolean - isStable: boolean - name: string - roundOff: number - symbol: string - isIgnorePriceVerification: boolean - imageBk: TransakCryptoCurrencyImage - kycCountriesNotSupported: Array - network: TransakCryptoCurrencyNetwork - uniqueID: string - tokenType: string - tokenIdentifier: string - isPayInAllowed: boolean - isSuspended: boolean -} - -export interface TransakCryptoCurrencyImage { - large: string - small: string - thumb: string -} - -export interface TransakCryptoCurrencyNetwork { - name: string - fiatCurrenciesNotSupported: Array - chainID: string -} - -export interface TransakCryptoCurrencyNetworkFiatNotSupported { - fiatCurrency: string - paymentMethod: string -} - -export interface TransakFiatCurrency { - symbol: string - supportingCountries: Array - logoSymbol: string - name: string - paymentOptions: Array - isPopular: boolean - isAllowed: boolean - roundOff: number - isPayOutAllowed: boolean - defaultCountryForNFT: string - icon: string - displayMessage: string -} - -export interface TransakFiatCurrencyPaymentOption { - name: string - id: string - isNftAllowed: boolean - isNonCustodial: boolean - processingTime: string - displayText: boolean - icon: string - limitCurrency: string - isActive: boolean - provider: string - maxAmount: number - minAmount: number - defaultAmount: number - isConverted: boolean - visaPayoutCountries: Array - mastercardPayoutCountries: Array - isPayOutAllowed: boolean - minAmountForPayOut: number - maxAmountForPayOut: number - defaultAmountForPayOut: number -} - -export interface TransakPrice { - quoteID: string - conversionPrice: number - marketConversionPrice: number - slippage: number - fiatCurrency: string - cryptoCurrency: string - paymentMethod: string - fiatAmount: number - cryptoAmount: number - isBuyOrSell: string - network: string - feeDecimal: number - totalFee: number - feeBreakdown: Array - nonce: number - cryptoLiquidityProvider: string - notes: Array -} - -export interface TransakPriceFeeBreakdown { - Name: string - Value: number - ID: string - Ids: Array -} - -export interface TransakGetPriceParams { - fiatCurrency: string - cryptoCurrency: string - isBuyOrSell: TransakBuySell - network: string - paymentMethod: string - fiatAmount: number - cryptoAmount: number - quoteCountryCode: string -} - -export interface TransakNFTData { - imageUrl: string - nftName: string - collectionAddress: string - tokenIds: Array - prices: Array - quantity: number - nftType: string -} - -export interface TransakGetWidgetURLParams { - targetContractAddress?: string - isNft?: boolean - calldata?: string - cryptoCurrencyCode?: string - estimatedGasLimit?: number - nftData: Array - walletAddress?: string - disableWalletAddressForm?: boolean - partnerOrderId?: string - network?: string - referrerDomain?: string - fiatAmount?: string - fiatCurrency?: string - defaultFiatAmount?: string - defaultCryptoCurrency?: string - cryptoCurrencyList?: string - networks?: string -} - -export interface TransakChain { - name: string - chainId: number -} - -export interface CheckoutOptionsPrimaryParams { - quantity: string - tokenId: string -} - -export interface CheckoutOptionsSecondaryParams { - collectionAddress: string - marketplaceAddress: string - currencyAddress: string - priceAmount: string - tokenId: string -} - -export interface CheckoutOptions { - crypto: CheckoutOptionCrypto - swap: Array - nftCheckout: Array - onRamp: Array -} - -export interface FortePayCreateIntent { - blockchain: string - buyer: FortePayBuyer - currency: string - idempotencyKey: string - items: Array - seller: FortePaySeller - transactionType: string -} - -export interface FortePayBuyer { - wallet: FortePayWallet - email: string - id: string -} - -export interface FortePaySeller { - wallet: FortePayWallet -} - -export interface FortePayWallet { - address: string - blockchain: string -} - -export interface FortePayItem { - amount: string - id: string - imageUrl: string - listingData: FortePayItemListingData - nftData: FortePayItemNFTData - mintData: FortePayItemMintData - title: string -} - -export interface FortePayItemListingData { - orderHash: string - protocol: string - protocolAddress: string - auctionHouse: string - tokenAddress: string - calldata: string - payToAddress: string - structuredCalldata: any -} - -export interface FortePayItemNFTData { - contractAddress: string - tokenId: string -} - -export interface FortePayItemMintData { - nonce: string - protocol: string - protocolAddress: string - signature: string - tokenIds: Array - calldata: string - payToAddress: string - tokenContractAddress: string - structuredCalldata: any -} - -export interface FortePayIntent { - flow: string - widgetData: string - paymentIntentId: string - notes: Array -} - -export interface FortePaymentStatus { - paymentIntentId: string - status: string -} - -export interface CrossChainFee { - providerFee: string - trailsSwapFee: string - providerFeeUSD: number - trailsSwapFeeUSD: number - totalFeeAmount: string - totalFeeUSD: number -} - -export interface MetaTxnFeeDetail { - metaTxnID: string - estimatedGasLimit: string - feeNative: string -} - -export interface ChainExecuteQuote { - chainId: string - totalGasLimit: string - gasPrice: string - totalFeeAmount: string - nativeTokenSymbol: string - nativeTokenPrice?: string - metaTxnFeeDetails: Array - totalFeeUSD?: string -} - -export interface ExecuteQuote { - chainQuotes: Array -} - -export interface PingRequest {} - -export interface PingResponse { - status: boolean -} - -export interface VersionRequest {} - -export interface VersionResponse { - version: Version -} - -export interface RuntimeStatusRequest {} - -export interface RuntimeStatusResponse { - status: RuntimeStatus -} - -export interface ClockRequest {} - -export interface ClockResponse { - serverTime: string -} - -export interface GetSequenceContextRequest {} - -export interface GetSequenceContextResponse { - data: SequenceContext -} - -export interface GetAuthTokenRequest { - ewtString: string - testnetMode?: boolean -} - -export interface GetAuthTokenResponse { - status: boolean - jwtToken: string - address: string - user?: User -} - -export interface GetAuthToken2Request { - ewtString: string - chainID: string -} - -export interface GetAuthToken2Response { - status: boolean - jwtToken: string - address: string - user?: User -} - -export interface SendPasswordlessLinkRequest { - email: string - redirectUri: string - intent: string -} - -export interface SendPasswordlessLinkResponse { - status: boolean -} - -export interface RegisterPublicKeyRequest { - publicKey: PublicKey -} - -export interface RegisterPublicKeyResponse { - status: boolean -} - -export interface GetPublicKeyRequest { - id: string -} - -export interface GetPublicKeyResponse { - publicKey: PublicKey -} - -export interface FriendListRequest { - nickname?: string - page?: Page -} - -export interface FriendListResponse { - page: Page - friends: Array -} - -export interface GetFriendByAddressRequest { - friendAddress: string -} - -export interface GetFriendByAddressResponse { - status: boolean - friend: Friend -} - -export interface SearchFriendsRequest { - filterUsername: string - page?: Page -} - -export interface SearchFriendsResponse { - friends: Array -} - -export interface AddFriendRequest { - friendAddress: string - optionalNickname?: string -} - -export interface AddFriendResponse { - status: boolean - friend?: Friend -} - -export interface UpdateFriendNicknameRequest { - friendAddress: string - nickname: string -} - -export interface UpdateFriendNicknameResponse { - status: boolean - friend?: Friend -} - -export interface RemoveFriendRequest { - friendAddress: string -} - -export interface RemoveFriendResponse { - status: boolean -} - -export interface ContractCallRequest { - chainID: string - contract: string - inputExpr: string - outputExpr: string - args: Array -} - -export interface ContractCallResponse { - returns: Array -} - -export interface DecodeContractCallRequest { - callData: string -} - -export interface DecodeContractCallResponse { - call: ContractCall -} - -export interface LookupContractCallSelectorsRequest { - selectors: Array -} - -export interface LookupContractCallSelectorsResponse { - signatures: Array> -} - -export interface UserStorageFetchRequest { - key: string -} - -export interface UserStorageFetchResponse { - object: any -} - -export interface UserStorageSaveRequest { - key: string - object: any -} - -export interface UserStorageSaveResponse { - ok: boolean -} - -export interface UserStorageDeleteRequest { - key: string -} - -export interface UserStorageDeleteResponse { - ok: boolean -} - -export interface UserStorageFetchAllRequest { - keys?: Array -} - -export interface UserStorageFetchAllResponse { - objects: { [key: string]: any } -} - -export interface GetMoonpayLinkRequest { - url: string -} - -export interface GetMoonpayLinkResponse { - signedUrl: string -} - -export interface ResolveENSAddressRequest { - ens: string -} - -export interface ResolveENSAddressResponse { - address: string - ok: boolean -} - -export interface IsValidSignatureRequest { - chainId: string - walletAddress: string - digest: string - signature: string -} - -export interface IsValidSignatureResponse { - isValid: boolean -} - -export interface IsValidMessageSignatureRequest { - chainId: string - walletAddress: string - message: string - signature: string -} - -export interface IsValidMessageSignatureResponse { - isValid: boolean -} - -export interface IsValidTypedDataSignatureRequest { - chainId: string - walletAddress: string - typedData: any - signature: string -} - -export interface IsValidTypedDataSignatureResponse { - isValid: boolean -} - -export interface IsValidETHAuthProofRequest { - chainId: string - walletAddress: string - ethAuthProofString: string -} - -export interface IsValidETHAuthProofResponse { - isValid: boolean -} - -export interface GetOnRampURLRequest { - chainId: string -} - -export interface GetOnRampURLResponse { - url: string -} - -export interface TransakGetCountriesRequest {} - -export interface TransakGetCountriesResponse { - regions: Array -} - -export interface TransakGetCryptoCurrenciesRequest {} - -export interface TransakGetCryptoCurrenciesResponse { - currencies: Array -} - -export interface TransakGetFiatCurrenciesRequest {} - -export interface TransakGetFiatCurrenciesResponse { - currencies: Array -} - -export interface TransakGetPriceRequest { - params: TransakGetPriceParams -} - -export interface TransakGetPriceResponse { - price: TransakPrice -} - -export interface TransakGetSupportedNFTCheckoutChainsRequest {} - -export interface TransakGetSupportedNFTCheckoutChainsResponse { - chains: Array -} - -export interface TransakGetWidgetURLRequest { - params: TransakGetWidgetURLParams -} - -export interface TransakGetWidgetURLResponse { - url: string -} - -export interface GetCoinPricesRequest { - tokens: Array -} - -export interface GetCoinPricesResponse { - tokenPrices: Array -} - -export interface GetCollectiblePricesRequest { - tokens: Array -} - -export interface GetCollectiblePricesResponse { - tokenPrices: Array -} - -export interface GetExchangeRateRequest { - toCurrency: string -} - -export interface GetExchangeRateResponse { - exchangeRate: ExchangeRate -} - -export interface MemoryStoreRequest { - key: string - value: string -} - -export interface MemoryStoreResponse { - ok: boolean -} - -export interface MemoryLoadRequest { - key: string -} - -export interface MemoryLoadResponse { - value: string -} - -export interface GetInviteInfoRequest {} - -export interface GetInviteInfoResponse { - inviteInfo: InviteInfo -} - -export interface IsValidAccessCodeRequest { - accessCode: string -} - -export interface IsValidAccessCodeResponse { - status: boolean -} - -export interface InternalClaimAccessCodeRequest { - address: string - accessCode: string -} - -export interface InternalClaimAccessCodeResponse { - status: boolean -} - -export interface BlockNumberAtTimeRequest { - chainId: number - timestamps: Array -} - -export interface BlockNumberAtTimeResponse { - blocks: Array -} - -export interface PaperSessionSecretRequest { - chainName: string - contractAddress: string - paramsJson: string - contractType: string -} - -export interface PaperSessionSecretResponse { - secret: string -} - -export interface PaperSessionSecret2Request { - chainName: string - contractAddress: string - paramsJson: string - abi: string -} - -export interface PaperSessionSecret2Response { - secret: string -} - -export interface LinkWalletRequest { - parentWalletAddress: string - parentWalletMessage: string - parentWalletSignature: string - linkedWalletAddress: string - linkedWalletMessage: string - linkedWalletSignature: string - signatureChainId: string - linkedWalletType?: string -} - -export interface LinkWalletResponse { - status: boolean -} - -export interface GetLinkedWalletsRequest { - parentWalletAddress: string - parentWalletMessage: string - parentWalletSignature: string - signatureChainId: string -} - -export interface GetLinkedWalletsResponse { - linkedWallets: Array -} - -export interface RemoveLinkedWalletRequest { - parentWalletAddress: string - parentWalletMessage: string - parentWalletSignature: string - linkedWalletAddress: string - signatureChainId: string -} - -export interface RemoveLinkedWalletResponse { - status: boolean -} - -export interface GenerateWaaSVerificationURLRequest { - walletAddress: string -} - -export interface GenerateWaaSVerificationURLResponse { - nonce: string - verificationURL: string -} - -export interface ValidateWaaSVerificationNonceRequest { - nonce: string - signature: string - sessionId: string - chainId: string -} - -export interface ValidateWaaSVerificationNonceResponse { - walletAddress: string -} - -export interface ListAdoptedWalletsRequest { - page?: Page -} - -export interface ListAdoptedWalletsResponse { - page: Page - wallets: Array -} - -export interface GetLifiChainsRequest {} - -export interface GetLifiChainsResponse { - chains: Array -} - -export interface GetLifiTokensRequest { - chainIds: Array -} - -export interface GetLifiTokensResponse { - tokens: Array -} - -export interface GetLifiSwapRoutesRequest { - params: GetLifiSwapRouteParams -} - -export interface GetLifiSwapRoutesResponse { - routes: Array -} - -export interface GetLifiSwapQuoteRequest { - params: GetLifiSwapQuoteParams -} - -export interface GetLifiSwapQuoteResponse { - quote: LifiSwapQuote -} - -export interface ListCurrencyGroupsRequest {} - -export interface ListCurrencyGroupsResponse { - currencyGroups: Array -} - -export interface AddOffchainInventoryRequest { - inventory: OffchainInventory -} - -export interface AddOffchainInventoryResponse { - inventoryId: number -} - -export interface GetOffchainInventoryRequest { - inventoryId: number -} - -export interface GetOffchainInventoryResponse { - inventory: OffchainInventory -} - -export interface ListOffchainInventoriesRequest { - projectId: number -} - -export interface ListOffchainInventoriesResponse { - inventory: Array -} - -export interface UpdateOffchainInventoryRequest { - inventory: OffchainInventory -} - -export interface UpdateOffchainInventoryResponse {} - -export interface DeleteOffchainInventoryRequest { - inventoryId: number -} - -export interface DeleteOffchainInventoryResponse { - ok: boolean -} - -export interface RequestOffchainPaymentRequest { - inventoryId: number - recipient: string - chainId?: number - tokenAddress?: string -} - -export interface RequestOffchainPaymentResponse { - payment: PaymentResponse -} - -export interface ListOffchainPaymentsRequest { - inventoryId: number - page?: Page -} - -export interface ListOffchainPaymentsResponse { - page: Page - payments: Array -} - -export interface SavePackRequest { - pack: Pack -} - -export interface SavePackResponse { - merkleRoot: string -} - -export interface GetPackRequest { - contractAddress: string - packId: string - chainId: number -} - -export interface GetPackResponse { - pack: Pack -} - -export interface GetPackIdsRequest { - contractAddress: string - chainId: number -} - -export interface GetPackIdsResponse { - packIds: Array -} - -export interface DeletePackRequest { - contractAddress: string - packId: string - chainId: number -} - -export interface DeletePackResponse { - status: boolean -} - -export interface UpdatePackContentRequest { - pack: Pack -} - -export interface UpdatePackContentResponse { - merkleRoot: string -} - -export interface GetRevealTxDataRequest { - contractAddress: string - packId: string - chainId: number - userAddress: string -} - -export interface GetRevealTxDataResponse { - txData: string -} - -export interface CheckoutOptionsPrimaryRequest { - chainId: number - wallet: string - contractAddress: string - collectionAddress: string - params: Array -} - -export interface CheckoutOptionsPrimaryResponse { - options: CheckoutOptions -} - -export interface CheckoutOptionsSecondaryRequest { - chainId: number - wallet: string - params: Array -} - -export interface CheckoutOptionsSecondaryResponse { - options: CheckoutOptions -} - -export interface CheckoutOptionsGetTransakContractIDRequest { - chainId: number - contractAddress: string -} - -export interface CheckoutOptionsGetTransakContractIDResponse { - contractId: string -} - -export interface FortePayCreateIntentRequest { - intent: FortePayCreateIntent -} - -export interface FortePayCreateIntentResponse { - resp: FortePayIntent -} - -export interface FortePayGetPaymentStatusesRequest { - paymentIntentIds: Array -} - -export interface FortePayGetPaymentStatusesResponse { - statuses: Array -} - -// -// Client -// - -export class API implements APIClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/API/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - ping: () => ['API', 'ping'] as const, - version: () => ['API', 'version'] as const, - runtimeStatus: () => ['API', 'runtimeStatus'] as const, - clock: () => ['API', 'clock'] as const, - getSequenceContext: () => ['API', 'getSequenceContext'] as const, - getAuthToken: (req: GetAuthTokenRequest) => ['API', 'getAuthToken', req] as const, - getAuthToken2: (req: GetAuthToken2Request) => ['API', 'getAuthToken2', req] as const, - sendPasswordlessLink: (req: SendPasswordlessLinkRequest) => ['API', 'sendPasswordlessLink', req] as const, - registerPublicKey: (req: RegisterPublicKeyRequest) => ['API', 'registerPublicKey', req] as const, - getPublicKey: (req: GetPublicKeyRequest) => ['API', 'getPublicKey', req] as const, - friendList: (req: FriendListRequest) => ['API', 'friendList', req] as const, - getFriendByAddress: (req: GetFriendByAddressRequest) => ['API', 'getFriendByAddress', req] as const, - searchFriends: (req: SearchFriendsRequest) => ['API', 'searchFriends', req] as const, - addFriend: (req: AddFriendRequest) => ['API', 'addFriend', req] as const, - updateFriendNickname: (req: UpdateFriendNicknameRequest) => ['API', 'updateFriendNickname', req] as const, - removeFriend: (req: RemoveFriendRequest) => ['API', 'removeFriend', req] as const, - contractCall: (req: ContractCallRequest) => ['API', 'contractCall', req] as const, - decodeContractCall: (req: DecodeContractCallRequest) => ['API', 'decodeContractCall', req] as const, - lookupContractCallSelectors: (req: LookupContractCallSelectorsRequest) => - ['API', 'lookupContractCallSelectors', req] as const, - userStorageFetch: (req: UserStorageFetchRequest) => ['API', 'userStorageFetch', req] as const, - userStorageSave: (req: UserStorageSaveRequest) => ['API', 'userStorageSave', req] as const, - userStorageDelete: (req: UserStorageDeleteRequest) => ['API', 'userStorageDelete', req] as const, - userStorageFetchAll: (req: UserStorageFetchAllRequest) => ['API', 'userStorageFetchAll', req] as const, - getMoonpayLink: (req: GetMoonpayLinkRequest) => ['API', 'getMoonpayLink', req] as const, - resolveENSAddress: (req: ResolveENSAddressRequest) => ['API', 'resolveENSAddress', req] as const, - isValidSignature: (req: IsValidSignatureRequest) => ['API', 'isValidSignature', req] as const, - isValidMessageSignature: (req: IsValidMessageSignatureRequest) => ['API', 'isValidMessageSignature', req] as const, - isValidTypedDataSignature: (req: IsValidTypedDataSignatureRequest) => - ['API', 'isValidTypedDataSignature', req] as const, - isValidETHAuthProof: (req: IsValidETHAuthProofRequest) => ['API', 'isValidETHAuthProof', req] as const, - getOnRampURL: (req: GetOnRampURLRequest) => ['API', 'getOnRampURL', req] as const, - transakGetCountries: () => ['API', 'transakGetCountries'] as const, - transakGetCryptoCurrencies: () => ['API', 'transakGetCryptoCurrencies'] as const, - transakGetFiatCurrencies: () => ['API', 'transakGetFiatCurrencies'] as const, - transakGetPrice: (req: TransakGetPriceRequest) => ['API', 'transakGetPrice', req] as const, - transakGetSupportedNFTCheckoutChains: () => ['API', 'transakGetSupportedNFTCheckoutChains'] as const, - transakGetWidgetURL: (req: TransakGetWidgetURLRequest) => ['API', 'transakGetWidgetURL', req] as const, - getCoinPrices: (req: GetCoinPricesRequest) => ['API', 'getCoinPrices', req] as const, - getCollectiblePrices: (req: GetCollectiblePricesRequest) => ['API', 'getCollectiblePrices', req] as const, - getExchangeRate: (req: GetExchangeRateRequest) => ['API', 'getExchangeRate', req] as const, - memoryStore: (req: MemoryStoreRequest) => ['API', 'memoryStore', req] as const, - memoryLoad: (req: MemoryLoadRequest) => ['API', 'memoryLoad', req] as const, - getInviteInfo: () => ['API', 'getInviteInfo'] as const, - isValidAccessCode: (req: IsValidAccessCodeRequest) => ['API', 'isValidAccessCode', req] as const, - internalClaimAccessCode: (req: InternalClaimAccessCodeRequest) => ['API', 'internalClaimAccessCode', req] as const, - blockNumberAtTime: (req: BlockNumberAtTimeRequest) => ['API', 'blockNumberAtTime', req] as const, - paperSessionSecret: (req: PaperSessionSecretRequest) => ['API', 'paperSessionSecret', req] as const, - paperSessionSecret2: (req: PaperSessionSecret2Request) => ['API', 'paperSessionSecret2', req] as const, - linkWallet: (req: LinkWalletRequest) => ['API', 'linkWallet', req] as const, - getLinkedWallets: (req: GetLinkedWalletsRequest) => ['API', 'getLinkedWallets', req] as const, - removeLinkedWallet: (req: RemoveLinkedWalletRequest) => ['API', 'removeLinkedWallet', req] as const, - generateWaaSVerificationURL: (req: GenerateWaaSVerificationURLRequest) => - ['API', 'generateWaaSVerificationURL', req] as const, - validateWaaSVerificationNonce: (req: ValidateWaaSVerificationNonceRequest) => - ['API', 'validateWaaSVerificationNonce', req] as const, - listAdoptedWallets: (req: ListAdoptedWalletsRequest) => ['API', 'listAdoptedWallets', req] as const, - getLifiChains: () => ['API', 'getLifiChains'] as const, - getLifiTokens: (req: GetLifiTokensRequest) => ['API', 'getLifiTokens', req] as const, - getLifiSwapRoutes: (req: GetLifiSwapRoutesRequest) => ['API', 'getLifiSwapRoutes', req] as const, - getLifiSwapQuote: (req: GetLifiSwapQuoteRequest) => ['API', 'getLifiSwapQuote', req] as const, - listCurrencyGroups: () => ['API', 'listCurrencyGroups'] as const, - addOffchainInventory: (req: AddOffchainInventoryRequest) => ['API', 'addOffchainInventory', req] as const, - getOffchainInventory: (req: GetOffchainInventoryRequest) => ['API', 'getOffchainInventory', req] as const, - listOffchainInventories: (req: ListOffchainInventoriesRequest) => ['API', 'listOffchainInventories', req] as const, - updateOffchainInventory: (req: UpdateOffchainInventoryRequest) => ['API', 'updateOffchainInventory', req] as const, - deleteOffchainInventory: (req: DeleteOffchainInventoryRequest) => ['API', 'deleteOffchainInventory', req] as const, - requestOffchainPayment: (req: RequestOffchainPaymentRequest) => ['API', 'requestOffchainPayment', req] as const, - listOffchainPayments: (req: ListOffchainPaymentsRequest) => ['API', 'listOffchainPayments', req] as const, - savePack: (req: SavePackRequest) => ['API', 'savePack', req] as const, - getPack: (req: GetPackRequest) => ['API', 'getPack', req] as const, - getPackIds: (req: GetPackIdsRequest) => ['API', 'getPackIds', req] as const, - deletePack: (req: DeletePackRequest) => ['API', 'deletePack', req] as const, - updatePackContent: (req: UpdatePackContentRequest) => ['API', 'updatePackContent', req] as const, - getRevealTxData: (req: GetRevealTxDataRequest) => ['API', 'getRevealTxData', req] as const, - checkoutOptionsPrimary: (req: CheckoutOptionsPrimaryRequest) => ['API', 'checkoutOptionsPrimary', req] as const, - checkoutOptionsSecondary: (req: CheckoutOptionsSecondaryRequest) => - ['API', 'checkoutOptionsSecondary', req] as const, - checkoutOptionsGetTransakContractID: (req: CheckoutOptionsGetTransakContractIDRequest) => - ['API', 'checkoutOptionsGetTransakContractID', req] as const, - fortePayCreateIntent: (req: FortePayCreateIntentRequest) => ['API', 'fortePayCreateIntent', req] as const, - fortePayGetPaymentStatuses: (req: FortePayGetPaymentStatusesRequest) => - ['API', 'fortePayGetPaymentStatuses', req] as const, - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PingResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'VersionResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RuntimeStatusResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - clock = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Clock'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ClockResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetSequenceContext'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetSequenceContextResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getAuthToken = (req: GetAuthTokenRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetAuthToken'), - createHttpRequest(JsonEncode(req, 'GetAuthTokenRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetAuthTokenResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getAuthToken2 = ( - req: GetAuthToken2Request, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetAuthToken2'), - createHttpRequest(JsonEncode(req, 'GetAuthToken2Request'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetAuthToken2Response') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - sendPasswordlessLink = ( - req: SendPasswordlessLinkRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SendPasswordlessLink'), - createHttpRequest(JsonEncode(req, 'SendPasswordlessLinkRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SendPasswordlessLinkResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - registerPublicKey = ( - req: RegisterPublicKeyRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RegisterPublicKey'), - createHttpRequest(JsonEncode(req, 'RegisterPublicKeyRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RegisterPublicKeyResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getPublicKey = (req: GetPublicKeyRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetPublicKey'), - createHttpRequest(JsonEncode(req, 'GetPublicKeyRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetPublicKeyResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - friendList = (req: FriendListRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('FriendList'), - createHttpRequest(JsonEncode(req, 'FriendListRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FriendListResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getFriendByAddress = ( - req: GetFriendByAddressRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetFriendByAddress'), - createHttpRequest(JsonEncode(req, 'GetFriendByAddressRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetFriendByAddressResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchFriends = ( - req: SearchFriendsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchFriends'), - createHttpRequest(JsonEncode(req, 'SearchFriendsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchFriendsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addFriend = (req: AddFriendRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('AddFriend'), - createHttpRequest(JsonEncode(req, 'AddFriendRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddFriendResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateFriendNickname = ( - req: UpdateFriendNicknameRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdateFriendNickname'), - createHttpRequest(JsonEncode(req, 'UpdateFriendNicknameRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateFriendNicknameResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeFriend = (req: RemoveFriendRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('RemoveFriend'), - createHttpRequest(JsonEncode(req, 'RemoveFriendRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveFriendResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - contractCall = (req: ContractCallRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('ContractCall'), - createHttpRequest(JsonEncode(req, 'ContractCallRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ContractCallResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - decodeContractCall = ( - req: DecodeContractCallRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DecodeContractCall'), - createHttpRequest(JsonEncode(req, 'DecodeContractCallRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DecodeContractCallResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - lookupContractCallSelectors = ( - req: LookupContractCallSelectorsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('LookupContractCallSelectors'), - createHttpRequest(JsonEncode(req, 'LookupContractCallSelectorsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'LookupContractCallSelectorsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - userStorageFetch = ( - req: UserStorageFetchRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UserStorageFetch'), - createHttpRequest(JsonEncode(req, 'UserStorageFetchRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UserStorageFetchResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - userStorageSave = ( - req: UserStorageSaveRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UserStorageSave'), - createHttpRequest(JsonEncode(req, 'UserStorageSaveRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UserStorageSaveResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - userStorageDelete = ( - req: UserStorageDeleteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UserStorageDelete'), - createHttpRequest(JsonEncode(req, 'UserStorageDeleteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UserStorageDeleteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - userStorageFetchAll = ( - req: UserStorageFetchAllRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UserStorageFetchAll'), - createHttpRequest(JsonEncode(req, 'UserStorageFetchAllRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UserStorageFetchAllResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMoonpayLink = ( - req: GetMoonpayLinkRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetMoonpayLink'), - createHttpRequest(JsonEncode(req, 'GetMoonpayLinkRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMoonpayLinkResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - resolveENSAddress = ( - req: ResolveENSAddressRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ResolveENSAddress'), - createHttpRequest(JsonEncode(req, 'ResolveENSAddressRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ResolveENSAddressResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isValidSignature = ( - req: IsValidSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsValidSignature'), - createHttpRequest(JsonEncode(req, 'IsValidSignatureRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsValidSignatureResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isValidMessageSignature = ( - req: IsValidMessageSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsValidMessageSignature'), - createHttpRequest(JsonEncode(req, 'IsValidMessageSignatureRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsValidMessageSignatureResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isValidTypedDataSignature = ( - req: IsValidTypedDataSignatureRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsValidTypedDataSignature'), - createHttpRequest(JsonEncode(req, 'IsValidTypedDataSignatureRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsValidTypedDataSignatureResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isValidETHAuthProof = ( - req: IsValidETHAuthProofRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsValidETHAuthProof'), - createHttpRequest(JsonEncode(req, 'IsValidETHAuthProofRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsValidETHAuthProofResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getOnRampURL = (req: GetOnRampURLRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetOnRampURL'), - createHttpRequest(JsonEncode(req, 'GetOnRampURLRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetOnRampURLResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetCountries = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('TransakGetCountries'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'TransakGetCountriesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetCryptoCurrencies = ( - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('TransakGetCryptoCurrencies'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'TransakGetCryptoCurrenciesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetFiatCurrencies = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('TransakGetFiatCurrencies'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'TransakGetFiatCurrenciesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetPrice = ( - req: TransakGetPriceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('TransakGetPrice'), - createHttpRequest(JsonEncode(req, 'TransakGetPriceRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'TransakGetPriceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetSupportedNFTCheckoutChains = ( - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('TransakGetSupportedNFTCheckoutChains'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode( - _data, - 'TransakGetSupportedNFTCheckoutChainsResponse', - ) - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - transakGetWidgetURL = ( - req: TransakGetWidgetURLRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('TransakGetWidgetURL'), - createHttpRequest(JsonEncode(req, 'TransakGetWidgetURLRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'TransakGetWidgetURLResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCoinPrices = ( - req: GetCoinPricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetCoinPrices'), - createHttpRequest(JsonEncode(req, 'GetCoinPricesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetCoinPricesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectiblePrices = ( - req: GetCollectiblePricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetCollectiblePrices'), - createHttpRequest(JsonEncode(req, 'GetCollectiblePricesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetCollectiblePricesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getExchangeRate = ( - req: GetExchangeRateRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetExchangeRate'), - createHttpRequest(JsonEncode(req, 'GetExchangeRateRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetExchangeRateResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - memoryStore = (req: MemoryStoreRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('MemoryStore'), - createHttpRequest(JsonEncode(req, 'MemoryStoreRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'MemoryStoreResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - memoryLoad = (req: MemoryLoadRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('MemoryLoad'), - createHttpRequest(JsonEncode(req, 'MemoryLoadRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'MemoryLoadResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getInviteInfo = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetInviteInfo'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetInviteInfoResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isValidAccessCode = ( - req: IsValidAccessCodeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsValidAccessCode'), - createHttpRequest(JsonEncode(req, 'IsValidAccessCodeRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsValidAccessCodeResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - internalClaimAccessCode = ( - req: InternalClaimAccessCodeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('InternalClaimAccessCode'), - createHttpRequest(JsonEncode(req, 'InternalClaimAccessCodeRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'InternalClaimAccessCodeResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - blockNumberAtTime = ( - req: BlockNumberAtTimeRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('BlockNumberAtTime'), - createHttpRequest(JsonEncode(req, 'BlockNumberAtTimeRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'BlockNumberAtTimeResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - paperSessionSecret = ( - req: PaperSessionSecretRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PaperSessionSecret'), - createHttpRequest(JsonEncode(req, 'PaperSessionSecretRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PaperSessionSecretResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - paperSessionSecret2 = ( - req: PaperSessionSecret2Request, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PaperSessionSecret2'), - createHttpRequest(JsonEncode(req, 'PaperSessionSecret2Request'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PaperSessionSecret2Response') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - linkWallet = (req: LinkWalletRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('LinkWallet'), - createHttpRequest(JsonEncode(req, 'LinkWalletRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'LinkWalletResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLinkedWallets = ( - req: GetLinkedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetLinkedWallets'), - createHttpRequest(JsonEncode(req, 'GetLinkedWalletsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetLinkedWalletsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeLinkedWallet = ( - req: RemoveLinkedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RemoveLinkedWallet'), - createHttpRequest(JsonEncode(req, 'RemoveLinkedWalletRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveLinkedWalletResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateWaaSVerificationURL = ( - req: GenerateWaaSVerificationURLRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GenerateWaaSVerificationURL'), - createHttpRequest(JsonEncode(req, 'GenerateWaaSVerificationURLRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GenerateWaaSVerificationURLResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - validateWaaSVerificationNonce = ( - req: ValidateWaaSVerificationNonceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ValidateWaaSVerificationNonce'), - createHttpRequest(JsonEncode(req, 'ValidateWaaSVerificationNonceRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ValidateWaaSVerificationNonceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listAdoptedWallets = ( - req: ListAdoptedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListAdoptedWallets'), - createHttpRequest(JsonEncode(req, 'ListAdoptedWalletsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListAdoptedWalletsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLifiChains = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetLifiChains'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetLifiChainsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLifiTokens = ( - req: GetLifiTokensRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetLifiTokens'), - createHttpRequest(JsonEncode(req, 'GetLifiTokensRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetLifiTokensResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLifiSwapRoutes = ( - req: GetLifiSwapRoutesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetLifiSwapRoutes'), - createHttpRequest(JsonEncode(req, 'GetLifiSwapRoutesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetLifiSwapRoutesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLifiSwapQuote = ( - req: GetLifiSwapQuoteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetLifiSwapQuote'), - createHttpRequest(JsonEncode(req, 'GetLifiSwapQuoteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetLifiSwapQuoteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCurrencyGroups = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListCurrencyGroups'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListCurrencyGroupsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addOffchainInventory = ( - req: AddOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('AddOffchainInventory'), - createHttpRequest(JsonEncode(req, 'AddOffchainInventoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddOffchainInventoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getOffchainInventory = ( - req: GetOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetOffchainInventory'), - createHttpRequest(JsonEncode(req, 'GetOffchainInventoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetOffchainInventoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listOffchainInventories = ( - req: ListOffchainInventoriesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListOffchainInventories'), - createHttpRequest(JsonEncode(req, 'ListOffchainInventoriesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListOffchainInventoriesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateOffchainInventory = ( - req: UpdateOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdateOffchainInventory'), - createHttpRequest(JsonEncode(req, 'UpdateOffchainInventoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateOffchainInventoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteOffchainInventory = ( - req: DeleteOffchainInventoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteOffchainInventory'), - createHttpRequest(JsonEncode(req, 'DeleteOffchainInventoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteOffchainInventoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - requestOffchainPayment = ( - req: RequestOffchainPaymentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RequestOffchainPayment'), - createHttpRequest(JsonEncode(req, 'RequestOffchainPaymentRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RequestOffchainPaymentResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listOffchainPayments = ( - req: ListOffchainPaymentsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListOffchainPayments'), - createHttpRequest(JsonEncode(req, 'ListOffchainPaymentsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListOffchainPaymentsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - savePack = (req: SavePackRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('SavePack'), - createHttpRequest(JsonEncode(req, 'SavePackRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SavePackResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getPack = (req: GetPackRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetPack'), createHttpRequest(JsonEncode(req, 'GetPackRequest'), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetPackResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getPackIds = (req: GetPackIdsRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetPackIds'), - createHttpRequest(JsonEncode(req, 'GetPackIdsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetPackIdsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deletePack = (req: DeletePackRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('DeletePack'), - createHttpRequest(JsonEncode(req, 'DeletePackRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeletePackResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updatePackContent = ( - req: UpdatePackContentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdatePackContent'), - createHttpRequest(JsonEncode(req, 'UpdatePackContentRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdatePackContentResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getRevealTxData = ( - req: GetRevealTxDataRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetRevealTxData'), - createHttpRequest(JsonEncode(req, 'GetRevealTxDataRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetRevealTxDataResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - checkoutOptionsPrimary = ( - req: CheckoutOptionsPrimaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('CheckoutOptionsPrimary'), - createHttpRequest(JsonEncode(req, 'CheckoutOptionsPrimaryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CheckoutOptionsPrimaryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - checkoutOptionsSecondary = ( - req: CheckoutOptionsSecondaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('CheckoutOptionsSecondary'), - createHttpRequest(JsonEncode(req, 'CheckoutOptionsSecondaryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CheckoutOptionsSecondaryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - checkoutOptionsGetTransakContractID = ( - req: CheckoutOptionsGetTransakContractIDRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('CheckoutOptionsGetTransakContractID'), - createHttpRequest(JsonEncode(req, 'CheckoutOptionsGetTransakContractIDRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode( - _data, - 'CheckoutOptionsGetTransakContractIDResponse', - ) - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - fortePayCreateIntent = ( - req: FortePayCreateIntentRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('FortePayCreateIntent'), - createHttpRequest(JsonEncode(req, 'FortePayCreateIntentRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FortePayCreateIntentResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - fortePayGetPaymentStatuses = ( - req: FortePayGetPaymentStatusesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('FortePayGetPaymentStatuses'), - createHttpRequest(JsonEncode(req, 'FortePayGetPaymentStatusesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FortePayGetPaymentStatusesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { - ...headers, - 'Content-Type': 'application/json', - [WebrpcHeader]: WebrpcHeaderValue, - } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -export const JsonEncode = (obj: T, _typ: string = ''): string => { - return JSON.stringify(obj) -} - -export const JsonDecode = (data: string | any, _typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return parsed as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class AbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Aborted' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request aborted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Rate-limited. Please slow down.` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 1008 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class AccessKeyNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyNotFound' - this.code = typeof error.code === 'number' ? error.code : 1101 - this.message = error.message || `Access key not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) - } -} - -export class AccessKeyMismatchError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyMismatch' - this.code = typeof error.code === 'number' ? error.code : 1102 - this.message = error.message || `Access key mismatch` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) - } -} - -export class InvalidOriginError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidOrigin' - this.code = typeof error.code === 'number' ? error.code : 1103 - this.message = error.message || `Invalid origin for Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidOriginError.prototype) - } -} - -export class InvalidServiceError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidService' - this.code = typeof error.code === 'number' ? error.code : 1104 - this.message = error.message || `Service not enabled for Access key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidServiceError.prototype) - } -} - -export class UnauthorizedUserError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnauthorizedUser' - this.code = typeof error.code === 'number' ? error.code : 1105 - this.message = error.message || `Unauthorized user` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) - } -} - -export class QuotaExceededError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaExceeded' - this.code = typeof error.code === 'number' ? error.code : 1200 - this.message = error.message || `Quota request exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaExceededError.prototype) - } -} - -export class QuotaRateLimitError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaRateLimit' - this.code = typeof error.code === 'number' ? error.code : 1201 - this.message = error.message || `Quota rate limit exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaRateLimitError.prototype) - } -} - -export class NoDefaultKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NoDefaultKey' - this.code = typeof error.code === 'number' ? error.code : 1300 - this.message = error.message || `No default access key found` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) - } -} - -export class MaxAccessKeysError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MaxAccessKeys' - this.code = typeof error.code === 'number' ? error.code : 1301 - this.message = error.message || `Access keys limit reached` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) - } -} - -export class AtLeastOneKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AtLeastOneKey' - this.code = typeof error.code === 'number' ? error.code : 1302 - this.message = error.message || `You need at least one Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Timeout' - this.code = typeof error.code === 'number' ? error.code : 1900 - this.message = error.message || `Request timed out` - this.status = typeof error.status === 'number' ? error.status : 408 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2000 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unavailable' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Unavailable resource` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class UnsupportedNetworkError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnsupportedNetwork' - this.code = typeof error.code === 'number' ? error.code : 3008 - this.message = error.message || `Unsupported network` - this.status = typeof error.status === 'number' ? error.status : 422 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnsupportedNetworkError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - AccessKeyNotFound = 'AccessKeyNotFound', - AccessKeyMismatch = 'AccessKeyMismatch', - InvalidOrigin = 'InvalidOrigin', - InvalidService = 'InvalidService', - UnauthorizedUser = 'UnauthorizedUser', - QuotaExceeded = 'QuotaExceeded', - QuotaRateLimit = 'QuotaRateLimit', - NoDefaultKey = 'NoDefaultKey', - MaxAccessKeys = 'MaxAccessKeys', - AtLeastOneKey = 'AtLeastOneKey', - Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', - Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - NotFound = 'NotFound', - UnsupportedNetwork = 'UnsupportedNetwork', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Aborted = 1005, - Geoblocked = 1006, - RateLimited = 1007, - ProjectNotFound = 1008, - AccessKeyNotFound = 1101, - AccessKeyMismatch = 1102, - InvalidOrigin = 1103, - InvalidService = 1104, - UnauthorizedUser = 1105, - QuotaExceeded = 1200, - QuotaRateLimit = 1201, - NoDefaultKey = 1300, - MaxAccessKeys = 1301, - AtLeastOneKey = 1302, - Timeout = 1900, - InvalidArgument = 2000, - Unavailable = 2002, - QueryFailed = 2003, - NotFound = 3000, - UnsupportedNetwork = 3008, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1008]: ProjectNotFoundError, - [1101]: AccessKeyNotFoundError, - [1102]: AccessKeyMismatchError, - [1103]: InvalidOriginError, - [1104]: InvalidServiceError, - [1105]: UnauthorizedUserError, - [1200]: QuotaExceededError, - [1201]: QuotaRateLimitError, - [1300]: NoDefaultKeyError, - [1301]: MaxAccessKeysError, - [1302]: AtLeastOneKeyError, - [1900]: TimeoutError, - [2000]: InvalidArgumentError, - [2002]: UnavailableError, - [2003]: QueryFailedError, - [3000]: NotFoundError, - [3008]: UnsupportedNetworkError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.31.0;gen-typescript@v0.22.5;sequence-api@v0.4.0' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/api/src/index.ts b/packages/services/api/src/index.ts deleted file mode 100644 index 14d83d8a13..0000000000 --- a/packages/services/api/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export * from './api.gen.js' - -import { API as ApiRpc } from './api.gen.js' - -export class SequenceAPIClient extends ApiRpc { - constructor( - hostname: string, - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} diff --git a/packages/services/api/tsconfig.json b/packages/services/api/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/api/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/builder/CHANGELOG.md b/packages/services/builder/CHANGELOG.md deleted file mode 100644 index e458893450..0000000000 --- a/packages/services/builder/CHANGELOG.md +++ /dev/null @@ -1,324 +0,0 @@ -# @0xsequence/builder - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 diff --git a/packages/services/builder/README.md b/packages/services/builder/README.md deleted file mode 100644 index ec20b181c2..0000000000 --- a/packages/services/builder/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/builder - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/builder/eslint.config.js b/packages/services/builder/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/builder/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/builder/package.json b/packages/services/builder/package.json deleted file mode 100644 index 1897f0ab87..0000000000 --- a/packages/services/builder/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/builder", - "version": "3.0.5", - "description": "builder sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/builder", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/builder/src/builder.gen.ts b/packages/services/builder/src/builder.gen.ts deleted file mode 100644 index a0e7049603..0000000000 --- a/packages/services/builder/src/builder.gen.ts +++ /dev/null @@ -1,714 +0,0 @@ -/* eslint-disable */ -// NOTE: this is just a subset of the builder api to scope down the -// surface area of the client. -// -// In the future we can include additional interfaces as needed. -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.22.1;gen-typescript@v0.16.2;sequence-builder@v0.1.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' - -// Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.1.0' - -// Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '461bc324d241f4df14fbf63268fde2cfe4873e3e' - -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - webrpcGenVersion: webrpcGenVersion!, - codeGenName: codeGenName!, - codeGenVersion: codeGenVersion!, - schemaName: schemaName!, - schemaVersion: schemaVersion!, - } -} - -// -// Types -// - -export interface AudienceContact { - id?: number - audienceId: number - name?: string - address: string - email?: string - userIp?: string - stage?: number - provider?: string - createdAt?: string - updatedAt?: string -} - -export interface AudienceRegistrationStatus { - totalCount: number -} - -export interface WalletProof { - address: string - message: string - signature: string - chainId: number -} - -export interface Builder { - ping(headers?: object, signal?: AbortSignal): Promise - registerAudienceContact( - args: RegisterAudienceContactArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getRegisteredAudienceContact( - args: GetRegisteredAudienceContactArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getAudienceRegistrationPublicStatus( - args: GetAudienceRegistrationPublicStatusArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - isAudienceContactRegistered( - args: IsAudienceContactRegisteredArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -export interface PingArgs {} - -export interface PingReturn { - status: boolean -} - -export interface RegisterAudienceContactArgs { - projectId: number - audienceId: number - contact: AudienceContact - walletProof: WalletProof -} - -export interface RegisterAudienceContactReturn { - ok: boolean -} -export interface GetRegisteredAudienceContactArgs { - projectId: number - audienceId: number - walletProof: WalletProof -} - -export interface GetRegisteredAudienceContactReturn { - contact: AudienceContact -} -export interface GetAudienceRegistrationPublicStatusArgs { - projectId: number - audienceId: number -} - -export interface GetAudienceRegistrationPublicStatusReturn { - status: AudienceRegistrationStatus -} -export interface IsAudienceContactRegisteredArgs { - projectId: number - audienceId: number - walletAddress: string -} - -export interface IsAudienceContactRegisteredReturn { - registered: boolean -} - -// -// Client -// -export class Builder implements Builder { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Builder/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - registerAudienceContact = ( - args: RegisterAudienceContactArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RegisterAudienceContact'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getRegisteredAudienceContact = ( - args: GetRegisteredAudienceContactArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetRegisteredAudienceContact'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - contact: _data.contact, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getAudienceRegistrationPublicStatus = ( - args: GetAudienceRegistrationPublicStatusArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetAudienceRegistrationPublicStatus'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - isAudienceContactRegistered = ( - args: IsAudienceContactRegisteredArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsAudienceContactRegistered'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - registered: _data.registered, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } -} - -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, - } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -// -// Errors -// - -export class WebrpcError extends Error { - name: string - code: number - message: string - status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) - } -} - -// Webrpc errors - -export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = 'endpoint error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = 'request failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = 'bad route', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = 'bad method', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = 'bad request', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = 'bad response', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = 'server panic', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = 'internal error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = 'client disconnected', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = 'stream lost', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = 'stream finished', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// Schema errors - -export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = 'Unauthorized access', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = 'Permission denied', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = 'Session expired', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = 'Method not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = 'Conflict with target resource', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class ServiceDisabledError extends WebrpcError { - constructor( - name: string = 'ServiceDisabled', - code: number = 1005, - message: string = 'Service disabled', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ServiceDisabledError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 2000, - message: string = 'Request timed out', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = 'Invalid argument', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = 'Resource not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class UserNotFoundError extends WebrpcError { - constructor( - name: string = 'UserNotFound', - code: number = 3001, - message: string = 'User not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UserNotFoundError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 3002, - message: string = 'Project not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class AlreadyCollaboratorError extends WebrpcError { - constructor( - name: string = 'AlreadyCollaborator', - code: number = 4001, - message: string = 'Already a collaborator', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AlreadyCollaboratorError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - ServiceDisabled = 'ServiceDisabled', - Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', - NotFound = 'NotFound', - UserNotFound = 'UserNotFound', - ProjectNotFound = 'ProjectNotFound', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - ServiceDisabled = 1005, - Timeout = 2000, - InvalidArgument = 2001, - NotFound = 3000, - UserNotFound = 3001, - ProjectNotFound = 3002, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: ServiceDisabledError, - [2000]: TimeoutError, - [2001]: InvalidArgumentError, - [3000]: NotFoundError, - [3001]: UserNotFoundError, - [3002]: ProjectNotFoundError, -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/services/builder/src/index.ts b/packages/services/builder/src/index.ts deleted file mode 100644 index 18f13758de..0000000000 --- a/packages/services/builder/src/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from './builder.gen.js' - -import { Builder as BuilderRpc } from './builder.gen.js' - -export class SequenceBuilderClient extends BuilderRpc { - constructor( - public projectAccessKey: string, - apiUrl?: string, - ) { - const hostname = apiUrl ?? 'https://api.sequence.build' - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const projectAccessKey = this.projectAccessKey - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} diff --git a/packages/services/builder/tsconfig.json b/packages/services/builder/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/builder/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/guard/CHANGELOG.md b/packages/services/guard/CHANGELOG.md deleted file mode 100644 index a705f48ba3..0000000000 --- a/packages/services/guard/CHANGELOG.md +++ /dev/null @@ -1,3111 +0,0 @@ -# @0xsequence/guard - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients -- Updated dependencies - - @0xsequence/account@2.3.8 - - @0xsequence/core@2.3.8 - - @0xsequence/signhub@2.3.8 - - @0xsequence/utils@2.3.8 - -## 2.3.7 - -### Patch Changes - -- Metadata updates -- Updated dependencies - - @0xsequence/account@2.3.7 - - @0xsequence/core@2.3.7 - - @0xsequence/signhub@2.3.7 - - @0xsequence/utils@2.3.7 - -## 2.3.6 - -### Patch Changes - -- New chains -- Updated dependencies - - @0xsequence/account@2.3.6 - - @0xsequence/core@2.3.6 - - @0xsequence/signhub@2.3.6 - - @0xsequence/utils@2.3.6 - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet -- Updated dependencies - - @0xsequence/account@2.3.5 - - @0xsequence/core@2.3.5 - - @0xsequence/signhub@2.3.5 - - @0xsequence/utils@2.3.5 - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client -- Updated dependencies - - @0xsequence/account@2.3.4 - - @0xsequence/core@2.3.4 - - @0xsequence/signhub@2.3.4 - - @0xsequence/utils@2.3.4 - -## 2.3.3 - -### Patch Changes - -- metadata: client update -- Updated dependencies - - @0xsequence/account@2.3.3 - - @0xsequence/core@2.3.3 - - @0xsequence/signhub@2.3.3 - - @0xsequence/utils@2.3.3 - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client -- Updated dependencies - - @0xsequence/account@2.3.2 - - @0xsequence/core@2.3.2 - - @0xsequence/signhub@2.3.2 - - @0xsequence/utils@2.3.2 - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client -- Updated dependencies - - @0xsequence/account@2.3.1 - - @0xsequence/core@2.3.1 - - @0xsequence/signhub@2.3.1 - - @0xsequence/utils@2.3.1 - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -### Patch Changes - -- Updated dependencies - - @0xsequence/account@2.3.0 - - @0xsequence/core@2.3.0 - - @0xsequence/signhub@2.3.0 - - @0xsequence/utils@2.3.0 - -## 2.2.15 - -### Patch Changes - -- API updates -- Updated dependencies - - @0xsequence/account@2.2.15 - - @0xsequence/core@2.2.15 - - @0xsequence/signhub@2.2.15 - - @0xsequence/utils@2.2.15 - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet -- Updated dependencies - - @0xsequence/account@2.2.14 - - @0xsequence/core@2.2.14 - - @0xsequence/signhub@2.2.14 - - @0xsequence/utils@2.2.14 - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks -- Updated dependencies - - @0xsequence/account@2.2.13 - - @0xsequence/core@2.2.13 - - @0xsequence/signhub@2.2.13 - - @0xsequence/utils@2.2.13 - -## 2.2.12 - -### Patch Changes - -- Add XR1 -- Updated dependencies - - @0xsequence/account@2.2.12 - - @0xsequence/core@2.2.12 - - @0xsequence/signhub@2.2.12 - - @0xsequence/utils@2.2.12 - -## 2.2.11 - -### Patch Changes - -- Relayer updates -- Updated dependencies - - @0xsequence/account@2.2.11 - - @0xsequence/core@2.2.11 - - @0xsequence/signhub@2.2.11 - - @0xsequence/utils@2.2.11 - -## 2.2.10 - -### Patch Changes - -- Etherlink support -- Updated dependencies - - @0xsequence/account@2.2.10 - - @0xsequence/core@2.2.10 - - @0xsequence/signhub@2.2.10 - - @0xsequence/utils@2.2.10 - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances -- Updated dependencies - - @0xsequence/account@2.2.9 - - @0xsequence/core@2.2.9 - - @0xsequence/signhub@2.2.9 - - @0xsequence/utils@2.2.9 - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha -- Updated dependencies - - @0xsequence/account@2.2.8 - - @0xsequence/core@2.2.8 - - @0xsequence/signhub@2.2.8 - - @0xsequence/utils@2.2.8 - -## 2.2.7 - -### Patch Changes - -- Update Builder package -- Updated dependencies - - @0xsequence/account@2.2.7 - - @0xsequence/core@2.2.7 - - @0xsequence/signhub@2.2.7 - - @0xsequence/utils@2.2.7 - -## 2.2.6 - -### Patch Changes - -- Update relayer package -- Updated dependencies - - @0xsequence/account@2.2.6 - - @0xsequence/core@2.2.6 - - @0xsequence/signhub@2.2.6 - - @0xsequence/utils@2.2.6 - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.2.5 - - @0xsequence/core@2.2.5 - - @0xsequence/signhub@2.2.5 - - @0xsequence/utils@2.2.5 - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.2.4 - - @0xsequence/core@2.2.4 - - @0xsequence/signhub@2.2.4 - - @0xsequence/utils@2.2.4 - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist -- Updated dependencies - - @0xsequence/account@2.2.3 - - @0xsequence/core@2.2.3 - - @0xsequence/signhub@2.2.3 - - @0xsequence/utils@2.2.3 - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times -- Updated dependencies - - @0xsequence/account@2.2.2 - - @0xsequence/core@2.2.2 - - @0xsequence/signhub@2.2.2 - - @0xsequence/utils@2.2.2 - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data -- Updated dependencies - - @0xsequence/account@2.2.1 - - @0xsequence/core@2.2.1 - - @0xsequence/signhub@2.2.1 - - @0xsequence/utils@2.2.1 - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -### Patch Changes - -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.2.0 - - @0xsequence/core@2.2.0 - - @0xsequence/signhub@2.2.0 - - @0xsequence/utils@2.2.0 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet -- Updated dependencies - - @0xsequence/account@2.1.8 - - @0xsequence/core@2.1.8 - - @0xsequence/signhub@2.1.8 - - @0xsequence/utils@2.1.8 - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests -- Updated dependencies - - @0xsequence/account@2.1.7 - - @0xsequence/core@2.1.7 - - @0xsequence/signhub@2.1.7 - - @0xsequence/utils@2.1.7 - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains -- Updated dependencies - - @0xsequence/account@2.1.6 - - @0xsequence/core@2.1.6 - - @0xsequence/signhub@2.1.6 - - @0xsequence/utils@2.1.6 - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 -- Updated dependencies - - @0xsequence/account@2.1.5 - - @0xsequence/core@2.1.5 - - @0xsequence/signhub@2.1.5 - - @0xsequence/utils@2.1.5 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider -- Updated dependencies - - @0xsequence/account@2.1.4 - - @0xsequence/core@2.1.4 - - @0xsequence/signhub@2.1.4 - - @0xsequence/utils@2.1.4 - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk -- Updated dependencies - - @0xsequence/account@2.1.3 - - @0xsequence/core@2.1.3 - - @0xsequence/signhub@2.1.3 - - @0xsequence/utils@2.1.3 - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly -- Updated dependencies - - @0xsequence/account@2.1.2 - - @0xsequence/core@2.1.2 - - @0xsequence/signhub@2.1.2 - - @0xsequence/utils@2.1.2 - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support -- Updated dependencies - - @0xsequence/account@2.1.1 - - @0xsequence/core@2.1.1 - - @0xsequence/signhub@2.1.1 - - @0xsequence/utils@2.1.1 - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.1.0 - - @0xsequence/core@2.1.0 - - @0xsequence/signhub@2.1.0 - - @0xsequence/utils@2.1.0 - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison -- Updated dependencies - - @0xsequence/account@2.0.26 - - @0xsequence/core@2.0.26 - - @0xsequence/signhub@2.0.26 - - @0xsequence/utils@2.0.26 - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m -- Updated dependencies - - @0xsequence/account@2.0.25 - - @0xsequence/core@2.0.25 - - @0xsequence/signhub@2.0.25 - - @0xsequence/utils@2.0.25 - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.0.24 - - @0xsequence/core@2.0.24 - - @0xsequence/signhub@2.0.24 - - @0xsequence/utils@2.0.24 - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support -- Updated dependencies - - @0xsequence/account@2.0.23 - - @0xsequence/core@2.0.23 - - @0xsequence/signhub@2.0.23 - - @0xsequence/utils@2.0.23 - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support -- Updated dependencies - - @0xsequence/account@2.0.22 - - @0xsequence/core@2.0.22 - - @0xsequence/signhub@2.0.22 - - @0xsequence/utils@2.0.22 - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor -- Updated dependencies - - @0xsequence/account@2.0.21 - - @0xsequence/core@2.0.21 - - @0xsequence/signhub@2.0.21 - - @0xsequence/utils@2.0.21 - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling -- Updated dependencies - - @0xsequence/account@2.0.20 - - @0xsequence/core@2.0.20 - - @0xsequence/signhub@2.0.20 - - @0xsequence/utils@2.0.20 - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support -- Updated dependencies - - @0xsequence/account@2.0.19 - - @0xsequence/core@2.0.19 - - @0xsequence/signhub@2.0.19 - - @0xsequence/utils@2.0.19 - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.0.18 - - @0xsequence/core@2.0.18 - - @0xsequence/signhub@2.0.18 - - @0xsequence/utils@2.0.18 - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn -- Updated dependencies - - @0xsequence/account@2.0.17 - - @0xsequence/core@2.0.17 - - @0xsequence/signhub@2.0.17 - - @0xsequence/utils@2.0.17 - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains -- Updated dependencies - - @0xsequence/account@2.0.16 - - @0xsequence/core@2.0.16 - - @0xsequence/signhub@2.0.16 - - @0xsequence/utils@2.0.16 - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions -- Updated dependencies - - @0xsequence/account@2.0.15 - - @0xsequence/core@2.0.15 - - @0xsequence/signhub@2.0.15 - - @0xsequence/utils@2.0.15 - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.0.14 - - @0xsequence/core@2.0.14 - - @0xsequence/signhub@2.0.14 - - @0xsequence/utils@2.0.14 - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet -- Updated dependencies - - @0xsequence/account@2.0.13 - - @0xsequence/core@2.0.13 - - @0xsequence/signhub@2.0.13 - - @0xsequence/utils@2.0.13 - -## 2.0.12 - -### Patch Changes - -- api: update bindings -- Updated dependencies - - @0xsequence/account@2.0.12 - - @0xsequence/core@2.0.12 - - @0xsequence/signhub@2.0.12 - - @0xsequence/utils@2.0.12 - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.0.11 - - @0xsequence/core@2.0.11 - - @0xsequence/signhub@2.0.11 - - @0xsequence/utils@2.0.11 - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet -- Updated dependencies - - @0xsequence/account@2.0.10 - - @0xsequence/core@2.0.10 - - @0xsequence/signhub@2.0.10 - - @0xsequence/utils@2.0.10 - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name -- Updated dependencies - - @0xsequence/account@2.0.9 - - @0xsequence/core@2.0.9 - - @0xsequence/signhub@2.0.9 - - @0xsequence/utils@2.0.9 - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/account@2.0.8 - - @0xsequence/core@2.0.8 - - @0xsequence/signhub@2.0.8 - - @0xsequence/utils@2.0.8 - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix -- Updated dependencies - - @0xsequence/account@2.0.7 - - @0xsequence/core@2.0.7 - - @0xsequence/signhub@2.0.7 - - @0xsequence/utils@2.0.7 - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol -- Updated dependencies - - @0xsequence/account@2.0.6 - - @0xsequence/core@2.0.6 - - @0xsequence/signhub@2.0.6 - - @0xsequence/utils@2.0.6 - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 -- Updated dependencies - - @0xsequence/account@2.0.5 - - @0xsequence/core@2.0.5 - - @0xsequence/signhub@2.0.5 - - @0xsequence/utils@2.0.5 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet -- Updated dependencies - - @0xsequence/account@2.0.4 - - @0xsequence/core@2.0.4 - - @0xsequence/signhub@2.0.4 - - @0xsequence/utils@2.0.4 - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() -- Updated dependencies - - @0xsequence/account@2.0.3 - - @0xsequence/core@2.0.3 - - @0xsequence/signhub@2.0.3 - - @0xsequence/utils@2.0.3 - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint -- Updated dependencies - - @0xsequence/account@2.0.2 - - @0xsequence/core@2.0.2 - - @0xsequence/signhub@2.0.2 - - @0xsequence/utils@2.0.2 - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@2.0.1 - - @0xsequence/core@2.0.1 - - @0xsequence/signhub@2.0.1 - - @0xsequence/utils@2.0.1 - -## 2.0.0 - -### Major Changes - -- ethers v6 - -### Patch Changes - -- Updated dependencies - - @0xsequence/account@2.0.0 - - @0xsequence/core@2.0.0 - - @0xsequence/signhub@2.0.0 - - @0xsequence/utils@2.0.0 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey -- Updated dependencies - - @0xsequence/account@1.10.15 - - @0xsequence/core@1.10.15 - - @0xsequence/signhub@1.10.15 - - @0xsequence/utils@1.10.15 - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks -- Updated dependencies - - @0xsequence/account@1.10.14 - - @0xsequence/core@1.10.14 - - @0xsequence/signhub@1.10.14 - - @0xsequence/utils@1.10.14 - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet -- Updated dependencies - - @0xsequence/account@1.10.13 - - @0xsequence/core@1.10.13 - - @0xsequence/signhub@1.10.13 - - @0xsequence/utils@1.10.13 - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.10.12 - - @0xsequence/core@1.10.12 - - @0xsequence/signhub@1.10.12 - - @0xsequence/utils@1.10.12 - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen -- Updated dependencies - - @0xsequence/account@1.10.11 - - @0xsequence/core@1.10.11 - - @0xsequence/signhub@1.10.11 - - @0xsequence/utils@1.10.11 - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api -- Updated dependencies - - @0xsequence/account@1.10.10 - - @0xsequence/core@1.10.10 - - @0xsequence/signhub@1.10.10 - - @0xsequence/utils@1.10.10 - -## 1.10.9 - -### Patch Changes - -- waas minor update -- Updated dependencies - - @0xsequence/account@1.10.9 - - @0xsequence/core@1.10.9 - - @0xsequence/signhub@1.10.9 - - @0xsequence/utils@1.10.9 - -## 1.10.8 - -### Patch Changes - -- update metadata bindings -- Updated dependencies - - @0xsequence/account@1.10.8 - - @0xsequence/core@1.10.8 - - @0xsequence/signhub@1.10.8 - - @0xsequence/utils@1.10.8 - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client -- Updated dependencies - - @0xsequence/account@1.10.7 - - @0xsequence/core@1.10.7 - - @0xsequence/signhub@1.10.7 - - @0xsequence/utils@1.10.7 - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/account@1.10.6 - - @0xsequence/core@1.10.6 - - @0xsequence/signhub@1.10.6 - - @0xsequence/utils@1.10.6 - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet -- Updated dependencies - - @0xsequence/account@1.10.5 - - @0xsequence/core@1.10.5 - - @0xsequence/signhub@1.10.5 - - @0xsequence/utils@1.10.5 - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia -- Updated dependencies - - @0xsequence/account@1.10.4 - - @0xsequence/core@1.10.4 - - @0xsequence/signhub@1.10.4 - - @0xsequence/utils@1.10.4 - -## 1.10.3 - -### Patch Changes - -- typing fix -- Updated dependencies - - @0xsequence/account@1.10.3 - - @0xsequence/core@1.10.3 - - @0xsequence/signhub@1.10.3 - - @0xsequence/utils@1.10.3 - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client -- Updated dependencies - - @0xsequence/account@1.10.2 - - @0xsequence/core@1.10.2 - - @0xsequence/signhub@1.10.2 - - @0xsequence/utils@1.10.2 - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/account@1.10.1 - - @0xsequence/core@1.10.1 - - @0xsequence/signhub@1.10.1 - - @0xsequence/utils@1.10.1 - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -### Patch Changes - -- Updated dependencies - - @0xsequence/account@1.10.0 - - @0xsequence/core@1.10.0 - - @0xsequence/signhub@1.10.0 - - @0xsequence/utils@1.10.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants -- Updated dependencies - - @0xsequence/account@1.9.37 - - @0xsequence/core@1.9.37 - - @0xsequence/signhub@1.9.37 - - @0xsequence/utils@1.9.37 - -## 1.9.36 - -### Patch Changes - -- guard: export client -- Updated dependencies - - @0xsequence/account@1.9.36 - - @0xsequence/core@1.9.36 - - @0xsequence/signhub@1.9.36 - - @0xsequence/utils@1.9.36 - -## 1.9.35 - -### Patch Changes - -- guard: update bindings -- Updated dependencies - - @0xsequence/account@1.9.35 - - @0xsequence/core@1.9.35 - - @0xsequence/signhub@1.9.35 - - @0xsequence/utils@1.9.35 - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email -- Updated dependencies - - @0xsequence/account@1.9.34 - - @0xsequence/core@1.9.34 - - @0xsequence/signhub@1.9.34 - - @0xsequence/utils@1.9.34 - -## 1.9.33 - -### Patch Changes - -- waas: umd build -- Updated dependencies - - @0xsequence/account@1.9.33 - - @0xsequence/core@1.9.33 - - @0xsequence/signhub@1.9.33 - - @0xsequence/utils@1.9.33 - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings -- Updated dependencies - - @0xsequence/account@1.9.32 - - @0xsequence/core@1.9.32 - - @0xsequence/signhub@1.9.32 - - @0xsequence/utils@1.9.32 - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes -- Updated dependencies - - @0xsequence/account@1.9.31 - - @0xsequence/core@1.9.31 - - @0xsequence/signhub@1.9.31 - - @0xsequence/utils@1.9.31 - -## 1.9.30 - -### Patch Changes - -- update -- Updated dependencies - - @0xsequence/account@1.9.30 - - @0xsequence/core@1.9.30 - - @0xsequence/signhub@1.9.30 - - @0xsequence/utils@1.9.30 - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain -- Updated dependencies - - @0xsequence/account@1.9.29 - - @0xsequence/core@1.9.29 - - @0xsequence/signhub@1.9.29 - - @0xsequence/utils@1.9.29 - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree -- Updated dependencies - - @0xsequence/account@1.9.28 - - @0xsequence/core@1.9.28 - - @0xsequence/signhub@1.9.28 - - @0xsequence/utils@1.9.28 - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.9.27 - - @0xsequence/core@1.9.27 - - @0xsequence/signhub@1.9.27 - - @0xsequence/utils@1.9.27 - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 -- Updated dependencies - - @0xsequence/account@1.9.26 - - @0xsequence/core@1.9.26 - - @0xsequence/signhub@1.9.26 - - @0xsequence/utils@1.9.26 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types -- Updated dependencies - - @0xsequence/account@1.9.25 - - @0xsequence/core@1.9.25 - - @0xsequence/signhub@1.9.25 - - @0xsequence/utils@1.9.25 - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore -- Updated dependencies - - @0xsequence/account@1.9.24 - - @0xsequence/core@1.9.24 - - @0xsequence/signhub@1.9.24 - - @0xsequence/utils@1.9.24 - -## 1.9.23 - -### Patch Changes - -- update api client bindings -- Updated dependencies - - @0xsequence/account@1.9.23 - - @0xsequence/core@1.9.23 - - @0xsequence/signhub@1.9.23 - - @0xsequence/utils@1.9.23 - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings -- Updated dependencies - - @0xsequence/account@1.9.22 - - @0xsequence/core@1.9.22 - - @0xsequence/signhub@1.9.22 - - @0xsequence/utils@1.9.22 - -## 1.9.21 - -### Patch Changes - -- api client bindings -- Updated dependencies - - @0xsequence/account@1.9.21 - - @0xsequence/core@1.9.21 - - @0xsequence/signhub@1.9.21 - - @0xsequence/utils@1.9.21 - -## 1.9.20 - -### Patch Changes - -- api client bindings update -- Updated dependencies - - @0xsequence/account@1.9.20 - - @0xsequence/core@1.9.20 - - @0xsequence/signhub@1.9.20 - - @0xsequence/utils@1.9.20 - -## 1.9.19 - -### Patch Changes - -- waas update -- Updated dependencies - - @0xsequence/account@1.9.19 - - @0xsequence/core@1.9.19 - - @0xsequence/signhub@1.9.19 - - @0xsequence/utils@1.9.19 - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions -- Updated dependencies - - @0xsequence/account@1.9.18 - - @0xsequence/core@1.9.18 - - @0xsequence/signhub@1.9.18 - - @0xsequence/utils@1.9.18 - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia -- Updated dependencies - - @0xsequence/account@1.9.17 - - @0xsequence/core@1.9.17 - - @0xsequence/signhub@1.9.17 - - @0xsequence/utils@1.9.17 - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions -- Updated dependencies - - @0xsequence/account@1.9.16 - - @0xsequence/core@1.9.16 - - @0xsequence/signhub@1.9.16 - - @0xsequence/utils@1.9.16 - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix -- Updated dependencies - - @0xsequence/account@1.9.15 - - @0xsequence/core@1.9.15 - - @0xsequence/signhub@1.9.15 - - @0xsequence/utils@1.9.15 - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.9.14 - - @0xsequence/core@1.9.14 - - @0xsequence/signhub@1.9.14 - - @0xsequence/utils@1.9.14 - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency -- Updated dependencies - - @0xsequence/account@1.9.13 - - @0xsequence/core@1.9.13 - - @0xsequence/signhub@1.9.13 - - @0xsequence/utils@1.9.13 - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.9.12 - - @0xsequence/core@1.9.12 - - @0xsequence/signhub@1.9.12 - - @0xsequence/utils@1.9.12 - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings -- Updated dependencies - - @0xsequence/account@1.9.11 - - @0xsequence/core@1.9.11 - - @0xsequence/signhub@1.9.11 - - @0xsequence/utils@1.9.11 - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings -- Updated dependencies - - @0xsequence/account@1.9.10 - - @0xsequence/core@1.9.10 - - @0xsequence/signhub@1.9.10 - - @0xsequence/utils@1.9.10 - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client -- Updated dependencies - - @0xsequence/account@1.9.9 - - @0xsequence/core@1.9.9 - - @0xsequence/signhub@1.9.9 - - @0xsequence/utils@1.9.9 - -## 1.9.8 - -### Patch Changes - -- waas client update -- Updated dependencies - - @0xsequence/account@1.9.8 - - @0xsequence/core@1.9.8 - - @0xsequence/signhub@1.9.8 - - @0xsequence/utils@1.9.8 - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer -- Updated dependencies - - @0xsequence/account@1.9.7 - - @0xsequence/core@1.9.7 - - @0xsequence/signhub@1.9.7 - - @0xsequence/utils@1.9.7 - -## 1.9.6 - -### Patch Changes - -- waas package update -- Updated dependencies - - @0xsequence/account@1.9.6 - - @0xsequence/core@1.9.6 - - @0xsequence/signhub@1.9.6 - - @0xsequence/utils@1.9.6 - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key -- Updated dependencies - - @0xsequence/account@1.9.5 - - @0xsequence/core@1.9.5 - - @0xsequence/signhub@1.9.5 - - @0xsequence/utils@1.9.5 - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency -- Updated dependencies - - @0xsequence/account@1.9.4 - - @0xsequence/core@1.9.4 - - @0xsequence/signhub@1.9.4 - - @0xsequence/utils@1.9.4 - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it -- Updated dependencies - - @0xsequence/account@1.9.3 - - @0xsequence/core@1.9.3 - - @0xsequence/signhub@1.9.3 - - @0xsequence/utils@1.9.3 - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia -- Updated dependencies - - @0xsequence/account@1.9.2 - - @0xsequence/core@1.9.2 - - @0xsequence/signhub@1.9.2 - - @0xsequence/utils@1.9.2 - -## 1.9.1 - -### Patch Changes - -- analytics fix -- Updated dependencies - - @0xsequence/account@1.9.1 - - @0xsequence/core@1.9.1 - - @0xsequence/signhub@1.9.1 - - @0xsequence/utils@1.9.1 - -## 1.9.0 - -### Minor Changes - -- waas release - -### Patch Changes - -- Updated dependencies - - @0xsequence/account@1.9.0 - - @0xsequence/core@1.9.0 - - @0xsequence/signhub@1.9.0 - - @0xsequence/utils@1.9.0 - -## 1.8.8 - -### Patch Changes - -- update metadata bindings -- Updated dependencies - - @0xsequence/account@1.8.8 - - @0xsequence/core@1.8.8 - - @0xsequence/signhub@1.8.8 - - @0xsequence/utils@1.8.8 - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 -- Updated dependencies - - @0xsequence/account@1.8.7 - - @0xsequence/core@1.8.7 - - @0xsequence/signhub@1.8.7 - - @0xsequence/utils@1.8.7 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof -- Updated dependencies - - @0xsequence/account@1.8.6 - - @0xsequence/core@1.8.6 - - @0xsequence/signhub@1.8.6 - - @0xsequence/utils@1.8.6 - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof -- Updated dependencies - - @0xsequence/account@1.8.5 - - @0xsequence/core@1.8.5 - - @0xsequence/signhub@1.8.5 - - @0xsequence/utils@1.8.5 - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list -- Updated dependencies - - @0xsequence/account@1.8.4 - - @0xsequence/core@1.8.4 - - @0xsequence/signhub@1.8.4 - - @0xsequence/utils@1.8.4 - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support -- Updated dependencies - - @0xsequence/account@1.8.3 - - @0xsequence/core@1.8.3 - - @0xsequence/signhub@1.8.3 - - @0xsequence/utils@1.8.3 - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested -- Updated dependencies - - @0xsequence/account@1.8.2 - - @0xsequence/core@1.8.2 - - @0xsequence/signhub@1.8.2 - - @0xsequence/utils@1.8.2 - -## 1.8.1 - -### Patch Changes - -- update to analytics provider -- Updated dependencies - - @0xsequence/account@1.8.1 - - @0xsequence/core@1.8.1 - - @0xsequence/signhub@1.8.1 - - @0xsequence/utils@1.8.1 - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -### Patch Changes - -- Updated dependencies - - @0xsequence/account@1.8.0 - - @0xsequence/core@1.8.0 - - @0xsequence/signhub@1.8.0 - - @0xsequence/utils@1.8.0 - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.7.2 - - @0xsequence/core@1.7.2 - - @0xsequence/signhub@1.7.2 - - @0xsequence/utils@1.7.2 - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI -- Updated dependencies - - @0xsequence/account@1.7.1 - - @0xsequence/core@1.7.1 - - @0xsequence/signhub@1.7.1 - - @0xsequence/utils@1.7.1 - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.7.0 - - @0xsequence/core@1.7.0 - - @0xsequence/signhub@1.7.0 - - @0xsequence/utils@1.7.0 - -## 1.6.3 - -### Patch Changes - -- network list update -- Updated dependencies - - @0xsequence/account@1.6.3 - - @0xsequence/core@1.6.3 - - @0xsequence/signhub@1.6.3 - - @0xsequence/utils@1.6.3 - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.6.2 - - @0xsequence/core@1.6.2 - - @0xsequence/signhub@1.6.2 - - @0xsequence/utils@1.6.2 - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.6.1 - - @0xsequence/core@1.6.1 - - @0xsequence/signhub@1.6.1 - - @0xsequence/utils@1.6.1 - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.6.0 - - @0xsequence/core@1.6.0 - - @0xsequence/signhub@1.6.0 - - @0xsequence/utils@1.6.0 - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.5.0 - - @0xsequence/core@1.5.0 - - @0xsequence/signhub@1.5.0 - - @0xsequence/utils@1.5.0 - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata -- Updated dependencies - - @0xsequence/account@1.4.9 - - @0xsequence/core@1.4.9 - - @0xsequence/signhub@1.4.9 - - @0xsequence/utils@1.4.9 - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners -- Updated dependencies - - @0xsequence/account@1.4.8 - - @0xsequence/core@1.4.8 - - @0xsequence/signhub@1.4.8 - - @0xsequence/utils@1.4.8 - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings -- Updated dependencies - - @0xsequence/account@1.4.7 - - @0xsequence/core@1.4.7 - - @0xsequence/signhub@1.4.7 - - @0xsequence/utils@1.4.7 - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings -- Updated dependencies - - @0xsequence/account@1.4.6 - - @0xsequence/core@1.4.6 - - @0xsequence/signhub@1.4.6 - - @0xsequence/utils@1.4.6 - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.4.5 - - @0xsequence/core@1.4.5 - - @0xsequence/signhub@1.4.5 - - @0xsequence/utils@1.4.5 - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.4.4 - - @0xsequence/core@1.4.4 - - @0xsequence/signhub@1.4.4 - - @0xsequence/utils@1.4.4 - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods -- Updated dependencies - - @0xsequence/account@1.4.3 - - @0xsequence/core@1.4.3 - - @0xsequence/signhub@1.4.3 - - @0xsequence/utils@1.4.3 - -## 1.4.2 - -### Patch Changes - -- guard: update bindings -- Updated dependencies - - @0xsequence/account@1.4.2 - - @0xsequence/core@1.4.2 - - @0xsequence/signhub@1.4.2 - - @0xsequence/utils@1.4.2 - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/account@1.4.1 - - @0xsequence/core@1.4.1 - - @0xsequence/signhub@1.4.1 - - @0xsequence/utils@1.4.1 - -## 1.4.0 - -### Minor Changes - -- project access key support - -### Patch Changes - -- Updated dependencies - - @0xsequence/core@1.4.0 - - @0xsequence/signhub@1.4.0 - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.3.0 - - @0xsequence/signhub@1.3.0 - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.2.9 - - @0xsequence/signhub@1.2.9 - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key -- Updated dependencies - - @0xsequence/core@1.2.8 - - @0xsequence/signhub@1.2.8 - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients -- Updated dependencies - - @0xsequence/core@1.2.7 - - @0xsequence/signhub@1.2.7 - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider -- Updated dependencies - - @0xsequence/core@1.2.6 - - @0xsequence/signhub@1.2.6 - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes -- Updated dependencies - - @0xsequence/core@1.2.5 - - @0xsequence/signhub@1.2.5 - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.2.4 - - @0xsequence/signhub@1.2.4 - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce -- Updated dependencies - - @0xsequence/core@1.2.3 - - @0xsequence/signhub@1.2.3 - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.2.2 - - @0xsequence/signhub@1.2.2 - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available -- Updated dependencies - - @0xsequence/core@1.2.1 - - @0xsequence/signhub@1.2.1 - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -### Patch Changes - -- Updated dependencies - - @0xsequence/core@1.2.0 - - @0xsequence/signhub@1.2.0 - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering -- Updated dependencies - - @0xsequence/core@1.1.15 - - @0xsequence/signhub@1.1.15 - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError -- Updated dependencies - - @0xsequence/core@1.1.14 - - @0xsequence/signhub@1.1.14 - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.1.13 - - @0xsequence/signhub@1.1.13 - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions -- Updated dependencies - - @0xsequence/core@1.1.12 - - @0xsequence/signhub@1.1.12 - -## 1.1.11 - -### Patch Changes - -- add homeverse configs -- Updated dependencies - - @0xsequence/core@1.1.11 - - @0xsequence/signhub@1.1.11 - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send -- Updated dependencies - - @0xsequence/core@1.1.10 - - @0xsequence/signhub@1.1.10 - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client -- Updated dependencies - - @0xsequence/core@1.1.9 - - @0xsequence/signhub@1.1.9 - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter -- Updated dependencies - - @0xsequence/core@1.1.8 - - @0xsequence/signhub@1.1.8 - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow -- Updated dependencies - - @0xsequence/core@1.1.7 - - @0xsequence/signhub@1.1.7 - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters -- Updated dependencies - - @0xsequence/core@1.1.6 - - @0xsequence/signhub@1.1.6 - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions -- Updated dependencies - - @0xsequence/core@1.1.5 - - @0xsequence/signhub@1.1.5 - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.1.4 - - @0xsequence/signhub@1.1.4 - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.1.3 - - @0xsequence/signhub@1.1.3 - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes -- Updated dependencies - - @0xsequence/core@1.1.2 - - @0xsequence/signhub@1.1.2 - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.1.1 - - @0xsequence/signhub@1.1.1 - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -### Patch Changes - -- Updated dependencies - - @0xsequence/core@1.1.0 - - @0xsequence/signhub@1.1.0 - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/core@1.0.5 - - @0xsequence/signhub@1.0.5 - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId -- Updated dependencies - - @0xsequence/core@1.0.4 - - @0xsequence/signhub@1.0.4 - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers -- Updated dependencies - - @0xsequence/core@1.0.3 - - @0xsequence/signhub@1.0.3 - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods -- Updated dependencies - - @0xsequence/core@1.0.2 - - @0xsequence/signhub@1.0.2 - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet -- Updated dependencies - - @0xsequence/core@1.0.1 - - @0xsequence/signhub@1.0.1 - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -### Patch Changes - -- Updated dependencies - - @0xsequence/core@1.0.0 - - @0xsequence/signhub@1.0.0 - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object - -## 0.43.28 - -### Patch Changes - -- update api bindings - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM - -## 0.43.22 - -### Patch Changes - -- add zkevm chain - -## 0.43.21 - -### Patch Changes - -- api: update client bindings - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings - -## 0.43.19 - -### Patch Changes - -- session proof update - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods - -## 0.43.14 - -### Patch Changes - -- bump - -## 0.43.13 - -### Patch Changes - -- update rpc bindings - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter - -## 0.43.10 - -### Patch Changes - -- various improvements - -## 0.43.9 - -### Patch Changes - -- update deps - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings - -## 0.42.6 - -### Patch Changes - -- api bindings update - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options - -## 0.42.3 - -### Patch Changes - -- update api bindings - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' - -## 0.41.3 - -### Patch Changes - -- api bindings update - -## 0.41.2 - -### Patch Changes - -- api bindings update - -## 0.41.1 - -### Patch Changes - -- update default networks - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain - -## 0.40.5 - -### Patch Changes - -- api: update bindings - -## 0.40.4 - -### Patch Changes - -- add unreal transport - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option - -## 0.39.4 - -### Patch Changes - -- api: update client bindings - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider - -## 0.39.2 - -### Patch Changes - -- update umd name - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) - -## 0.36.7 - -### Patch Changes - -- fix missing break - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation - -## 0.35.10 - -### Patch Changes - -- upgrade deps - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -## 0.29.8 - -### Patch Changes - -- update api - -## 0.28.0 - -### Minor Changes - -- extension provider - -## 0.27.0 - -### Minor Changes - -- Add requireFreshSigner lib to sessions - -## 0.25.1 - -### Patch Changes - -- Fix build typescrypt issue - -## 0.25.0 - -### Minor Changes - -- 10c8af8: Add estimator package - Fix multicall few calls bug - -## 0.23.0 - -### Minor Changes - -- - relayer: offer variety of gas fee options from the relayer service" - -## 0.22.2 - -### Patch Changes - -- e1c109e: Fix authProof on expired sessions - -## 0.22.1 - -### Patch Changes - -- transport session cache - -## 0.22.0 - -### Minor Changes - -- e667b65: Expose all relayer options on networks - -## 0.21.5 - -### Patch Changes - -- Give priority to metaTxnId returned by relayer - -## 0.21.4 - -### Patch Changes - -- Add has enough signers method - -## 0.21.3 - -### Patch Changes - -- add window session cache - -## 0.21.2 - -### Patch Changes - -- exception handlind in relayer - -## 0.21.0 - -### Minor Changes - -- - fix gas estimation on wallets with large number of signers - - update to session handling and wallet config construction upon auth - -## 0.19.3 - -### Patch Changes - -- jwtAuth visibility, package version sync - -## 0.19.0 - -### Minor Changes - -- - provider, improve dapp / wallet transport io - -## 0.18.0 - -### Minor Changes - -- relayer improvements and pending transaction handling - -## 0.16.0 - -### Minor Changes - -- relayer as its own service separate from chaind - -## 0.15.1 - -### Patch Changes - -- update api clients - -## 0.14.3 - -### Patch Changes - -- Fix 0xSequence relayer dependencies - -## 0.14.2 - -### Patch Changes - -- Add debug logs to rpc-relayer - -## 0.14.0 - -### Minor Changes - -- update sequence utils finder which includes optimization - -## 0.13.0 - -### Minor Changes - -- Update SequenceUtils deployed contract - -## 0.12.1 - -### Patch Changes - -- npm bump - -## 0.12.0 - -### Minor Changes - -- provider: improvements to window transport - -## 0.11.4 - -### Patch Changes - -- update api client - -## 0.11.3 - -### Patch Changes - -- improve openWindow state options handling - -## 0.11.2 - -### Patch Changes - -- Fix multicall proxy scopes - -## 0.11.1 - -### Patch Changes - -- Add support for dynamic and nested signatures - -## 0.11.0 - -### Minor Changes - -- Update wallet context to 1.7 contracts - -## 0.10.9 - -### Patch Changes - -- add support for public addresses as signers in session.open - -## 0.10.8 - -### Patch Changes - -- Multicall production configuration - -## 0.10.7 - -### Patch Changes - -- allow provider transport to force disconnect - -## 0.10.6 - -### Patch Changes - -- - fix getWalletState method - -## 0.10.5 - -### Patch Changes - -- update relayer gas refund options - -## 0.10.4 - -### Patch Changes - -- Update api proto - -## 0.10.3 - -### Patch Changes - -- Fix loading config cross-chain - -## 0.10.2 - -### Patch Changes - -- - message digest fix - -## 0.10.1 - -### Patch Changes - -- upgrade deps - -## 0.10.0 - -### Minor Changes - -- Deployed new contracts with ERC1271 signer support - -## 0.9.6 - -### Patch Changes - -- Update ABIs for latest sequence contracts - -## 0.9.3 - -### Patch Changes - -- - minor improvements - -## 0.9.1 - -### Patch Changes - -- - patch bump - -## 0.9.0 - -### Minor Changes - -- - provider transport hardening - -## 0.8.5 - -### Patch Changes - -- - use latest wallet-contracts - -## 0.8.4 - -### Patch Changes - -- - minor improvements, name updates and comments - -## 0.8.3 - -### Patch Changes - -- - refinements - - - normalize signer address in config - - - provider: getWalletState() method to WalletProvider - -## 0.8.2 - -### Patch Changes - -- - field rename and ethauth dependency bump - -## 0.8.1 - -### Patch Changes - -- - variety of optimizations - -## 0.8.0 - -### Minor Changes - -- - changeset fix - -## 0.7.0 - -### Patch Changes - -- 6f11ed7: sequence.js, init release diff --git a/packages/services/guard/README.md b/packages/services/guard/README.md deleted file mode 100644 index dfb8a383fa..0000000000 --- a/packages/services/guard/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/guard - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/guard/eslint.config.js b/packages/services/guard/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/guard/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/guard/package.json b/packages/services/guard/package.json deleted file mode 100644 index fc489cd03c..0000000000 --- a/packages/services/guard/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@0xsequence/guard", - "version": "3.0.5", - "description": "guard sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/guard", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "ox": "^0.9.17" - } -} diff --git a/packages/services/guard/src/client/guard.gen.ts b/packages/services/guard/src/client/guard.gen.ts deleted file mode 100644 index 4eea436eeb..0000000000 --- a/packages/services/guard/src/client/guard.gen.ts +++ /dev/null @@ -1,1088 +0,0 @@ -/* eslint-disable */ -// sequence-guard v0.5.0 910e01c32ffb24b42386d4ca6be119b0acc55c5f -// -- -// Code generated by webrpc-gen@v0.25.3 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=guard.ridl -target=typescript -client -out=./clients/guard.gen.ts - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.25.3;gen-typescript@v0.17.0;sequence-guard@v0.5.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' - -// Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.5.0' - -// Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '910e01c32ffb24b42386d4ca6be119b0acc55c5f' - -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} - -// -// Types -// - -export enum PayloadType { - Calls = 'Calls', - Message = 'Message', - ConfigUpdate = 'ConfigUpdate', - SessionImplicitAuthorize = 'SessionImplicitAuthorize', -} - -export enum SignatureType { - Hash = 'Hash', - Sapient = 'Sapient', - EthSign = 'EthSign', - Erc1271 = 'Erc1271', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string -} - -export interface WalletConfig { - address: string - content: string -} - -export interface WalletSigner { - address: string - weight: number -} - -export interface SignRequest { - chainId: number - msg: string - auxData?: string - wallet?: string - payloadType?: PayloadType - payloadData?: string - signatures?: Array -} - -export interface OwnershipProof { - wallet: string - timestamp: number - signer: string - signature: string - chainId: number -} - -export interface AuthToken { - id: string - token: string - resetAuth?: boolean -} - -export interface RecoveryCode { - code: string - used: boolean -} - -export interface Signature { - address: string - type: SignatureType - imageHash?: string - data: string -} - -export interface Guard { - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - getSignerConfig(args: GetSignerConfigArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user signs in, and signs messages/transactions/migrations. - * Requires a valid 2FA token if enabled. - */ - sign(args: SignArgs, headers?: object, signal?: AbortSignal): Promise - signWith(args: SignWithArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Internal use only. - * Only ever needs to be called once per chain. - * Signs a preconfigured payload that the caller has no control over. - */ - patch(args: PatchArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when it needs to check the user's 2FA. - * This happens during sign in, before signing messages and transactions, and when configuring 2FA. - * Requires either a valid JWT or a signature by one of the wallet's signers. - */ - authMethods(args: AuthMethodsArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Not currently called. Requires both a JWT and a wallet signature. - */ - setPIN(args: SetPINArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Not currently called. Requires both a JWT and a wallet signature. - */ - resetPIN(args: ResetPINArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user configures their 2FA. - * Requires both a JWT and a wallet signature. - */ - createTOTP(args: CreateTOTPArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user configures their 2FA. - * Requires both a JWT and a wallet signature. - */ - commitTOTP(args: CommitTOTPArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user configures their 2FA. - * Requires both a JWT and a wallet signature. - */ - resetTOTP(args: ResetTOTPArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user uses a recovery code. - * Requires either a valid JWT or a signature by one of the wallet's signers. - */ - reset2FA(args: Reset2FAArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user is viewing their recovery codes. - * Requires both a JWT and a wallet signature. - */ - recoveryCodes(args: RecoveryCodesArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Called by sequence.app when the user is viewing their recovery codes. - * Requires both a JWT and a wallet signature. - */ - resetRecoveryCodes( - args: ResetRecoveryCodesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -export interface PingArgs {} - -export interface PingReturn { - status: boolean -} -export interface VersionArgs {} - -export interface VersionReturn { - version: Version -} -export interface RuntimeStatusArgs {} - -export interface RuntimeStatusReturn { - status: RuntimeStatus -} -export interface GetSignerConfigArgs { - signer: string -} - -export interface GetSignerConfigReturn { - signerConfig: WalletConfig -} -export interface SignArgs { - request: SignRequest - token?: AuthToken -} - -export interface SignReturn { - sig: string -} -export interface SignWithArgs { - signer: string - request: SignRequest - token?: AuthToken -} - -export interface SignWithReturn { - sig: string -} -export interface PatchArgs { - signer: string - chainId: number - secret: string -} - -export interface PatchReturn { - txs: any -} -export interface AuthMethodsArgs { - proof?: OwnershipProof -} - -export interface AuthMethodsReturn { - methods: Array - active: boolean -} -export interface SetPINArgs { - pin: string - timestamp: number - signature: string - chainId: number -} - -export interface SetPINReturn {} -export interface ResetPINArgs { - timestamp: number - signature: string - chainId: number -} - -export interface ResetPINReturn {} -export interface CreateTOTPArgs { - timestamp: number - signature: string - chainId: number -} - -export interface CreateTOTPReturn { - uri: string -} -export interface CommitTOTPArgs { - token: string -} - -export interface CommitTOTPReturn { - codes: Array -} -export interface ResetTOTPArgs { - timestamp: number - signature: string - chainId: number -} - -export interface ResetTOTPReturn {} -export interface Reset2FAArgs { - code: string - proof?: OwnershipProof -} - -export interface Reset2FAReturn {} -export interface RecoveryCodesArgs { - timestamp: number - signature: string - chainId: number -} - -export interface RecoveryCodesReturn { - codes: Array -} -export interface ResetRecoveryCodesArgs { - timestamp: number - signature: string - chainId: number -} - -export interface ResetRecoveryCodesReturn { - codes: Array -} - -// -// Client -// -export class Guard implements Guard { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Guard/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - version: _data.version, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getSignerConfig = ( - args: GetSignerConfigArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetSignerConfig'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - signerConfig: _data.signerConfig, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - sign = (args: SignArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Sign'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - sig: _data.sig, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - signWith = (args: SignWithArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SignWith'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - sig: _data.sig, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - patch = (args: PatchArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Patch'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - txs: _data.txs, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - authMethods = (args: AuthMethodsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AuthMethods'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - methods: >_data.methods, - active: _data.active, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - setPIN = (args: SetPINArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SetPIN'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - resetPIN = (args: ResetPINArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ResetPIN'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - createTOTP = (args: CreateTOTPArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('CreateTOTP'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - uri: _data.uri, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - commitTOTP = (args: CommitTOTPArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('CommitTOTP'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - codes: >_data.codes, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - resetTOTP = (args: ResetTOTPArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ResetTOTP'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - reset2FA = (args: Reset2FAArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Reset2FA'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - recoveryCodes = (args: RecoveryCodesArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RecoveryCodes'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - codes: >_data.codes, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - resetRecoveryCodes = ( - args: ResetRecoveryCodesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ResetRecoveryCodes'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - codes: >_data.codes, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } -} - -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, - } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -// -// Errors -// - -export class WebrpcError extends Error { - name: string - code: number - message: string - status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) - } -} - -// Webrpc errors - -export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// Schema errors - -export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = `Unauthorized access`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = `Permission denied`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = `Session expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = `Method not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = `Conflict with target resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = `Request aborted`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = `Geoblocked region`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = `Rate-limited. Please slow down.`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = `Invalid argument`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor( - name: string = 'Unavailable', - code: number = 2002, - message: string = `Unavailable resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = `Query failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class ValidationFailedError extends WebrpcError { - constructor( - name: string = 'ValidationFailed', - code: number = 2004, - message: string = `Validation Failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ValidationFailedError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = `Resource not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class RequiresTOTPError extends WebrpcError { - constructor( - name: string = 'RequiresTOTP', - code: number = 6600, - message: string = `TOTP is required`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequiresTOTPError.prototype) - } -} - -export class RequiresPINError extends WebrpcError { - constructor( - name: string = 'RequiresPIN', - code: number = 6601, - message: string = `PIN is required`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequiresPINError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - InvalidArgument = 'InvalidArgument', - Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - ValidationFailed = 'ValidationFailed', - NotFound = 'NotFound', - RequiresTOTP = 'RequiresTOTP', - RequiresPIN = 'RequiresPIN', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Aborted = 1005, - Geoblocked = 1006, - RateLimited = 1007, - InvalidArgument = 2001, - Unavailable = 2002, - QueryFailed = 2003, - ValidationFailed = 2004, - NotFound = 3000, - RequiresTOTP = 6600, - RequiresPIN = 6601, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [2001]: InvalidArgumentError, - [2002]: UnavailableError, - [2003]: QueryFailedError, - [2004]: ValidationFailedError, - [3000]: NotFoundError, - [6600]: RequiresTOTPError, - [6601]: RequiresPINError, -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/services/guard/src/index.ts b/packages/services/guard/src/index.ts deleted file mode 100644 index 40d7085757..0000000000 --- a/packages/services/guard/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './types.js' -export { PayloadType, SignatureType, type Signature } from './client/guard.gen.js' - -export * as Client from './client/guard.gen.js' -export * as Sequence from './sequence.js' -export * as Local from './local.js' diff --git a/packages/services/guard/src/local.ts b/packages/services/guard/src/local.ts deleted file mode 100644 index 3eeb7b8d5d..0000000000 --- a/packages/services/guard/src/local.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Address, Hex, Bytes, Secp256k1 } from 'ox' -import * as Client from './client/guard.gen.js' -import * as Types from './types.js' - -export class Guard implements Types.Guard { - public readonly address: Address.Address - - constructor(private readonly privateKey: Hex.Hex) { - const publicKey = Secp256k1.getPublicKey({ privateKey: this.privateKey }) - this.address = Address.fromPublicKey(publicKey) - } - - async signPayload( - _wallet: Address.Address, - _chainId: number, - _type: Client.PayloadType, - digest: Bytes.Bytes, - _message: Bytes.Bytes, - _signatures?: Client.Signature[], - _token?: Client.AuthToken, - ) { - return Secp256k1.sign({ privateKey: this.privateKey, payload: digest }) - } -} diff --git a/packages/services/guard/src/sequence.ts b/packages/services/guard/src/sequence.ts deleted file mode 100644 index b2acc812af..0000000000 --- a/packages/services/guard/src/sequence.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Address, Hex, Signature, Bytes } from 'ox' -import * as Client from './client/guard.gen.js' -import * as Types from './types.js' - -export class Guard implements Types.Guard { - private readonly guard?: Client.Guard - public readonly address: Address.Address - - constructor(hostname: string, address: Address.Address, fetch?: Client.Fetch) { - if (hostname && address) { - this.guard = new Client.Guard(hostname, fetch ?? window.fetch) - } - this.address = address - } - - async signPayload( - wallet: Address.Address, - chainId: number, - type: Client.PayloadType, - digest: Bytes.Bytes, - message: Bytes.Bytes, - signatures?: Client.Signature[], - token?: Client.AuthToken, - ) { - if (!this.guard || !this.address) { - throw new Error('Guard not initialized') - } - - try { - const res = await this.guard.signWith({ - signer: this.address, - request: { - chainId: chainId, - msg: Hex.fromBytes(digest), - wallet, - payloadType: type, - payloadData: Hex.fromBytes(message), - signatures, - }, - token, - }) - - Hex.assert(res.sig) - return Signature.fromHex(res.sig) - } catch (error) { - if (error instanceof Client.RequiresTOTPError) { - throw new Types.AuthRequiredError('TOTP') - } - if (error instanceof Client.RequiresPINError) { - throw new Types.AuthRequiredError('PIN') - } - console.error(error) - throw new Error('Error signing with guard', { cause: error }) - } - } -} diff --git a/packages/services/guard/src/types.ts b/packages/services/guard/src/types.ts deleted file mode 100644 index cb5073fa02..0000000000 --- a/packages/services/guard/src/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Address, Bytes, Signature } from 'ox' -import * as Client from './client/guard.gen.js' - -export interface Guard { - readonly address: Address.Address - - signPayload( - wallet: Address.Address, - chainId: number, - type: Client.PayloadType, - digest: Bytes.Bytes, - message: Bytes.Bytes, - signatures?: Client.Signature[], - token?: Client.AuthToken, - ): Promise -} - -export class AuthRequiredError extends Error { - public readonly id: 'TOTP' | 'PIN' - - constructor(id: 'TOTP' | 'PIN') { - super('auth required') - this.id = id - this.name = 'AuthRequiredError' - Object.setPrototypeOf(this, AuthRequiredError.prototype) - } -} diff --git a/packages/services/guard/test/sequence.test.ts b/packages/services/guard/test/sequence.test.ts deleted file mode 100644 index ebc9a47b76..0000000000 --- a/packages/services/guard/test/sequence.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Guard } from '../src/sequence.js' -import { PayloadType } from '../src/client/guard.gen.js' -import { Address, Bytes, Hex } from 'ox' - -// Mock fetch globally for guard API calls -const mockFetch = vi.fn() -globalThis.fetch = mockFetch - -describe('Sequence', () => { - describe('GuardSigner', () => { - let guard: Guard - let testWallet: Address.Address - let testMessage: Bytes.Bytes - let testMessageDigest: Bytes.Bytes - - beforeEach(() => { - vi.clearAllMocks() - guard = new Guard('https://guard.sequence.app', '0xaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae', fetch) - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - testMessage = Bytes.fromString('Test message') - testMessageDigest = Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890') - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - describe('sign()', () => { - it('Should successfully sign a payload with guard service', async () => { - const mockSignature = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - sig: mockSignature, - }), - text: async () => - JSON.stringify({ - sig: mockSignature, - }), - ok: true, - }) - - const result = await guard.signPayload( - testWallet, - 42161, - PayloadType.ConfigUpdate, - testMessageDigest, - testMessage, - ) - - expect(result).toBeDefined() - expect(result.r).toBeDefined() - expect(result.s).toBeDefined() - expect(result.yParity).toBeDefined() - - // Verify API call was made correctly - expect(mockFetch).toHaveBeenCalledOnce() - const [url, options] = mockFetch.mock.calls[0] - - expect(url).toContain('/rpc/Guard/SignWith') - expect(options.method).toBe('POST') - expect(options.headers['Content-Type']).toBe('application/json') - - const requestBody = JSON.parse(options.body) - expect(requestBody.signer).toBe('0xaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae') - expect(requestBody.request.chainId).toBe(42161) - expect(requestBody.request.msg).toBe(Hex.fromBytes(testMessageDigest).toString()) - expect(requestBody.request.payloadType).toBe(PayloadType.ConfigUpdate) - expect(requestBody.request.payloadData).toBe(Hex.fromBytes(testMessage).toString()) - expect(requestBody.request.wallet).toBe(testWallet) - }) - - it('Should handle custom chainId in sign request', async () => { - const customChainId = 1 // Ethereum mainnet - - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - text: async () => - JSON.stringify({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - ok: true, - }) - - await guard.signPayload(testWallet, customChainId, PayloadType.ConfigUpdate, testMessageDigest, testMessage) - - const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body) - expect(requestBody.request.chainId).toBe(1) - }) - - it('Should throw error when guard service fails', async () => { - mockFetch.mockRejectedValueOnce(new Error('Network error')) - - await expect( - guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage), - ).rejects.toThrow('Error signing with guard') - }) - - it('Should throw error when guard service returns invalid response', async () => { - mockFetch.mockResolvedValueOnce({ - json: async () => { - throw new Error('Invalid JSON') - }, - text: async () => { - throw new Error('Invalid JSON') - }, - ok: true, - }) - - await expect( - guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage), - ).rejects.toThrow('Error signing with guard') - }) - - it('Should preserve the original guard failure as cause', async () => { - mockFetch.mockRejectedValueOnce(new Error('Network error')) - - try { - await guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage) - throw new Error('Expected signPayload to throw') - } catch (error) { - expect(error).toBeInstanceOf(Error) - expect((error as Error).message).toBe('Error signing with guard') - expect((error as Error & { cause?: unknown }).cause).toBeInstanceOf(Error) - expect((error as Error & { cause?: Error }).cause?.name).toBe('WebrpcRequestFailed') - expect((error as Error & { cause?: Error }).cause?.message).toBe('request failed') - } - }) - - it('Should include proper headers and signer address in request', async () => { - const mockGuardAddress = '0x9876543210987654321098765432109876543210' as Address.Address - const customGuard = new Guard('https://guard.sequence.app', mockGuardAddress, fetch) - - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - text: async () => - JSON.stringify({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - ok: true, - }) - - await customGuard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage) - - const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body) - expect(requestBody.signer).toBe(mockGuardAddress) - }) - - describe('Error Handling', () => { - it('Should handle malformed guard service response', async () => { - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - // Missing 'sig' field - error: 'Invalid request', - }), - text: async () => - JSON.stringify({ - error: 'Invalid request', - }), - ok: true, - }) - - await expect( - guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage), - ).rejects.toThrow('Error signing with guard') - }) - - it('Should handle network timeout errors', async () => { - mockFetch.mockImplementationOnce( - () => new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 100)), - ) - - await expect( - guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage), - ).rejects.toThrow('Error signing with guard') - }) - - it('Should handle HTTP error responses', async () => { - mockFetch.mockResolvedValueOnce({ - ok: false, - status: 500, - json: async () => ({ - error: 'Internal server error', - }), - text: async () => - JSON.stringify({ - error: 'Internal server error', - }), - }) - - await expect( - guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage), - ).rejects.toThrow('Error signing with guard') - }) - }) - }) - }) -}) diff --git a/packages/services/guard/tsconfig.json b/packages/services/guard/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/guard/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/identity-instrument/CHANGELOG.md b/packages/services/identity-instrument/CHANGELOG.md deleted file mode 100644 index ff6cebf477..0000000000 --- a/packages/services/identity-instrument/CHANGELOG.md +++ /dev/null @@ -1,170 +0,0 @@ -# @0xsequence/identity-instrument - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 diff --git a/packages/services/identity-instrument/eslint.config.js b/packages/services/identity-instrument/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/identity-instrument/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/identity-instrument/package.json b/packages/services/identity-instrument/package.json deleted file mode 100644 index bb61d8e20c..0000000000 --- a/packages/services/identity-instrument/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@0xsequence/identity-instrument", - "version": "3.0.5", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run", - "lint": "eslint . --max-warnings 0", - "typecheck": "tsc --noEmit" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "json-canonicalize": "^2.0.0", - "jwt-decode": "^4.0.0", - "ox": "^0.9.17" - } -} diff --git a/packages/services/identity-instrument/src/challenge.ts b/packages/services/identity-instrument/src/challenge.ts deleted file mode 100644 index 53e3519dd6..0000000000 --- a/packages/services/identity-instrument/src/challenge.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { Bytes, Hash, Hex } from 'ox' -import { jwtDecode } from 'jwt-decode' -import { IdentityType, AuthMode, Key } from './identity-instrument.gen.js' - -interface CommitChallengeParams { - authMode: AuthMode - identityType: IdentityType - handle?: string - signer?: Key - metadata: { [key: string]: string } -} - -interface CompleteChallengeParams { - authMode: AuthMode - identityType: IdentityType - verifier: string - answer: string -} - -export abstract class Challenge { - public abstract getCommitParams(): CommitChallengeParams - public abstract getCompleteParams(): CompleteChallengeParams -} - -export class IdTokenChallenge extends Challenge { - private handle = '' - private exp = '' - - constructor( - readonly issuer: string, - readonly audience: string, - readonly idToken: string, - ) { - super() - const decoded = jwtDecode(this.idToken) - const idTokenHash = Hash.keccak256(new TextEncoder().encode(this.idToken)) - this.handle = Hex.fromBytes(idTokenHash) - this.exp = decoded.exp?.toString() ?? '' - } - - public getCommitParams(): CommitChallengeParams { - return { - authMode: AuthMode.IDToken, - identityType: IdentityType.OIDC, - handle: this.handle, - metadata: { - iss: this.issuer, - aud: this.audience, - exp: this.exp, - }, - } - } - - public getCompleteParams(): CompleteChallengeParams { - return { - authMode: AuthMode.IDToken, - identityType: IdentityType.OIDC, - verifier: this.handle, - answer: this.idToken, - } - } -} - -export class AuthCodeChallenge extends Challenge { - private handle = '' - private signer?: Key - - constructor( - readonly issuer: string, - readonly audience: string, - readonly redirectUri: string, - readonly authCode: string, - ) { - super() - const authCodeHash = Hash.keccak256(new TextEncoder().encode(this.authCode)) - this.handle = Hex.fromBytes(authCodeHash) - } - - public getCommitParams(): CommitChallengeParams { - return { - authMode: AuthMode.AuthCode, - identityType: IdentityType.OIDC, - signer: this.signer, - handle: this.handle, - metadata: { - iss: this.issuer, - aud: this.audience, - redirect_uri: this.redirectUri, - }, - } - } - - public getCompleteParams(): CompleteChallengeParams { - return { - authMode: AuthMode.AuthCode, - identityType: IdentityType.OIDC, - verifier: this.handle, - answer: this.authCode, - } - } - - public withSigner(signer: Key): AuthCodeChallenge { - const challenge = new AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, this.authCode) - challenge.handle = this.handle - challenge.signer = signer - return challenge - } -} - -export class AuthCodePkceChallenge extends Challenge { - private verifier?: string - private authCode?: string - private signer?: Key - - constructor( - readonly issuer: string, - readonly audience: string, - readonly redirectUri: string, - ) { - super() - } - - public getCommitParams(): CommitChallengeParams { - return { - authMode: AuthMode.AuthCodePKCE, - identityType: IdentityType.OIDC, - signer: this.signer, - metadata: { - iss: this.issuer, - aud: this.audience, - redirect_uri: this.redirectUri, - }, - } - } - - public getCompleteParams(): CompleteChallengeParams { - if (!this.verifier || !this.authCode) { - throw new Error('AuthCodePkceChallenge is not complete') - } - - return { - authMode: AuthMode.AuthCodePKCE, - identityType: IdentityType.OIDC, - verifier: this.verifier, - answer: this.authCode, - } - } - - public withSigner(signer: Key): AuthCodePkceChallenge { - const challenge = new AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) - challenge.verifier = this.verifier - challenge.signer = signer - return challenge - } - - public withAnswer(verifier: string, authCode: string): AuthCodePkceChallenge { - const challenge = new AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) - challenge.signer = this.signer - challenge.verifier = verifier - challenge.authCode = authCode - return challenge - } -} - -export class OtpChallenge extends Challenge { - private answer?: string - private recipient?: string - private signer?: Key - - private constructor(readonly identityType: IdentityType) { - super() - } - - public static fromRecipient(identityType: IdentityType, recipient: string): OtpChallenge { - const challenge = new OtpChallenge(identityType) - challenge.recipient = recipient - return challenge - } - - public static fromSigner(identityType: IdentityType, signer: Key): OtpChallenge { - const challenge = new OtpChallenge(identityType) - challenge.signer = signer - return challenge - } - - public getCommitParams(): CommitChallengeParams { - if (!this.recipient && (!this.signer || !this.signer.address || !this.signer.keyType)) { - throw new Error('OtpChallenge is not complete') - } - - return { - authMode: AuthMode.OTP, - identityType: this.identityType, - handle: this.recipient, - signer: this.signer, - metadata: {}, - } - } - - public getCompleteParams(): CompleteChallengeParams { - if (!this.answer || (!this.recipient && !this.signer)) { - throw new Error('OtpChallenge is not complete') - } - - return { - authMode: AuthMode.OTP, - identityType: this.identityType, - verifier: this.recipient ?? (this.signer ? `${this.signer.keyType}:${this.signer.address}` : ''), - answer: this.answer, - } - } - - public withAnswer(codeChallenge: string, otp: string): OtpChallenge { - const challenge = new OtpChallenge(this.identityType) - challenge.recipient = this.recipient - challenge.signer = this.signer - const answerHash = Hash.keccak256(Bytes.fromString(codeChallenge + otp)) - challenge.answer = Hex.fromBytes(answerHash) - return challenge - } -} diff --git a/packages/services/identity-instrument/src/identity-instrument.gen.ts b/packages/services/identity-instrument/src/identity-instrument.gen.ts deleted file mode 100644 index 6ee9d5d590..0000000000 --- a/packages/services/identity-instrument/src/identity-instrument.gen.ts +++ /dev/null @@ -1,781 +0,0 @@ -/* eslint-disable */ -// identity-instrument v0.1.0 b0ca08fbbd2e98d269d745176d4de5cbfa8960d6 -// -- -// Code generated by webrpc-gen@v0.23.1 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=identity-instrument.ridl -target=typescript -client -out=./clients/identity-instrument.gen.ts - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.23.1;gen-typescript@v0.16.3;identity-instrument@v0.1.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' - -// Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.1.0' - -// Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = 'b0ca08fbbd2e98d269d745176d4de5cbfa8960d6' - -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} - -// -// Types -// - -export enum KeyType { - WebCrypto_Secp256r1 = 'WebCrypto_Secp256r1', - Ethereum_Secp256k1 = 'Ethereum_Secp256k1', -} - -export enum IdentityType { - Email = 'Email', - OIDC = 'OIDC', -} - -export enum AuthMode { - OTP = 'OTP', - IDToken = 'IDToken', - AccessToken = 'AccessToken', - AuthCode = 'AuthCode', - AuthCodePKCE = 'AuthCodePKCE', -} - -export interface CommitVerifierParams { - scope?: string - identityType: IdentityType - authMode: AuthMode - metadata: { [key: string]: string } - handle?: string - signer?: Key -} - -export interface CompleteAuthParams { - scope?: string - identityType: IdentityType - signerType: KeyType - authMode: AuthMode - verifier: string - answer: string - lifetime?: number -} - -export interface SignParams { - scope?: string - signer: Key - nonce: string - digest: string -} - -export interface Identity { - type: IdentityType - issuer: string - subject: string - email: string -} - -export interface Key { - keyType: KeyType - address: string -} - -export interface AuthID { - scope: string - authMode: AuthMode - identityType: IdentityType - verifier: string -} - -export interface AuthKeyData { - scope: string - authKey: string - signer: string - expiry: string -} - -export interface SignerData { - scope: string - identity: Identity - keyType: KeyType - privateKey: string -} - -export interface AuthCommitmentData { - scope: string - authKey: string - authMode: AuthMode - identityType: IdentityType - handle: string - signer: string - challenge: string - answer: string - metadata: { [key: string]: string } - attempts: number - expiry: string -} - -export interface IdentityInstrument { - commitVerifier(args: CommitVerifierArgs, headers?: object, signal?: AbortSignal): Promise - completeAuth(args: CompleteAuthArgs, headers?: object, signal?: AbortSignal): Promise - sign(args: SignArgs, headers?: object, signal?: AbortSignal): Promise -} - -export interface CommitVerifierArgs { - params: CommitVerifierParams - authKey: Key - signature: string -} - -export interface CommitVerifierReturn { - verifier: string - loginHint: string - challenge: string -} -export interface CompleteAuthArgs { - params: CompleteAuthParams - authKey: Key - signature: string -} - -export interface CompleteAuthReturn { - signer: Key - identity: Identity -} -export interface SignArgs { - params: SignParams - authKey: Key - signature: string -} - -export interface SignReturn { - signature: string -} - -// -// Client -// -export class IdentityInstrument implements IdentityInstrument { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/IdentityInstrument/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - commitVerifier = ( - args: CommitVerifierArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CommitVerifier'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - verifier: _data.verifier, - loginHint: _data.loginHint, - challenge: _data.challenge, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - completeAuth = (args: CompleteAuthArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('CompleteAuth'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - signer: _data.signer, - identity: _data.identity, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - sign = (args: SignArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Sign'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - signature: _data.signature, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } -} - -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, - } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -// -// Errors -// - -export class WebrpcError extends Error { - name: string - code: number - message: string - status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) - } -} - -// Webrpc errors - -export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// Schema errors - -export class InternalErrorError extends WebrpcError { - constructor( - name: string = 'InternalError', - code: number = 7100, - message: string = `Internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InternalErrorError.prototype) - } -} - -export class EncryptionErrorError extends WebrpcError { - constructor( - name: string = 'EncryptionError', - code: number = 7101, - message: string = `Encryption error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, EncryptionErrorError.prototype) - } -} - -export class DatabaseErrorError extends WebrpcError { - constructor( - name: string = 'DatabaseError', - code: number = 7102, - message: string = `Database error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, DatabaseErrorError.prototype) - } -} - -export class DataIntegrityErrorError extends WebrpcError { - constructor( - name: string = 'DataIntegrityError', - code: number = 7103, - message: string = `Data integrity error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, DataIntegrityErrorError.prototype) - } -} - -export class IdentityProviderErrorError extends WebrpcError { - constructor( - name: string = 'IdentityProviderError', - code: number = 7104, - message: string = `Identity provider error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, IdentityProviderErrorError.prototype) - } -} - -export class InvalidRequestError extends WebrpcError { - constructor( - name: string = 'InvalidRequest', - code: number = 7200, - message: string = `The request was invalid`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidRequestError.prototype) - } -} - -export class InvalidSignatureError extends WebrpcError { - constructor( - name: string = 'InvalidSignature', - code: number = 7201, - message: string = `The signature was invalid`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidSignatureError.prototype) - } -} - -export class KeyNotFoundError extends WebrpcError { - constructor( - name: string = 'KeyNotFound', - code: number = 7202, - message: string = `The authentication key was not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, KeyNotFoundError.prototype) - } -} - -export class KeyExpiredError extends WebrpcError { - constructor( - name: string = 'KeyExpired', - code: number = 7203, - message: string = `The authentication key expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, KeyExpiredError.prototype) - } -} - -export class SignerNotFoundError extends WebrpcError { - constructor( - name: string = 'SignerNotFound', - code: number = 7204, - message: string = `The signer was not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SignerNotFoundError.prototype) - } -} - -export class ProofVerificationFailedError extends WebrpcError { - constructor( - name: string = 'ProofVerificationFailed', - code: number = 7002, - message: string = `The authentication proof could not be verified`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProofVerificationFailedError.prototype) - } -} - -export class AnswerIncorrectError extends WebrpcError { - constructor( - name: string = 'AnswerIncorrect', - code: number = 7003, - message: string = `The provided answer is incorrect`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AnswerIncorrectError.prototype) - } -} - -export class ChallengeExpiredError extends WebrpcError { - constructor( - name: string = 'ChallengeExpired', - code: number = 7004, - message: string = `The challenge has expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ChallengeExpiredError.prototype) - } -} - -export class TooManyAttemptsError extends WebrpcError { - constructor( - name: string = 'TooManyAttempts', - code: number = 7005, - message: string = `Too many attempts`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, TooManyAttemptsError.prototype) - } -} - -export class OAuthErrorError extends WebrpcError { - constructor( - name: string = 'OAuthError', - code: number = 7006, - message: string = `Failed to exchange OAuth credentials`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, OAuthErrorError.prototype) - } -} - -export class AccessErrorError extends WebrpcError { - constructor( - name: string = 'AccessError', - code: number = 7007, - message: string = `Access error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessErrorError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - InternalError = 'InternalError', - EncryptionError = 'EncryptionError', - DatabaseError = 'DatabaseError', - DataIntegrityError = 'DataIntegrityError', - IdentityProviderError = 'IdentityProviderError', - InvalidRequest = 'InvalidRequest', - InvalidSignature = 'InvalidSignature', - KeyNotFound = 'KeyNotFound', - KeyExpired = 'KeyExpired', - SignerNotFound = 'SignerNotFound', - ProofVerificationFailed = 'ProofVerificationFailed', - AnswerIncorrect = 'AnswerIncorrect', - ChallengeExpired = 'ChallengeExpired', - TooManyAttempts = 'TooManyAttempts', - OAuthError = 'OAuthError', - AccessError = 'AccessError', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - InternalError = 7100, - EncryptionError = 7101, - DatabaseError = 7102, - DataIntegrityError = 7103, - IdentityProviderError = 7104, - InvalidRequest = 7200, - InvalidSignature = 7201, - KeyNotFound = 7202, - KeyExpired = 7203, - SignerNotFound = 7204, - ProofVerificationFailed = 7002, - AnswerIncorrect = 7003, - ChallengeExpired = 7004, - TooManyAttempts = 7005, - OAuthError = 7006, - AccessError = 7007, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [7100]: InternalErrorError, - [7101]: EncryptionErrorError, - [7102]: DatabaseErrorError, - [7103]: DataIntegrityErrorError, - [7104]: IdentityProviderErrorError, - [7200]: InvalidRequestError, - [7201]: InvalidSignatureError, - [7202]: KeyNotFoundError, - [7203]: KeyExpiredError, - [7204]: SignerNotFoundError, - [7002]: ProofVerificationFailedError, - [7003]: AnswerIncorrectError, - [7004]: ChallengeExpiredError, - [7005]: TooManyAttemptsError, - [7006]: OAuthErrorError, - [7007]: AccessErrorError, -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/services/identity-instrument/src/index.ts b/packages/services/identity-instrument/src/index.ts deleted file mode 100644 index f7b477b6ce..0000000000 --- a/packages/services/identity-instrument/src/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Hex, Bytes } from 'ox' -import { canonicalize } from 'json-canonicalize' -import { - CommitVerifierReturn, - CompleteAuthReturn, - IdentityInstrument as IdentityInstrumentRpc, - KeyType, - IdentityType, - AuthMode, -} from './identity-instrument.gen.js' -export * as Client from './identity-instrument.gen.js' -import { Challenge } from './challenge.js' - -export type { CommitVerifierReturn, CompleteAuthReturn } -export { KeyType, IdentityType, AuthMode } -export * from './challenge.js' - -export class IdentityInstrument { - private scope?: string - private rpc: IdentityInstrumentRpc - - constructor(hostname: string, scope?: string, fetch = window.fetch) { - this.rpc = new IdentityInstrumentRpc(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.scope = scope - } - - async commitVerifier(authKey: AuthKey, challenge: Challenge) { - const params = { - ...challenge.getCommitParams(), - scope: this.scope, - } - const signature = await authKey.sign(Bytes.fromString(canonicalize(params))) - return this.rpc.commitVerifier({ - params, - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, - signature, - }) - } - - async completeAuth(authKey: AuthKey, challenge: Challenge) { - const params = { - ...challenge.getCompleteParams(), - signerType: KeyType.Ethereum_Secp256k1, - scope: this.scope, - } - const signature = await authKey.sign(Bytes.fromString(canonicalize(params))) - return this.rpc.completeAuth({ - params, - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, - signature, - }) - } - - async sign(authKey: AuthKey, digest: Bytes.Bytes) { - const params = { - scope: this.scope, - signer: { - address: authKey.signer, - keyType: KeyType.Ethereum_Secp256k1, - }, - digest: Hex.fromBytes(digest), - nonce: Hex.fromNumber(Date.now()), - } - const res = await this.rpc.sign({ - params, - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, - signature: await authKey.sign(Bytes.fromString(canonicalize(params))), - }) - Hex.assert(res.signature) - return res.signature - } -} - -export interface AuthKey { - signer: string - address: string - keyType: KeyType - sign(digest: Bytes.Bytes): Promise -} diff --git a/packages/services/identity-instrument/test/challenge.test.ts b/packages/services/identity-instrument/test/challenge.test.ts deleted file mode 100644 index 015def4735..0000000000 --- a/packages/services/identity-instrument/test/challenge.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { AuthCodeChallenge, AuthCodePkceChallenge, IdTokenChallenge, OtpChallenge } from '../src/challenge.js' -import { IdentityType, KeyType } from '../src/index.js' - -describe('IdTokenChallenge', () => { - const idToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImF1ZCI6ImF1ZGllbmNlIiwiaWF0IjoxNzE2MjM5MDIyLCJleHAiOjE4MTYyMzkwMjJ9.vo-hzFNUd8uzKmMVEj04eIiqeXfOQahZu9ZWGnJPE74' - - it('returns correct commit params', () => { - const challenge = new IdTokenChallenge('https://example.com', 'audience', idToken) - const params = challenge.getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('IDToken') - expect(params.identityType).toBe('OIDC') - expect(params.handle).toBe('0x800fa2a1ca87f4a37d7f0a2e1858d36cd622cc2970d886e7e8a00f82edca3455') - expect(params.metadata).toBeDefined() - expect(params.metadata.iss).toBe('https://example.com') - expect(params.metadata.aud).toBe('audience') - expect(params.metadata.exp).toBe('1816239022') - }) - - it('returns correct complete params', () => { - const challenge = new IdTokenChallenge('https://example.com', 'audience', idToken) - const params = challenge.getCompleteParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('IDToken') - expect(params.identityType).toBe('OIDC') - expect(params.verifier).toBe('0x800fa2a1ca87f4a37d7f0a2e1858d36cd622cc2970d886e7e8a00f82edca3455') - expect(params.answer).toBe(idToken) - }) -}) - -describe('AuthCodeChallenge', () => { - const authCode = '1234567890' - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } - - it('returns correct commit params', () => { - const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode) - const params = challenge.getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCode') - expect(params.identityType).toBe('OIDC') - expect(params.handle).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e') - expect(params.signer).toBeUndefined() - expect(params.metadata).toBeDefined() - expect(params.metadata.iss).toBe('https://example.com') - expect(params.metadata.aud).toBe('audience') - expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect') - }) - - it('returns correct commit params with signer', () => { - const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode) - const params = challenge.withSigner(signer).getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCode') - expect(params.identityType).toBe('OIDC') - expect(params.signer).toBe(signer) - expect(params.handle).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e') - expect(params.metadata).toBeDefined() - expect(params.metadata.iss).toBe('https://example.com') - expect(params.metadata.aud).toBe('audience') - expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect') - }) - - it('returns correct complete params', () => { - const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode) - const params = challenge.getCompleteParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCode') - expect(params.identityType).toBe('OIDC') - expect(params.verifier).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e') - expect(params.answer).toBe(authCode) - }) -}) - -describe('AuthCodePkceChallenge', () => { - const challenge = new AuthCodePkceChallenge('https://example.com', 'audience', 'https://dapp.com/redirect') - const authCode = '1234567890' - const verifier = 'verifier' - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } - - it('returns correct commit params', () => { - const params = challenge.getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCodePKCE') - expect(params.identityType).toBe('OIDC') - expect(params.handle).toBeUndefined() - expect(params.metadata).toBeDefined() - expect(params.metadata.iss).toBe('https://example.com') - expect(params.metadata.aud).toBe('audience') - expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect') - }) - - it('returns correct commit params with signer', () => { - const params = challenge.withSigner(signer).getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCodePKCE') - expect(params.identityType).toBe('OIDC') - expect(params.signer).toBe(signer) - expect(params.handle).toBeUndefined() - expect(params.metadata).toBeDefined() - expect(params.metadata.iss).toBe('https://example.com') - expect(params.metadata.aud).toBe('audience') - expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect') - }) - - it('returns correct complete params with answer and verifier', () => { - const params = challenge.withAnswer(verifier, authCode).getCompleteParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('AuthCodePKCE') - expect(params.identityType).toBe('OIDC') - expect(params.verifier).toBe(verifier) - expect(params.answer).toBe(authCode) - }) - - it('throws if answer and verifier are not provided', () => { - expect(() => challenge.getCompleteParams()).toThrow() - }) - - it('throws if answer is not provided', () => { - expect(() => challenge.withAnswer(verifier, '').getCompleteParams()).toThrow() - }) - - it('throws if verifier is not provided', () => { - expect(() => challenge.withAnswer('', authCode).getCompleteParams()).toThrow() - }) -}) - -describe('OtpChallenge', () => { - const otp = '123456' - const codeChallenge = 'codeChallenge' - - // finalAnswer = keccak256(codeChallenge + otp) - const finalAnswer = '0xab1b443dd7ae1f1dd51f81f8d346565c1a63e7d090a1c220e44ed578183b08f5' - - describe('fromRecipient', () => { - const recipient = 'test@example.com' - - describe('getCommitParams', () => { - it('returns correct commit params', () => { - const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient) - const params = challenge.getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('OTP') - expect(params.identityType).toBe('Email') - expect(params.handle).toBe(recipient) - expect(params.signer).toBeUndefined() - }) - - it('throws if recipient is not provided', () => { - const challenge = OtpChallenge.fromRecipient(IdentityType.Email, '') - expect(() => challenge.getCommitParams()).toThrow() - }) - }) - - describe('getCompleteParams', () => { - it('returns correct complete params', () => { - const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient) - const params = challenge.withAnswer(codeChallenge, otp).getCompleteParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('OTP') - expect(params.identityType).toBe('Email') - expect(params.verifier).toBe(recipient) - expect(params.answer).toBe(finalAnswer) - }) - - it('throws if answer is not provided', () => { - const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient) - expect(() => challenge.getCompleteParams()).toThrow() - }) - }) - }) - - describe('fromSigner', () => { - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } - - describe('getCommitParams', () => { - it('returns correct commit params', () => { - const challenge = OtpChallenge.fromSigner(IdentityType.Email, signer) - const params = challenge.getCommitParams() - expect(params).toBeDefined() - expect(params.authMode).toBe('OTP') - expect(params.identityType).toBe('Email') - expect(params.handle).toBeUndefined() - expect(params.signer).toBe(signer) - }) - - it('throws if signer is not provided', () => { - const challenge = OtpChallenge.fromSigner(IdentityType.Email, { - address: '', - keyType: KeyType.Ethereum_Secp256k1, - }) - expect(() => challenge.getCommitParams()).toThrow() - }) - }) - }) -}) diff --git a/packages/services/identity-instrument/tsconfig.json b/packages/services/identity-instrument/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/identity-instrument/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/identity-instrument/vitest.config.ts b/packages/services/identity-instrument/vitest.config.ts deleted file mode 100644 index 763b162158..0000000000 --- a/packages/services/identity-instrument/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - environment: 'happy-dom', - globals: true, - }, -}) diff --git a/packages/services/indexer/CHANGELOG.md b/packages/services/indexer/CHANGELOG.md deleted file mode 100644 index fb104e35fb..0000000000 --- a/packages/services/indexer/CHANGELOG.md +++ /dev/null @@ -1,1948 +0,0 @@ -# @0xsequence/indexer - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet - -## 2.0.12 - -### Patch Changes - -- api: update bindings - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints - -## 2.0.0 - -### Major Changes - -- ethers v6 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api - -## 1.10.9 - -### Patch Changes - -- waas minor update - -## 1.10.8 - -### Patch Changes - -- update metadata bindings - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia - -## 1.10.3 - -### Patch Changes - -- typing fix - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants - -## 1.9.36 - -### Patch Changes - -- guard: export client - -## 1.9.35 - -### Patch Changes - -- guard: update bindings - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email - -## 1.9.33 - -### Patch Changes - -- waas: umd build - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes - -## 1.9.30 - -### Patch Changes - -- update - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore - -## 1.9.23 - -### Patch Changes - -- update api client bindings - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings - -## 1.9.21 - -### Patch Changes - -- api client bindings - -## 1.9.20 - -### Patch Changes - -- api client bindings update - -## 1.9.19 - -### Patch Changes - -- waas update - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client - -## 1.9.8 - -### Patch Changes - -- waas client update - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer - -## 1.9.6 - -### Patch Changes - -- waas package update - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia - -## 1.9.1 - -### Patch Changes - -- analytics fix - -## 1.9.0 - -### Minor Changes - -- waas release - -## 1.8.8 - -### Patch Changes - -- update metadata bindings - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested - -## 1.8.1 - -### Patch Changes - -- update to analytics provider - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks - -## 1.6.3 - -### Patch Changes - -- network list update - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods - -## 1.4.2 - -### Patch Changes - -- guard: update bindings - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic - -## 1.4.0 - -### Minor Changes - -- project access key support - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions - -## 1.1.11 - -### Patch Changes - -- add homeverse configs - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object - -## 0.43.28 - -### Patch Changes - -- update api bindings - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM - -## 0.43.22 - -### Patch Changes - -- add zkevm chain - -## 0.43.21 - -### Patch Changes - -- api: update client bindings - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings - -## 0.43.19 - -### Patch Changes - -- session proof update - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods - -## 0.43.14 - -### Patch Changes - -- bump - -## 0.43.13 - -### Patch Changes - -- update rpc bindings - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter - -## 0.43.10 - -### Patch Changes - -- various improvements - -## 0.43.9 - -### Patch Changes - -- update deps - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings - -## 0.42.6 - -### Patch Changes - -- api bindings update - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options - -## 0.42.3 - -### Patch Changes - -- update api bindings - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' - -## 0.41.3 - -### Patch Changes - -- api bindings update - -## 0.41.2 - -### Patch Changes - -- api bindings update - -## 0.41.1 - -### Patch Changes - -- update default networks - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain - -## 0.40.5 - -### Patch Changes - -- api: update bindings - -## 0.40.4 - -### Patch Changes - -- add unreal transport - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option - -## 0.39.4 - -### Patch Changes - -- api: update client bindings - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider - -## 0.39.2 - -### Patch Changes - -- update umd name - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) - -## 0.36.7 - -### Patch Changes - -- fix missing break - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation - -## 0.35.10 - -### Patch Changes - -- upgrade deps - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -## 0.29.8 - -### Patch Changes - -- update api - -## 0.29.3 - -### Patch Changes - -- indexer: add bridge contract types - -## 0.29.0 - -### Minor Changes - -- major architectural changes in Sequence design - - only one API instance, API is no longer a per-chain service - - separate per-chain indexer service, API no longer handles indexing - - single contract metadata service, API no longer serves metadata - - chaind package has been removed, indexer and metadata packages have been added - - stronger typing with new explicit ChainId type - - multicall fixes and improvements - - forbid "wait" transactions in sendTransactionBatch calls diff --git a/packages/services/indexer/README.md b/packages/services/indexer/README.md deleted file mode 100644 index f468766d82..0000000000 --- a/packages/services/indexer/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/indexer - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/indexer/eslint.config.js b/packages/services/indexer/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/indexer/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/indexer/package.json b/packages/services/indexer/package.json deleted file mode 100644 index 7ad0de895c..0000000000 --- a/packages/services/indexer/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/indexer", - "version": "3.0.5", - "description": "indexer sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/indexer", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/indexer/src/index.ts b/packages/services/indexer/src/index.ts deleted file mode 100644 index 15f28c2cb1..0000000000 --- a/packages/services/indexer/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -export * from './indexer.gen.js' -export * as IndexerGateway from './indexergw.gen.js' - -import { Indexer as IndexerRpc } from './indexer.gen.js' -import { IndexerGateway as IndexerGatewayRpc } from './indexergw.gen.js' - -export class SequenceIndexer extends IndexerRpc { - constructor( - hostname: string, - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} - -export class SequenceIndexerGateway extends IndexerGatewayRpc { - constructor( - hostname: string, - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} diff --git a/packages/services/indexer/src/indexer.gen.ts b/packages/services/indexer/src/indexer.gen.ts deleted file mode 100644 index 8dba62d16d..0000000000 --- a/packages/services/indexer/src/indexer.gen.ts +++ /dev/null @@ -1,2896 +0,0 @@ -/* eslint-disable */ -// sequence-indexer v0.4.0 2bca559c4c590bce7d70c33df115a58399efec82 -// -- -// Code generated by Webrpc-gen@v0.31.2 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=merged.gen.json -service=Indexer -target=typescript -client -out=./clients/indexer.gen.ts - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.4.0' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '2bca559c4c590bce7d70c33df115a58399efec82' - -// -// Client interface -// - -export interface IndexerClient { - addWebhookListener( - req: AddWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Fetches a single receipt and then will stop the subscription - */ - fetchTransactionReceipt( - req: FetchTransactionReceiptRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Fetches a single receipt with filter and then will stop the subscription - */ - fetchTransactionReceiptWithFilter( - req: FetchTransactionReceiptWithFilterRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Webhooks - */ - getAllWebhookListeners( - req: GetAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Get balance update aggregate values -- useful for syncing balance details of a contract, ie. from Skyweaver. - * Also consider using SubscribeBalanceUpdates or SubscribeEvents as other alternatives. - */ - getBalanceUpdates( - req: GetBalanceUpdatesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Get the chain ID of the indexer - */ - getChainID(headers?: object, signal?: AbortSignal): Promise - - /** - * Queries an ethereum node for the latest and confirm ETH balances - * DEPRECATED: use GetNativeTokenBalance instead - * - * @deprecated GetNativeTokenBalance - */ - getEtherBalance(req: GetEtherBalanceRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * GetMarketplaceOrders queries marketplace orders with filtering and pagination. - * - * Retrieves buy orders (offers) and sell orders (listings) from a specific marketplace - * and collection with comprehensive filtering options. - * - * Parameters: - * marketplaceContractAddress: Target marketplace contract (required) - * collectionAddress: NFT collection contract (required) - * filter: MarketplaceOrderFilter with options: - * - isListing: true=listings, false=offers, omit=both - * - userAddresses: Include specific users - * - currencyAddresses: Filter by currencies (empty=all) - * - orderIds: Filter by specific order ids (empty=all) - * - tokenIds: Filter by specific tokens (empty=all) - * - excludeUserAddresses: Exclude specific users - * - blockNumberGt: Orders greater than block number - * - createdAtAfter: Orders after timestamp - * - orderStatuses: Filter by status (OPEN, CLOSED, CANCELLED) - * - returnExpired: Include expired orders - * page: Pagination control (optional) - * - * Returns: Updated pagination info and array of matching orders - */ - getMarketplaceOrders( - req: GetMarketplaceOrdersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetMarketplaceTopOrders finds the most competitive orders for specific tokens. - * - * Optimized for price discovery, returns the best available orders for each token. - * Useful for displaying current market prices and finding trading opportunities. - * - * Parameters: - * marketplaceContractAddress: Target marketplace contract (required) - * collectionAddress: NFT collection contract (required) - * filter: MarketplaceTopOrdersFilter with options: - * - currencyAddresses: Consider specific currencies (empty=all) - * - tokenIds: Target token IDs (required, non-empty) - * - isListing: true=listings/sell orders, false=offers/buy orders - * - priceSort: ASC=lowest first, DESC=highest first - * - excludeUser: Hide orders from specific user - * - * Returns: Array of top-priced active orders, sorted by priceSort preference - */ - getMarketplaceTopOrders( - req: GetMarketplaceTopOrdersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetNativeTokenBalance queries an ethereum node for the latest native token account balance. - * The native token is the token of the chain the indexer is connected to, for example, ETH on Ethereum - * and POL on Polygon. - */ - getNativeTokenBalance( - req: GetNativeTokenBalanceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalances returns a balance summary/details for a specific account. By default - * if accountAddress is left empty, it will use the account from the jwt session. - * - * Also, if contractAddress is undefined, then it will list all current user coins/collectibles. - * But, if contractAddress is provided, then it will return the token balances for the contract, this is - * only useful for 1155, but for other tokens, it can act as a filter for the single balance. - * - * DEPRECATED: use GetTokenBalancesSummary / GetTokenBalancesDetails - * - * @deprecated GetTokenBalancesSummary - */ - getTokenBalances( - req: GetTokenBalancesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesByContract returns a balances for a specific accounts and - * contracts. The collection ERC721 & ERC1155 tokens are represented as - * individual balances. - * - * If `filter` is not provided, it will error out as it requires at least - * contract address. - * - * If `filter.contractStatus` is not provided, it will include verified only - * tokens. - */ - getTokenBalancesByContract( - req: GetTokenBalancesByContractRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesDetails returns a detailed balance summary for a specific - * accounts. The collection ERC721 & ERC1155 tokens are represented as - * individual balances. - * - * If `filter` is not provided, it will use the filter with account from the - * jwt session. - * - * If `filter.contractStatus` is not provided, it will include verified only - * tokens. - */ - getTokenBalancesDetails( - req: GetTokenBalancesDetailsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesSummary returns a summary of token balances for a specific - * accounts. The collection ERC721 & ERC1155 tokens are represented as a - * single aggregated balance. - * - * If `filter` is not provided, it will use the filter with account from the - * jwt session. - * - * If `filter.contractStatus` is not provided, it will include verified only - * tokens. - */ - getTokenBalancesSummary( - req: GetTokenBalancesSummaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenIDRanges returns the range of tokenIDs for a token collection contract. - * This is useful for ERC-721 and ERC-1155 contracts to get the range of valid tokenIDs. It is similar to - * GetTokenIDs, but returns the range of tokenIDs instead of the list of tokenIDs, which is more efficient - * for large collections and very easy to the caller to expand the range into a list of tokenIDs. - * - * NOTE: this method will only return up to 15,000 ranges, if there are more ranges, it will return - * a boolean to indicate there are more ranges beyond the first 15,000. Therefore, if `moreRanges` is - * false then you have all the ranges, but if true, you need to make a follow up call to fetch the next - * page of ranges. - * - * As an example, if a NFT collection of 100,000 tokens uses ids from 1,2,3,...,100_000 then this endpoint - * will return just a single range from [1,100_000], but if there are gaps between the sequence, then - * those will be broken into separate range entries. - */ - getTokenIDRanges( - req: GetTokenIDRangesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenIDs returns the list of each individual token id for a token collection contract. - * This is useful for ERC-721 and ERC-1155 contracts to get the list of valid tokenIDs. - */ - getTokenIDs(req: GetTokenIDsRequest, headers?: object, signal?: AbortSignal): Promise - - getTokenPrice(req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal): Promise - - getTokenPrices(req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * GetTokenSupplies returns the set of tokenIDs used by a contract address, supporting ERC-20, ERC-721, and ERC-1155 - * contracts, and their respective supply as well. - */ - getTokenSupplies( - req: GetTokenSuppliesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenSuppliesMap returns the token supplies of ERC-20 and ERC-1155 tokens as requested in the `tokenMap` - * represented as a map of contractAddress :: []tokenIDs. - * - * For an ERC-20 specify tokenIDs as an empty array or [0], for example, { '0xdef': [] } or { '0xdef': [0] } - * For ERC-1155 pass the array of tokens are strings, ie. { '0xabc': ['1', '2', '3'] } - */ - getTokenSuppliesMap( - req: GetTokenSuppliesMapRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * History of mined transactions for the account which includes a list of token transfers (sent/recieved) - * and sent transactions from a Sequence wallet - */ - getTransactionHistory( - req: GetTransactionHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getWebhookListener( - req: GetWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listTokenPrices(req: ListTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise - - pauseAllWebhookListeners( - req: PauseAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Ping the indexer - */ - ping(headers?: object, signal?: AbortSignal): Promise - - removeAllWebhookListeners( - req: RemoveAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeWebhookListener( - req: RemoveWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - resumeAllWebhookListeners( - req: ResumeAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Get the current runtime health status of the indexer - */ - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - - /** - * SubscribeBalanceUpdates listens to balance updates for a specific contract address - */ - subscribeBalanceUpdates( - req: SubscribeBalanceUpdatesRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController - - /** - * SubscribeEvents listens to events on-chain based on the filter criteria - * - * TODO: some additional options can be passed such as block, reorg true, etc. - * or stay behind, etc. - */ - subscribeEvents( - req: SubscribeEventsRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController - - /** - * Listen to transaction receipts on-chain based on the filter criteria - */ - subscribeReceipts( - req: SubscribeReceiptsRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController - - /** - * Re-sync an incorrect token balance with the correct on-chain balance - * NOTE: this method is almost never used, but we've marked it internal in case - * we ever want to use it again. This method was written a very long time ago in - * scenarios when the indexer had little bugs, but now its solid. - */ - syncBalance(req: SyncBalanceRequest, headers?: object, signal?: AbortSignal): Promise - - toggleWebhookListener( - req: ToggleWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - updateWebhookListener( - req: UpdateWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Get the current version of the indexer - */ - version(headers?: object, signal?: AbortSignal): Promise -} - -// -// Schema types -// - -export interface Asset { - id: number - collectionId: number - tokenId?: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string -} - -export enum BackupMode { - INCREMENTAL = 'INCREMENTAL', - COMPLETE = 'COMPLETE', -} - -export interface BloomStats { - hitRatio: string - falsePositivesPercent: string - hitCount: number - missCount: number - falsePositives: number -} - -export interface BloomStatus { - enabled: boolean - initialized: boolean - bloomInitElapsedTime: string - stats: BloomStats -} - -export interface Bond { - pebble: PebbleMetrics - estimatedDiskUsagePerTable: any - estimatedDiskUsageTotal: string -} - -export interface ChainInfo { - chainId: number - chainName: string -} - -export interface ContractInfo { - chainId: number - address: string - source: string - name: string - type: string - symbol: string - decimals?: number - logoURI: string - deployed: boolean - bytecodeHash: string - extensions: ContractInfoExtensions - updatedAt: string - queuedAt?: string - status: ResourceStatus -} - -export interface ContractInfoExtensionBridgeInfo { - tokenAddress: string -} - -export interface ContractInfoExtensionIndexingInfo { - useOnChainBalance: boolean -} - -export interface ContractInfoExtensions { - link?: string - description?: string - categories?: Array - bridgeInfo?: { [key: string]: ContractInfoExtensionBridgeInfo } - indexingInfo?: ContractInfoExtensionIndexingInfo - ogImage?: string - ogName?: string - originChainId?: number - originAddress?: string - blacklist?: boolean - verified?: boolean - verifiedBy?: string - featured?: boolean - featureIndex?: number -} - -export enum ContractType { - UNKNOWN = 'UNKNOWN', - NATIVE = 'NATIVE', - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', - SEQUENCE_WALLET = 'SEQUENCE_WALLET', - ERC20_BRIDGE = 'ERC20_BRIDGE', - ERC721_BRIDGE = 'ERC721_BRIDGE', - ERC1155_BRIDGE = 'ERC1155_BRIDGE', - SEQ_MARKETPLACE = 'SEQ_MARKETPLACE', - ERC6909 = 'ERC6909', -} - -export enum ContractVerificationStatus { - VERIFIED = 'VERIFIED', - UNVERIFIED = 'UNVERIFIED', - ALL = 'ALL', -} - -export interface DiskUsage { - humanReadable: string - used: number - size: number - percent: number - dirs: { [key: string]: string } -} - -export interface EtherBalance { - accountAddress: string - balanceWei: string -} - -export interface EventDecoded { - topicHash: string - eventSig: string - types: Array - names: Array - values: Array -} - -export interface EventFilter { - events?: Array - contractAddresses?: Array - accounts?: Array - tokenIDs?: Array -} - -export interface EventLog { - id: number - uid: string - type: EventLogType - blockNumber: number - blockHash: string - parentBlockHash: string - contractAddress: string - contractType: ContractType - txnHash: string - txnIndex: number - txnLogIndex: number - logDataType: EventLogDataType - ts: string - txnInfo?: TxnInfo - rawLog?: { [key: string]: any } - event?: EventDecoded -} - -export enum EventLogDataType { - EVENT = 'EVENT', - TOKEN_TRANSFER = 'TOKEN_TRANSFER', - NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', - SEQUENCE_TXN = 'SEQUENCE_TXN', -} - -export enum EventLogType { - UNKNOWN = 'UNKNOWN', - BLOCK_ADDED = 'BLOCK_ADDED', - BLOCK_REMOVED = 'BLOCK_REMOVED', -} - -export interface GatewayBackendResponseTime { - percentiles: { [key: string]: number } - average: number -} - -export interface GatewayBackendRuntimeStatus { - name: string - chainId: number - responseTime: GatewayBackendResponseTime -} - -export interface GatewayEtherBalance { - chainId: number - errorReason?: string - result: EtherBalance -} - -export interface GatewayNativeTokenBalance { - chainId: number - errorReason?: string - result: NativeTokenBalance -} - -export interface GatewayNativeTokenBalances { - chainId: number - errorReason?: string - results: Array -} - -export interface GatewayPrice { - chainID: number - errorReason?: string - results: Array -} - -export interface GatewayRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - backends: Array -} - -export interface GatewayTokenBalance { - chainId: number - errorReason?: string - results: Array -} - -export interface GatewayTokenPriceQuery { - chainID: number - queries: Array -} - -export interface GatewayTransaction { - chainId: number - errorReason?: string - results: Array -} - -export interface IndexState { - chainId: string - lastBlockNum: number - lastBlockHash: string -} - -export interface IndexedBlock { - blockNumber: number - blockShortHash: string -} - -export interface IndexerMetrics { - blocksPerSecond: number - eventsPerSecond: number -} - -export interface MarketplaceOrder { - orderId: string - tokenContract: string - tokenId: string - isListing: boolean - quantity: string - quantityRemaining: string - currencyAddress: string - pricePerToken: string - expiry: string - orderStatus: OrderStatus - createdBy: string - blockNumber: number - orderbookContractAddress: string - createdAt: number -} - -export interface MarketplaceOrderFilter { - isListing?: boolean - userAddresses?: Array - currencyAddresses: Array - orderIds: Array - tokenIds: Array - excludeUserAddresses?: Array - blockNumberGt: number - createdAtAfter: number - orderStatuses: Array - returnExpired: boolean -} - -export interface MarketplaceTopOrdersFilter { - currencyAddresses: Array - tokenIds: Array - isListing: boolean - priceSort: SortOrder - excludeUser?: string -} - -export interface MetadataOptions { - verifiedOnly?: boolean - unverifiedOnly?: boolean - includeContracts?: Array -} - -export interface NativeTokenBalance { - accountAddress: string - chainId: number - name: string - symbol: string - balance: string - balanceUSD: string - priceUSD: string - priceUpdatedAt?: string - errorReason?: string -} - -export enum NetworkType { - MAINNETS = 'MAINNETS', - TESTNETS = 'TESTNETS', - ALL = 'ALL', -} - -export enum OrderStatus { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - CANCELLED = 'CANCELLED', -} - -export interface Page { - page?: number - column?: string - before?: any - after?: any - sort?: Array - pageSize?: number - more?: boolean -} - -export interface PebbleMetrics { - compactionCount: number - compactionEstimatedDebt: number - compactionInProgressBytes: number - compactionNumInProgress: number - compactionMarkedFiles: number -} - -export interface Price { - contractAddress: string - tokenID?: string - priceUSD: string - updatedAt?: string -} - -export enum ResourceStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', -} - -export interface RuntimeChecks { - running: boolean - runnables: any - cgoEnabled: boolean - quotaControlEnabled: boolean - syncMode: string - percentIndexed: number - lastBlockNum: number - lastBlockNumWithState: number - bloomStatus: BloomStatus - bond: Bond - diskUsage: DiskUsage - metrics: IndexerMetrics -} - -export interface RuntimeStatus { - healthOK: boolean - indexerEnabled: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - checks: RuntimeChecks -} - -export interface SortBy { - column: string - order: SortOrder -} - -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', -} - -export interface TokenBalance { - contractType: ContractType - contractAddress: string - accountAddress: string - tokenID?: string - balance: string - balanceUSD: string - priceUSD: string - priceUpdatedAt?: string - blockHash: string - blockNumber: number - chainId: number - uniqueCollectibles: string - isSummary: boolean - contractInfo?: ContractInfo - tokenMetadata?: TokenMetadata -} - -export interface TokenBalanceFilter { - contractAddress: string - sinceBlockNumber: number -} - -export interface TokenBalancesByContractFilter { - contractAddresses: Array - accountAddresses?: Array - contractStatus?: ContractVerificationStatus - tokenIDs?: Array -} - -export interface TokenBalancesFilter { - accountAddresses: Array - contractStatus?: ContractVerificationStatus - contractTypes?: Array - contractWhitelist?: Array - contractBlacklist?: Array - omitNativeBalances: boolean - omitPrices?: boolean - tokenIDs?: Array -} - -export interface TokenHistory { - blockNumber: number - blockHash: string - contractAddress: string - contractType: ContractType - fromAddress: string - toAddress: string - txnHash: string - txnIndex: number - txnLogIndex: number - tokenIDs: string - amounts: string - ts: string -} - -export interface TokenIDRange { - start: string - end: string -} - -export interface TokenMetadata { - chainId?: number - contractAddress?: string - tokenId: string - source: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: ResourceStatus - queuedAt?: string - lastFetched?: string -} - -export interface TokenPriceQuery { - contractAddress: string - tokenID?: string -} - -export interface TokenSupply { - tokenID: string - supply: string - chainId: number - contractInfo?: ContractInfo - tokenMetadata?: TokenMetadata -} - -export interface Transaction { - txnHash: string - blockNumber: number - blockHash: string - chainId: number - metaTxnID?: string - transfers?: Array - timestamp: string -} - -export interface TransactionFilter { - txnHash?: string - from?: string - to?: string - contractAddress?: string - event?: string -} - -export interface TransactionHistoryFilter { - accountAddress?: string - contractAddress?: string - accountAddresses?: Array - contractAddresses?: Array - transactionHashes?: Array - metaTransactionIDs?: Array - fromBlock?: number - toBlock?: number - tokenID?: string - omitPrices?: boolean -} - -export interface TransactionLog { - contractAddress: string - topics: Array - data: string - index: number -} - -export interface TransactionReceipt { - txnHash: string - txnStatus: TransactionStatus - txnIndex: number - txnType: TransactionType - blockHash: string - blockNumber: number - gasUsed: number - effectiveGasPrice: string - from: string - to: string - logs: Array - final: boolean - reorged: boolean -} - -export enum TransactionStatus { - FAILED = 'FAILED', - SUCCESSFUL = 'SUCCESSFUL', -} - -export enum TransactionType { - LegacyTxnType = 'LegacyTxnType', - AccessListTxnType = 'AccessListTxnType', - DynamicFeeTxnType = 'DynamicFeeTxnType', -} - -export interface TxnInfo { - from: string - to: string - value: string -} - -export interface TxnTransfer { - transferType: TxnTransferType - contractAddress: string - contractType: ContractType - from: string - to: string - tokenIds?: Array - amounts: Array - logIndex: number - amountsUSD?: Array - pricesUSD?: Array - contractInfo?: ContractInfo - tokenMetadata?: { [key: string]: TokenMetadata } -} - -export enum TxnTransferType { - UNKNOWN = 'UNKNOWN', - SEND = 'SEND', - RECEIVE = 'RECEIVE', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface WALWriterRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - percentWALWritten: number -} - -export interface WebhookListener { - id: number - projectID: number - url: string - filters: EventFilter - name: string - updatedAt: string - active: boolean -} - -export interface AddWebhookListenerRequest { - url: string - filters: EventFilter - projectId?: number -} - -export interface AddWebhookListenerResponse { - status: boolean - listener: WebhookListener -} - -export interface FetchTransactionReceiptRequest { - txnHash: string - maxBlockWait?: number -} - -export interface FetchTransactionReceiptResponse { - receipt: TransactionReceipt -} - -export interface FetchTransactionReceiptWithFilterRequest { - filter: TransactionFilter - maxBlockWait?: number -} - -export interface FetchTransactionReceiptWithFilterResponse { - receipt: TransactionReceipt -} - -export interface GetAllWebhookListenersRequest { - projectId?: number -} - -export interface GetAllWebhookListenersResponse { - listeners: Array -} - -export interface GetBalanceUpdatesRequest { - contractAddress: string - lastBlockNumber: number - lastBlockHash?: string - page?: Page -} - -export interface GetBalanceUpdatesResponse { - page: Page - balances: Array -} - -export interface GetChainIDRequest {} - -export interface GetChainIDResponse { - chainID: number -} - -export interface GetEtherBalanceRequest { - accountAddress?: string -} - -export interface GetEtherBalanceResponse { - balance: EtherBalance -} - -export interface GetMarketplaceOrdersRequest { - marketplaceContractAddress: string - collectionAddress: string - filter?: MarketplaceOrderFilter - page?: Page -} - -export interface GetMarketplaceOrdersResponse { - page?: Page - orders: Array -} - -export interface GetMarketplaceTopOrdersRequest { - marketplaceContractAddress: string - collectionAddress: string - filter: MarketplaceTopOrdersFilter -} - -export interface GetMarketplaceTopOrdersResponse { - orders: Array -} - -export interface GetNativeTokenBalanceRequest { - accountAddress?: string - omitPrices?: boolean -} - -export interface GetNativeTokenBalanceResponse { - balance: NativeTokenBalance -} - -export interface GetTokenBalancesRequest { - accountAddress?: string - contractAddress?: string - tokenID?: string - includeMetadata?: boolean - metadataOptions?: MetadataOptions - includeCollectionTokens?: boolean - page?: Page -} - -export interface GetTokenBalancesResponse { - page: Page - balances: Array -} - -export interface GetTokenBalancesByContractRequest { - filter: TokenBalancesByContractFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesByContractResponse { - page: Page - balances: Array -} - -export interface GetTokenBalancesDetailsRequest { - filter: TokenBalancesFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesDetailsResponse { - page: Page - nativeBalances: Array - balances: Array -} - -export interface GetTokenBalancesSummaryRequest { - filter: TokenBalancesFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesSummaryResponse { - page: Page - nativeBalances: Array - balances: Array -} - -export interface GetTokenIDRangesRequest { - contractAddress: string - lastTokenID?: string -} - -export interface GetTokenIDRangesResponse { - contractType: ContractType - tokenIDRanges: Array - moreRanges: boolean -} - -export interface GetTokenIDsRequest { - contractAddress: string - page?: Page -} - -export interface GetTokenIDsResponse { - page: Page - contractType: ContractType - tokenIDs: Array -} - -export interface GetTokenPriceRequest { - q: TokenPriceQuery -} - -export interface GetTokenPriceResponse { - price: Price -} - -export interface GetTokenPricesRequest { - q: Array -} - -export interface GetTokenPricesResponse { - prices: Array -} - -export interface GetTokenSuppliesRequest { - contractAddress: string - includeMetadata?: boolean - page?: Page -} - -export interface GetTokenSuppliesResponse { - page: Page - contractType: ContractType - tokenIDs: Array -} - -export interface GetTokenSuppliesMapRequest { - tokenMap: { [key: string]: Array } - includeMetadata?: boolean -} - -export interface GetTokenSuppliesMapResponse { - supplies: { [key: string]: Array } -} - -export interface GetTransactionHistoryRequest { - filter: TransactionHistoryFilter - page?: Page - includeMetadata?: boolean - metadataOptions?: MetadataOptions -} - -export interface GetTransactionHistoryResponse { - page: Page - transactions: Array -} - -export interface GetWebhookListenerRequest { - id: number - projectId?: number -} - -export interface GetWebhookListenerResponse { - listener: WebhookListener -} - -export interface ListTokenPricesRequest { - page?: Page -} - -export interface ListTokenPricesResponse { - page: Page - prices: Array -} - -export interface PauseAllWebhookListenersRequest { - projectId?: number -} - -export interface PauseAllWebhookListenersResponse { - status: boolean -} - -export interface PingRequest {} - -export interface PingResponse { - status: boolean -} - -export interface RemoveAllWebhookListenersRequest { - projectId?: number -} - -export interface RemoveAllWebhookListenersResponse { - status: boolean -} - -export interface RemoveWebhookListenerRequest { - id: number - projectId?: number -} - -export interface RemoveWebhookListenerResponse { - status: boolean -} - -export interface ResumeAllWebhookListenersRequest { - projectId?: number -} - -export interface ResumeAllWebhookListenersResponse { - status: boolean -} - -export interface RuntimeStatusRequest {} - -export interface RuntimeStatusResponse { - status: RuntimeStatus -} - -export interface SubscribeBalanceUpdatesRequest { - contractAddress: string -} - -export interface SubscribeBalanceUpdatesResponse { - balance: TokenBalance -} - -export interface SubscribeEventsRequest { - filter: EventFilter -} - -export interface SubscribeEventsResponse { - log: EventLog -} - -export interface SubscribeReceiptsRequest { - filter: TransactionFilter -} - -export interface SubscribeReceiptsResponse { - receipt: TransactionReceipt -} - -export interface SyncBalanceRequest { - accountAddress: string - contractAddress: string - tokenID?: string -} - -export interface SyncBalanceResponse {} - -export interface ToggleWebhookListenerRequest { - id: number - projectId?: number -} - -export interface ToggleWebhookListenerResponse { - webhookListener: WebhookListener -} - -export interface UpdateWebhookListenerRequest { - listener: WebhookListener - projectId?: number -} - -export interface UpdateWebhookListenerResponse { - status: boolean -} - -export interface VersionRequest {} - -export interface VersionResponse { - version: Version -} - -// -// Client -// - -export class Indexer implements IndexerClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Indexer/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - addWebhookListener: (req: AddWebhookListenerRequest) => ['Indexer', 'addWebhookListener', req] as const, - fetchTransactionReceipt: (req: FetchTransactionReceiptRequest) => - ['Indexer', 'fetchTransactionReceipt', req] as const, - fetchTransactionReceiptWithFilter: (req: FetchTransactionReceiptWithFilterRequest) => - ['Indexer', 'fetchTransactionReceiptWithFilter', req] as const, - getAllWebhookListeners: (req: GetAllWebhookListenersRequest) => ['Indexer', 'getAllWebhookListeners', req] as const, - getBalanceUpdates: (req: GetBalanceUpdatesRequest) => ['Indexer', 'getBalanceUpdates', req] as const, - getChainID: () => ['Indexer', 'getChainID'] as const, - getEtherBalance: (req: GetEtherBalanceRequest) => ['Indexer', 'getEtherBalance', req] as const, - getMarketplaceOrders: (req: GetMarketplaceOrdersRequest) => ['Indexer', 'getMarketplaceOrders', req] as const, - getMarketplaceTopOrders: (req: GetMarketplaceTopOrdersRequest) => - ['Indexer', 'getMarketplaceTopOrders', req] as const, - getNativeTokenBalance: (req: GetNativeTokenBalanceRequest) => ['Indexer', 'getNativeTokenBalance', req] as const, - getTokenBalances: (req: GetTokenBalancesRequest) => ['Indexer', 'getTokenBalances', req] as const, - getTokenBalancesByContract: (req: GetTokenBalancesByContractRequest) => - ['Indexer', 'getTokenBalancesByContract', req] as const, - getTokenBalancesDetails: (req: GetTokenBalancesDetailsRequest) => - ['Indexer', 'getTokenBalancesDetails', req] as const, - getTokenBalancesSummary: (req: GetTokenBalancesSummaryRequest) => - ['Indexer', 'getTokenBalancesSummary', req] as const, - getTokenIDRanges: (req: GetTokenIDRangesRequest) => ['Indexer', 'getTokenIDRanges', req] as const, - getTokenIDs: (req: GetTokenIDsRequest) => ['Indexer', 'getTokenIDs', req] as const, - getTokenPrice: (req: GetTokenPriceRequest) => ['Indexer', 'getTokenPrice', req] as const, - getTokenPrices: (req: GetTokenPricesRequest) => ['Indexer', 'getTokenPrices', req] as const, - getTokenSupplies: (req: GetTokenSuppliesRequest) => ['Indexer', 'getTokenSupplies', req] as const, - getTokenSuppliesMap: (req: GetTokenSuppliesMapRequest) => ['Indexer', 'getTokenSuppliesMap', req] as const, - getTransactionHistory: (req: GetTransactionHistoryRequest) => ['Indexer', 'getTransactionHistory', req] as const, - getWebhookListener: (req: GetWebhookListenerRequest) => ['Indexer', 'getWebhookListener', req] as const, - listTokenPrices: (req: ListTokenPricesRequest) => ['Indexer', 'listTokenPrices', req] as const, - pauseAllWebhookListeners: (req: PauseAllWebhookListenersRequest) => - ['Indexer', 'pauseAllWebhookListeners', req] as const, - ping: () => ['Indexer', 'ping'] as const, - removeAllWebhookListeners: (req: RemoveAllWebhookListenersRequest) => - ['Indexer', 'removeAllWebhookListeners', req] as const, - removeWebhookListener: (req: RemoveWebhookListenerRequest) => ['Indexer', 'removeWebhookListener', req] as const, - resumeAllWebhookListeners: (req: ResumeAllWebhookListenersRequest) => - ['Indexer', 'resumeAllWebhookListeners', req] as const, - runtimeStatus: () => ['Indexer', 'runtimeStatus'] as const, - subscribeBalanceUpdates: (req: SubscribeBalanceUpdatesRequest) => - ['Indexer', 'subscribeBalanceUpdates', req] as const, - subscribeEvents: (req: SubscribeEventsRequest) => ['Indexer', 'subscribeEvents', req] as const, - subscribeReceipts: (req: SubscribeReceiptsRequest) => ['Indexer', 'subscribeReceipts', req] as const, - syncBalance: (req: SyncBalanceRequest) => ['Indexer', 'syncBalance', req] as const, - toggleWebhookListener: (req: ToggleWebhookListenerRequest) => ['Indexer', 'toggleWebhookListener', req] as const, - updateWebhookListener: (req: UpdateWebhookListenerRequest) => ['Indexer', 'updateWebhookListener', req] as const, - version: () => ['Indexer', 'version'] as const, - } - - addWebhookListener = ( - req: AddWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddWebhookListenerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - fetchTransactionReceipt = ( - req: FetchTransactionReceiptRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FetchTransactionReceipt'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FetchTransactionReceiptResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - fetchTransactionReceiptWithFilter = ( - req: FetchTransactionReceiptWithFilterRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('FetchTransactionReceiptWithFilter'), - createHttpRequest(JsonEncode(req), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode( - _data, - 'FetchTransactionReceiptWithFilterResponse', - ) - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getAllWebhookListeners = ( - req: GetAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetAllWebhookListenersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getBalanceUpdates = ( - req: GetBalanceUpdatesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetBalanceUpdates'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetBalanceUpdatesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getChainID = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetChainID'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetChainIDResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getEtherBalance = ( - req: GetEtherBalanceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetEtherBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetEtherBalanceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMarketplaceOrders = ( - req: GetMarketplaceOrdersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMarketplaceOrders'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMarketplaceOrdersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMarketplaceTopOrders = ( - req: GetMarketplaceTopOrdersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMarketplaceTopOrders'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMarketplaceTopOrdersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getNativeTokenBalance = ( - req: GetNativeTokenBalanceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetNativeTokenBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetNativeTokenBalanceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalances = ( - req: GetTokenBalancesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalances'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesByContract = ( - req: GetTokenBalancesByContractRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesByContract'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesByContractResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesDetails = ( - req: GetTokenBalancesDetailsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesDetails'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesDetailsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesSummary = ( - req: GetTokenBalancesSummaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesSummary'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesSummaryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenIDRanges = ( - req: GetTokenIDRangesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenIDRanges'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenIDRangesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenIDs = (req: GetTokenIDsRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTokenIDs'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenIDsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenPrice = ( - req: GetTokenPriceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenPrice'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenPriceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenPrices = ( - req: GetTokenPricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenPricesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenSupplies = ( - req: GetTokenSuppliesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenSupplies'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenSuppliesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenSuppliesMap = ( - req: GetTokenSuppliesMapRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenSuppliesMap'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenSuppliesMapResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTransactionHistory = ( - req: GetTransactionHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTransactionHistory'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTransactionHistoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getWebhookListener = ( - req: GetWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetWebhookListenerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listTokenPrices = ( - req: ListTokenPricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListTokenPricesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - pauseAllWebhookListeners = ( - req: PauseAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('PauseAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PauseAllWebhookListenersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PingResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeAllWebhookListeners = ( - req: RemoveAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveAllWebhookListenersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeWebhookListener = ( - req: RemoveWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveWebhookListenerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - resumeAllWebhookListeners = ( - req: ResumeAllWebhookListenersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ResumeAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ResumeAllWebhookListenersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RuntimeStatusResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - subscribeBalanceUpdates = ( - req: SubscribeBalanceUpdatesRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController => { - const abortController = new AbortController() - const abortSignal = abortController.signal - - if (options.signal) { - abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { - signal: options.signal, - }) - } - - const _fetch = () => - this.fetch( - this.url('SubscribeBalanceUpdates'), - createHttpRequest(JsonEncode(req), options.headers, abortSignal), - ).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - - const resp = _fetch() - return { - abort: abortController.abort.bind(abortController), - closed: resp, - } - } - subscribeEvents = ( - req: SubscribeEventsRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController => { - const abortController = new AbortController() - const abortSignal = abortController.signal - - if (options.signal) { - abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { - signal: options.signal, - }) - } - - const _fetch = () => - this.fetch(this.url('SubscribeEvents'), createHttpRequest(JsonEncode(req), options.headers, abortSignal)).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - - const resp = _fetch() - return { - abort: abortController.abort.bind(abortController), - closed: resp, - } - } - subscribeReceipts = ( - req: SubscribeReceiptsRequest, - options: WebrpcStreamOptions, - ): WebrpcStreamController => { - const abortController = new AbortController() - const abortSignal = abortController.signal - - if (options.signal) { - abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { - signal: options.signal, - }) - } - - const _fetch = () => - this.fetch(this.url('SubscribeReceipts'), createHttpRequest(JsonEncode(req), options.headers, abortSignal)).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - - const resp = _fetch() - return { - abort: abortController.abort.bind(abortController), - closed: resp, - } - } - syncBalance = (req: SyncBalanceRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SyncBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SyncBalanceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - toggleWebhookListener = ( - req: ToggleWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ToggleWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ToggleWebhookListenerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateWebhookListener = ( - req: UpdateWebhookListenerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateWebhookListenerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'VersionResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const sseResponse = async (res: Response, options: WebrpcStreamOptions, retryFetch: () => Promise) => { - const { onMessage, onOpen, onClose, onError } = options - - if (!res.ok) { - try { - await buildResponse(res) - } catch (error) { - // @ts-ignore - onError(error, retryFetch) - } - return - } - - if (!res.body) { - onError( - WebrpcBadResponseError.new({ - status: res.status, - cause: 'Invalid response, missing body', - }), - retryFetch, - ) - return - } - - onOpen && onOpen() - - const reader = res.body.getReader() - const decoder = new TextDecoder() - let buffer = '' - let lastReadTime = Date.now() - const timeout = (10 + 1) * 1000 - let timeoutError = false - const intervalId = setInterval(() => { - if (Date.now() - lastReadTime > timeout) { - timeoutError = true - clearInterval(intervalId) - reader.releaseLock() - } - }, timeout) - - while (true) { - let value - let done - try { - ;({ value, done } = await reader.read()) - if (timeoutError) throw new Error('Timeout, no data or heartbeat received') - lastReadTime = Date.now() - buffer += decoder.decode(value, { stream: true }) - } catch (error) { - if (error instanceof DOMException && error.name === 'AbortError') { - onError( - WebrpcClientAbortedError.new({ - message: 'AbortError', - cause: `AbortError: ${error instanceof Error ? error.message : String(error)}`, - }), - () => { - throw new Error('Abort signal cannot be used to reconnect') - }, - ) - } else { - onError( - WebrpcStreamLostError.new({ - cause: `reader.read(): ${error instanceof Error ? error.message : String(error)}`, - }), - retryFetch, - ) - } - return - } - - let lines = buffer.split('\n') - for (let i = 0; i < lines.length - 1; i++) { - const line = lines[i] - if (line?.length === 0) { - continue - } - let data: any - try { - data = JSON.parse(line) - if (data.hasOwnProperty('webrpcError')) { - const error = data.webrpcError - const code: number = typeof error.code === 'number' ? error.code : 0 - onError((webrpcErrorByCode[code] || WebrpcError).new(error), retryFetch) - return - } - } catch (error) { - if (error instanceof Error && error.message === 'Abort signal cannot be used to reconnect') { - throw error - } - onError( - WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}`, - }), - retryFetch, - ) - } - onMessage(data) - } - - if (!done) { - const lastLine = lines[lines.length - 1] - buffer = lastLine || '' - continue - } - - onClose && onClose() - return - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { - ...headers, - 'Content-Type': 'application/json', - [WebrpcHeader]: WebrpcHeaderValue, - } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -export interface WebrpcStreamOptions extends WebrpcOptions { - onMessage: (message: T) => void - onError: (error: WebrpcError, reconnect: () => void) => void - onOpen?: () => void - onClose?: () => void -} - -export interface WebrpcOptions { - headers?: HeadersInit - signal?: AbortSignal -} - -export interface WebrpcStreamController { - abort: (reason?: any) => void - closed: Promise -} - -export const JsonEncode = (obj: T): string => { - return JSON.stringify(obj) -} - -export const JsonDecode = (data: string | any, _typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return parsed as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class AbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Aborted' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request aborted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class AccessKeyMismatchError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyMismatch' - this.code = typeof error.code === 'number' ? error.code : 1102 - this.message = error.message || `Access key mismatch` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) - } -} - -export class AccessKeyNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyNotFound' - this.code = typeof error.code === 'number' ? error.code : 1101 - this.message = error.message || `Access key not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) - } -} - -export class AtLeastOneKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AtLeastOneKey' - this.code = typeof error.code === 'number' ? error.code : 1302 - this.message = error.message || `You need at least one Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2001 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class InvalidOriginError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidOrigin' - this.code = typeof error.code === 'number' ? error.code : 1103 - this.message = error.message || `Invalid origin for Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidOriginError.prototype) - } -} - -export class InvalidServiceError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidService' - this.code = typeof error.code === 'number' ? error.code : 1104 - this.message = error.message || `Service not enabled for Access key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidServiceError.prototype) - } -} - -export class MaxAccessKeysError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MaxAccessKeys' - this.code = typeof error.code === 'number' ? error.code : 1301 - this.message = error.message || `Access keys limit reached` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) - } -} - -export class MetadataCallFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MetadataCallFailed' - this.code = typeof error.code === 'number' ? error.code : 3003 - this.message = error.message || `Metadata service call failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MetadataCallFailedError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class NoDefaultKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NoDefaultKey' - this.code = typeof error.code === 'number' ? error.code : 1300 - this.message = error.message || `No default access key found` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 1100 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class QuotaExceededError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaExceeded' - this.code = typeof error.code === 'number' ? error.code : 1200 - this.message = error.message || `Quota exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaExceededError.prototype) - } -} - -export class RateLimitError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimit' - this.code = typeof error.code === 'number' ? error.code : 1201 - this.message = error.message || `Rate limit exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Rate-limited. Please slow down.` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class ResourceExhaustedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ResourceExhausted' - this.code = typeof error.code === 'number' ? error.code : 2004 - this.message = error.message || `Resource exhausted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ResourceExhaustedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Timeout' - this.code = typeof error.code === 'number' ? error.code : 1900 - this.message = error.message || `Request timed out` - this.status = typeof error.status === 'number' ? error.status : 408 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class UnauthorizedUserError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnauthorizedUser' - this.code = typeof error.code === 'number' ? error.code : 1105 - this.message = error.message || `Unauthorized user` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unavailable' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Unavailable resource` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Aborted = 'Aborted', - AccessKeyMismatch = 'AccessKeyMismatch', - AccessKeyNotFound = 'AccessKeyNotFound', - AtLeastOneKey = 'AtLeastOneKey', - Geoblocked = 'Geoblocked', - InvalidArgument = 'InvalidArgument', - InvalidOrigin = 'InvalidOrigin', - InvalidService = 'InvalidService', - MaxAccessKeys = 'MaxAccessKeys', - MetadataCallFailed = 'MetadataCallFailed', - MethodNotFound = 'MethodNotFound', - NoDefaultKey = 'NoDefaultKey', - NotFound = 'NotFound', - PermissionDenied = 'PermissionDenied', - ProjectNotFound = 'ProjectNotFound', - QueryFailed = 'QueryFailed', - QuotaExceeded = 'QuotaExceeded', - RateLimit = 'RateLimit', - RateLimited = 'RateLimited', - RequestConflict = 'RequestConflict', - ResourceExhausted = 'ResourceExhausted', - SessionExpired = 'SessionExpired', - Timeout = 'Timeout', - Unauthorized = 'Unauthorized', - UnauthorizedUser = 'UnauthorizedUser', - Unavailable = 'Unavailable', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Aborted = 1005, - AccessKeyMismatch = 1102, - AccessKeyNotFound = 1101, - AtLeastOneKey = 1302, - Geoblocked = 1006, - InvalidArgument = 2001, - InvalidOrigin = 1103, - InvalidService = 1104, - MaxAccessKeys = 1301, - MetadataCallFailed = 3003, - MethodNotFound = 1003, - NoDefaultKey = 1300, - NotFound = 3000, - PermissionDenied = 1001, - ProjectNotFound = 1100, - QueryFailed = 2003, - QuotaExceeded = 1200, - RateLimit = 1201, - RateLimited = 1007, - RequestConflict = 1004, - ResourceExhausted = 2004, - SessionExpired = 1002, - Timeout = 1900, - Unauthorized = 1000, - UnauthorizedUser = 1105, - Unavailable = 2002, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1005]: AbortedError, - [1102]: AccessKeyMismatchError, - [1101]: AccessKeyNotFoundError, - [1302]: AtLeastOneKeyError, - [1006]: GeoblockedError, - [2001]: InvalidArgumentError, - [1103]: InvalidOriginError, - [1104]: InvalidServiceError, - [1301]: MaxAccessKeysError, - [3003]: MetadataCallFailedError, - [1003]: MethodNotFoundError, - [1300]: NoDefaultKeyError, - [3000]: NotFoundError, - [1001]: PermissionDeniedError, - [1100]: ProjectNotFoundError, - [2003]: QueryFailedError, - [1200]: QuotaExceededError, - [1201]: RateLimitError, - [1007]: RateLimitedError, - [1004]: RequestConflictError, - [2004]: ResourceExhaustedError, - [1002]: SessionExpiredError, - [1900]: TimeoutError, - [1000]: UnauthorizedError, - [1105]: UnauthorizedUserError, - [2002]: UnavailableError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/indexer/src/indexergw.gen.ts b/packages/services/indexer/src/indexergw.gen.ts deleted file mode 100644 index d22bc5b565..0000000000 --- a/packages/services/indexer/src/indexergw.gen.ts +++ /dev/null @@ -1,1838 +0,0 @@ -/* eslint-disable */ -// sequence-indexer v0.4.0 212120aad9a46e88ead9a2183c5717e9902d8c2b -// -- -// Code generated by Webrpc-gen@v0.31.2 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=merged.gen.json -service=IndexerGateway -target=typescript -client -out=./clients/indexergw.gen.ts - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.4.0' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '212120aad9a46e88ead9a2183c5717e9902d8c2b' - -// -// Client interface -// - -export interface IndexerGatewayClient { - /** - * GetTokenBalances returns a balance summary/details for an specific account - * on all indexer nodes. By default if accountAddress is left empty, it will - * use the account from the jwt session. - */ - getBalanceUpdates( - req: GetBalanceUpdatesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetChains returns a list of chains with their ID and name - */ - getChains(req: GetChainsRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * GetNativeTokenBalance queries indexer nodes for the latest native token - * account balance. - */ - getNativeTokenBalance( - req: GetNativeTokenBalanceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalances returns a balance summary/details for a specific account - * on all indexer nodes. By default if accountAddress is left empty, it will - * use the account from the jwt session. - * - * @deprecated Use GetTokenBalancesSummary or GetTokenBalancesDetails instead. - */ - getTokenBalances( - req: GetTokenBalancesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesByContract returns a balances for specific accounts and - * contracts on all indexer nodes. The collection ERC721 & ERC1155 tokens are - * represented as individual balances. - */ - getTokenBalancesByContract( - req: GetTokenBalancesByContractRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesDetails returns a detailed balance summary for the given - * accounts on all indexer nodes. The collection ERC721 & ERC1155 tokens are - * represented as individual balances. - */ - getTokenBalancesDetails( - req: GetTokenBalancesDetailsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenBalancesSummary returns a summary of token balances for the given - * accounts on all indexer nodes. The collection ERC721 & ERC1155 tokens are - * represented as a single aggregated balance. - */ - getTokenBalancesSummary( - req: GetTokenBalancesSummaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getTokenPrice(req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal): Promise - - getTokenPrices(req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise - - /** - * GetTransactionHistory returns the history of mined transactions for the - * given account on all indexer nodes, which includes a list of token transfer - * (sent/received) , and sent transactions from a Sequence wallet. - */ - getTransactionHistory( - req: GetTransactionHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Ping the indexer - */ - ping(headers?: object, signal?: AbortSignal): Promise - - /** - * Get the current runtime health status of the indexer gatewya - */ - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - - /** - * Get the current version of the indexer - */ - version(headers?: object, signal?: AbortSignal): Promise -} - -// -// Schema types -// - -export interface Asset { - id: number - collectionId: number - tokenId?: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string -} - -export enum BackupMode { - INCREMENTAL = 'INCREMENTAL', - COMPLETE = 'COMPLETE', -} - -export interface BloomStats { - hitRatio: string - falsePositivesPercent: string - hitCount: number - missCount: number - falsePositives: number -} - -export interface BloomStatus { - enabled: boolean - initialized: boolean - bloomInitElapsedTime: string - stats: BloomStats -} - -export interface Bond { - pebble: PebbleMetrics - estimatedDiskUsagePerTable: any - estimatedDiskUsageTotal: string -} - -export interface ChainInfo { - chainId: number - chainName: string -} - -export interface ContractInfo { - chainId: number - address: string - source: string - name: string - type: string - symbol: string - decimals?: number - logoURI: string - deployed: boolean - bytecodeHash: string - extensions: ContractInfoExtensions - updatedAt: string - queuedAt?: string - status: ResourceStatus -} - -export interface ContractInfoExtensionBridgeInfo { - tokenAddress: string -} - -export interface ContractInfoExtensionIndexingInfo { - useOnChainBalance: boolean -} - -export interface ContractInfoExtensions { - link?: string - description?: string - categories?: Array - bridgeInfo?: { [key: string]: ContractInfoExtensionBridgeInfo } - indexingInfo?: ContractInfoExtensionIndexingInfo - ogImage?: string - ogName?: string - originChainId?: number - originAddress?: string - blacklist?: boolean - verified?: boolean - verifiedBy?: string - featured?: boolean - featureIndex?: number -} - -export enum ContractType { - UNKNOWN = 'UNKNOWN', - NATIVE = 'NATIVE', - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', - SEQUENCE_WALLET = 'SEQUENCE_WALLET', - ERC20_BRIDGE = 'ERC20_BRIDGE', - ERC721_BRIDGE = 'ERC721_BRIDGE', - ERC1155_BRIDGE = 'ERC1155_BRIDGE', - SEQ_MARKETPLACE = 'SEQ_MARKETPLACE', - ERC6909 = 'ERC6909', -} - -export enum ContractVerificationStatus { - VERIFIED = 'VERIFIED', - UNVERIFIED = 'UNVERIFIED', - ALL = 'ALL', -} - -export interface DiskUsage { - humanReadable: string - used: number - size: number - percent: number - dirs: { [key: string]: string } -} - -export interface EtherBalance { - accountAddress: string - balanceWei: string -} - -export interface EventDecoded { - topicHash: string - eventSig: string - types: Array - names: Array - values: Array -} - -export interface EventFilter { - events?: Array - contractAddresses?: Array - accounts?: Array - tokenIDs?: Array -} - -export interface EventLog { - id: number - uid: string - type: EventLogType - blockNumber: number - blockHash: string - parentBlockHash: string - contractAddress: string - contractType: ContractType - txnHash: string - txnIndex: number - txnLogIndex: number - logDataType: EventLogDataType - ts: string - txnInfo?: TxnInfo - rawLog?: { [key: string]: any } - event?: EventDecoded -} - -export enum EventLogDataType { - EVENT = 'EVENT', - TOKEN_TRANSFER = 'TOKEN_TRANSFER', - NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', - SEQUENCE_TXN = 'SEQUENCE_TXN', -} - -export enum EventLogType { - UNKNOWN = 'UNKNOWN', - BLOCK_ADDED = 'BLOCK_ADDED', - BLOCK_REMOVED = 'BLOCK_REMOVED', -} - -export interface GatewayBackendResponseTime { - percentiles: { [key: string]: number } - average: number -} - -export interface GatewayBackendRuntimeStatus { - name: string - chainId: number - responseTime: GatewayBackendResponseTime -} - -export interface GatewayEtherBalance { - chainId: number - errorReason?: string - result: EtherBalance -} - -export interface GatewayNativeTokenBalance { - chainId: number - errorReason?: string - result: NativeTokenBalance -} - -export interface GatewayNativeTokenBalances { - chainId: number - errorReason?: string - results: Array -} - -export interface GatewayPrice { - chainID: number - errorReason?: string - results: Array -} - -export interface GatewayRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - backends: Array -} - -export interface GatewayTokenBalance { - chainId: number - errorReason?: string - results: Array -} - -export interface GatewayTokenPriceQuery { - chainID: number - queries: Array -} - -export interface GatewayTransaction { - chainId: number - errorReason?: string - results: Array -} - -export interface IndexState { - chainId: string - lastBlockNum: number - lastBlockHash: string -} - -export interface IndexedBlock { - blockNumber: number - blockShortHash: string -} - -export interface IndexerMetrics { - blocksPerSecond: number - eventsPerSecond: number -} - -export interface MarketplaceOrder { - orderId: string - tokenContract: string - tokenId: string - isListing: boolean - quantity: string - quantityRemaining: string - currencyAddress: string - pricePerToken: string - expiry: string - orderStatus: OrderStatus - createdBy: string - blockNumber: number - orderbookContractAddress: string - createdAt: number -} - -export interface MarketplaceOrderFilter { - isListing?: boolean - userAddresses?: Array - currencyAddresses: Array - orderIds: Array - tokenIds: Array - excludeUserAddresses?: Array - blockNumberGt: number - createdAtAfter: number - orderStatuses: Array - returnExpired: boolean -} - -export interface MarketplaceTopOrdersFilter { - currencyAddresses: Array - tokenIds: Array - isListing: boolean - priceSort: SortOrder - excludeUser?: string -} - -export interface MetadataOptions { - verifiedOnly?: boolean - unverifiedOnly?: boolean - includeContracts?: Array -} - -export interface NativeTokenBalance { - accountAddress: string - chainId: number - name: string - symbol: string - balance: string - balanceUSD: string - priceUSD: string - priceUpdatedAt?: string - errorReason?: string -} - -export enum NetworkType { - MAINNETS = 'MAINNETS', - TESTNETS = 'TESTNETS', - ALL = 'ALL', -} - -export enum OrderStatus { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - CANCELLED = 'CANCELLED', -} - -export interface Page { - page?: number - column?: string - before?: any - after?: any - sort?: Array - pageSize?: number - more?: boolean -} - -export interface PebbleMetrics { - compactionCount: number - compactionEstimatedDebt: number - compactionInProgressBytes: number - compactionNumInProgress: number - compactionMarkedFiles: number -} - -export interface Price { - contractAddress: string - tokenID?: string - priceUSD: string - updatedAt?: string -} - -export enum ResourceStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', -} - -export interface RuntimeChecks { - running: boolean - runnables: any - cgoEnabled: boolean - quotaControlEnabled: boolean - syncMode: string - percentIndexed: number - lastBlockNum: number - lastBlockNumWithState: number - bloomStatus: BloomStatus - bond: Bond - diskUsage: DiskUsage - metrics: IndexerMetrics -} - -export interface RuntimeStatus { - healthOK: boolean - indexerEnabled: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - checks: RuntimeChecks -} - -export interface SortBy { - column: string - order: SortOrder -} - -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', -} - -export interface TokenBalance { - contractType: ContractType - contractAddress: string - accountAddress: string - tokenID?: string - balance: string - balanceUSD: string - priceUSD: string - priceUpdatedAt?: string - blockHash: string - blockNumber: number - chainId: number - uniqueCollectibles: string - isSummary: boolean - contractInfo?: ContractInfo - tokenMetadata?: TokenMetadata -} - -export interface TokenBalanceFilter { - contractAddress: string - sinceBlockNumber: number -} - -export interface TokenBalancesByContractFilter { - contractAddresses: Array - accountAddresses?: Array - contractStatus?: ContractVerificationStatus - tokenIDs?: Array -} - -export interface TokenBalancesFilter { - accountAddresses: Array - contractStatus?: ContractVerificationStatus - contractTypes?: Array - contractWhitelist?: Array - contractBlacklist?: Array - omitNativeBalances: boolean - omitPrices?: boolean - tokenIDs?: Array -} - -export interface TokenHistory { - blockNumber: number - blockHash: string - contractAddress: string - contractType: ContractType - fromAddress: string - toAddress: string - txnHash: string - txnIndex: number - txnLogIndex: number - tokenIDs: string - amounts: string - ts: string -} - -export interface TokenIDRange { - start: string - end: string -} - -export interface TokenMetadata { - chainId?: number - contractAddress?: string - tokenId: string - source: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: ResourceStatus - queuedAt?: string - lastFetched?: string -} - -export interface TokenPriceQuery { - contractAddress: string - tokenID?: string -} - -export interface TokenSupply { - tokenID: string - supply: string - chainId: number - contractInfo?: ContractInfo - tokenMetadata?: TokenMetadata -} - -export interface Transaction { - txnHash: string - blockNumber: number - blockHash: string - chainId: number - metaTxnID?: string - transfers?: Array - timestamp: string -} - -export interface TransactionFilter { - txnHash?: string - from?: string - to?: string - contractAddress?: string - event?: string -} - -export interface TransactionHistoryFilter { - accountAddress?: string - contractAddress?: string - accountAddresses?: Array - contractAddresses?: Array - transactionHashes?: Array - metaTransactionIDs?: Array - fromBlock?: number - toBlock?: number - tokenID?: string - omitPrices?: boolean -} - -export interface TransactionLog { - contractAddress: string - topics: Array - data: string - index: number -} - -export interface TransactionReceipt { - txnHash: string - txnStatus: TransactionStatus - txnIndex: number - txnType: TransactionType - blockHash: string - blockNumber: number - gasUsed: number - effectiveGasPrice: string - from: string - to: string - logs: Array - final: boolean - reorged: boolean -} - -export enum TransactionStatus { - FAILED = 'FAILED', - SUCCESSFUL = 'SUCCESSFUL', -} - -export enum TransactionType { - LegacyTxnType = 'LegacyTxnType', - AccessListTxnType = 'AccessListTxnType', - DynamicFeeTxnType = 'DynamicFeeTxnType', -} - -export interface TxnInfo { - from: string - to: string - value: string -} - -export interface TxnTransfer { - transferType: TxnTransferType - contractAddress: string - contractType: ContractType - from: string - to: string - tokenIds?: Array - amounts: Array - logIndex: number - amountsUSD?: Array - pricesUSD?: Array - contractInfo?: ContractInfo - tokenMetadata?: { [key: string]: TokenMetadata } -} - -export enum TxnTransferType { - UNKNOWN = 'UNKNOWN', - SEND = 'SEND', - RECEIVE = 'RECEIVE', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface WALWriterRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - percentWALWritten: number -} - -export interface WebhookListener { - id: number - projectID: number - url: string - filters: EventFilter - name: string - updatedAt: string - active: boolean -} - -export interface GetBalanceUpdatesRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - contractAddress: string - lastBlockNumber: number - lastBlockHash?: string - page?: Page -} - -export interface GetBalanceUpdatesResponse { - page: Page - balances: Array -} - -export interface GetChainsRequest { - networkType?: NetworkType -} - -export interface GetChainsResponse { - chains: Array -} - -export interface GetNativeTokenBalanceRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - accountAddress?: string - omitPrices?: boolean -} - -export interface GetNativeTokenBalanceResponse { - balances: Array -} - -export interface GetTokenBalancesRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - accountAddress?: string - contractAddress?: string - tokenID?: string - includeMetadata?: boolean - metadataOptions?: MetadataOptions - includeCollectionTokens?: boolean - page?: Page -} - -export interface GetTokenBalancesResponse { - page: Page - balances: Array -} - -export interface GetTokenBalancesByContractRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - filter: TokenBalancesByContractFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesByContractResponse { - page: Page - balances: Array -} - -export interface GetTokenBalancesDetailsRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - filter: TokenBalancesFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesDetailsResponse { - page: Page - nativeBalances: Array - balances: Array -} - -export interface GetTokenBalancesSummaryRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - filter: TokenBalancesFilter - omitMetadata?: boolean - page?: Page -} - -export interface GetTokenBalancesSummaryResponse { - page: Page - nativeBalances: Array - balances: Array -} - -export interface GetTokenPriceRequest { - q: GatewayTokenPriceQuery -} - -export interface GetTokenPriceResponse { - price: GatewayPrice -} - -export interface GetTokenPricesRequest { - q: Array -} - -export interface GetTokenPricesResponse { - prices: Array -} - -export interface GetTransactionHistoryRequest { - chainIds?: Array - networks?: Array - networkType?: NetworkType - filter: TransactionHistoryFilter - includeMetadata?: boolean - metadataOptions?: MetadataOptions - page?: Page -} - -export interface GetTransactionHistoryResponse { - page: Page - transactions: Array -} - -export interface PingRequest {} - -export interface PingResponse { - status: boolean -} - -export interface RuntimeStatusRequest {} - -export interface RuntimeStatusResponse { - status: GatewayRuntimeStatus -} - -export interface VersionRequest {} - -export interface VersionResponse { - version: Version -} - -// -// Client -// - -export class IndexerGateway implements IndexerGatewayClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/IndexerGateway/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - getBalanceUpdates: (req: GetBalanceUpdatesRequest) => ['IndexerGateway', 'getBalanceUpdates', req] as const, - getChains: (req: GetChainsRequest) => ['IndexerGateway', 'getChains', req] as const, - getNativeTokenBalance: (req: GetNativeTokenBalanceRequest) => - ['IndexerGateway', 'getNativeTokenBalance', req] as const, - getTokenBalances: (req: GetTokenBalancesRequest) => ['IndexerGateway', 'getTokenBalances', req] as const, - getTokenBalancesByContract: (req: GetTokenBalancesByContractRequest) => - ['IndexerGateway', 'getTokenBalancesByContract', req] as const, - getTokenBalancesDetails: (req: GetTokenBalancesDetailsRequest) => - ['IndexerGateway', 'getTokenBalancesDetails', req] as const, - getTokenBalancesSummary: (req: GetTokenBalancesSummaryRequest) => - ['IndexerGateway', 'getTokenBalancesSummary', req] as const, - getTokenPrice: (req: GetTokenPriceRequest) => ['IndexerGateway', 'getTokenPrice', req] as const, - getTokenPrices: (req: GetTokenPricesRequest) => ['IndexerGateway', 'getTokenPrices', req] as const, - getTransactionHistory: (req: GetTransactionHistoryRequest) => - ['IndexerGateway', 'getTransactionHistory', req] as const, - ping: () => ['IndexerGateway', 'ping'] as const, - runtimeStatus: () => ['IndexerGateway', 'runtimeStatus'] as const, - version: () => ['IndexerGateway', 'version'] as const, - } - - getBalanceUpdates = ( - req: GetBalanceUpdatesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetBalanceUpdates'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetBalanceUpdatesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getChains = (req: GetChainsRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetChains'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetChainsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getNativeTokenBalance = ( - req: GetNativeTokenBalanceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetNativeTokenBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetNativeTokenBalanceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalances = ( - req: GetTokenBalancesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalances'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesByContract = ( - req: GetTokenBalancesByContractRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesByContract'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesByContractResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesDetails = ( - req: GetTokenBalancesDetailsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesDetails'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesDetailsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenBalancesSummary = ( - req: GetTokenBalancesSummaryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesSummary'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenBalancesSummaryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenPrice = ( - req: GetTokenPriceRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenPrice'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenPriceResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenPrices = ( - req: GetTokenPricesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenPricesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTransactionHistory = ( - req: GetTransactionHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTransactionHistory'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTransactionHistoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PingResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RuntimeStatusResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'VersionResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { - ...headers, - 'Content-Type': 'application/json', - [WebrpcHeader]: WebrpcHeaderValue, - } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -export const JsonEncode = (obj: T): string => { - return JSON.stringify(obj) -} - -export const JsonDecode = (data: string | any, _typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return parsed as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class AbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Aborted' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request aborted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class AccessKeyMismatchError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyMismatch' - this.code = typeof error.code === 'number' ? error.code : 1102 - this.message = error.message || `Access key mismatch` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) - } -} - -export class AccessKeyNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyNotFound' - this.code = typeof error.code === 'number' ? error.code : 1101 - this.message = error.message || `Access key not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) - } -} - -export class AtLeastOneKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AtLeastOneKey' - this.code = typeof error.code === 'number' ? error.code : 1302 - this.message = error.message || `You need at least one Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2001 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class InvalidOriginError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidOrigin' - this.code = typeof error.code === 'number' ? error.code : 1103 - this.message = error.message || `Invalid origin for Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidOriginError.prototype) - } -} - -export class InvalidServiceError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidService' - this.code = typeof error.code === 'number' ? error.code : 1104 - this.message = error.message || `Service not enabled for Access key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidServiceError.prototype) - } -} - -export class MaxAccessKeysError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MaxAccessKeys' - this.code = typeof error.code === 'number' ? error.code : 1301 - this.message = error.message || `Access keys limit reached` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) - } -} - -export class MetadataCallFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MetadataCallFailed' - this.code = typeof error.code === 'number' ? error.code : 3003 - this.message = error.message || `Metadata service call failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MetadataCallFailedError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class NoDefaultKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NoDefaultKey' - this.code = typeof error.code === 'number' ? error.code : 1300 - this.message = error.message || `No default access key found` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 1100 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class QuotaExceededError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaExceeded' - this.code = typeof error.code === 'number' ? error.code : 1200 - this.message = error.message || `Quota exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaExceededError.prototype) - } -} - -export class RateLimitError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimit' - this.code = typeof error.code === 'number' ? error.code : 1201 - this.message = error.message || `Rate limit exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Rate-limited. Please slow down.` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class ResourceExhaustedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ResourceExhausted' - this.code = typeof error.code === 'number' ? error.code : 2004 - this.message = error.message || `Resource exhausted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ResourceExhaustedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Timeout' - this.code = typeof error.code === 'number' ? error.code : 1900 - this.message = error.message || `Request timed out` - this.status = typeof error.status === 'number' ? error.status : 408 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class UnauthorizedUserError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnauthorizedUser' - this.code = typeof error.code === 'number' ? error.code : 1105 - this.message = error.message || `Unauthorized user` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unavailable' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Unavailable resource` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Aborted = 'Aborted', - AccessKeyMismatch = 'AccessKeyMismatch', - AccessKeyNotFound = 'AccessKeyNotFound', - AtLeastOneKey = 'AtLeastOneKey', - Geoblocked = 'Geoblocked', - InvalidArgument = 'InvalidArgument', - InvalidOrigin = 'InvalidOrigin', - InvalidService = 'InvalidService', - MaxAccessKeys = 'MaxAccessKeys', - MetadataCallFailed = 'MetadataCallFailed', - MethodNotFound = 'MethodNotFound', - NoDefaultKey = 'NoDefaultKey', - NotFound = 'NotFound', - PermissionDenied = 'PermissionDenied', - ProjectNotFound = 'ProjectNotFound', - QueryFailed = 'QueryFailed', - QuotaExceeded = 'QuotaExceeded', - RateLimit = 'RateLimit', - RateLimited = 'RateLimited', - RequestConflict = 'RequestConflict', - ResourceExhausted = 'ResourceExhausted', - SessionExpired = 'SessionExpired', - Timeout = 'Timeout', - Unauthorized = 'Unauthorized', - UnauthorizedUser = 'UnauthorizedUser', - Unavailable = 'Unavailable', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Aborted = 1005, - AccessKeyMismatch = 1102, - AccessKeyNotFound = 1101, - AtLeastOneKey = 1302, - Geoblocked = 1006, - InvalidArgument = 2001, - InvalidOrigin = 1103, - InvalidService = 1104, - MaxAccessKeys = 1301, - MetadataCallFailed = 3003, - MethodNotFound = 1003, - NoDefaultKey = 1300, - NotFound = 3000, - PermissionDenied = 1001, - ProjectNotFound = 1100, - QueryFailed = 2003, - QuotaExceeded = 1200, - RateLimit = 1201, - RateLimited = 1007, - RequestConflict = 1004, - ResourceExhausted = 2004, - SessionExpired = 1002, - Timeout = 1900, - Unauthorized = 1000, - UnauthorizedUser = 1105, - Unavailable = 2002, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1005]: AbortedError, - [1102]: AccessKeyMismatchError, - [1101]: AccessKeyNotFoundError, - [1302]: AtLeastOneKeyError, - [1006]: GeoblockedError, - [2001]: InvalidArgumentError, - [1103]: InvalidOriginError, - [1104]: InvalidServiceError, - [1301]: MaxAccessKeysError, - [3003]: MetadataCallFailedError, - [1003]: MethodNotFoundError, - [1300]: NoDefaultKeyError, - [3000]: NotFoundError, - [1001]: PermissionDeniedError, - [1100]: ProjectNotFoundError, - [2003]: QueryFailedError, - [1200]: QuotaExceededError, - [1201]: RateLimitError, - [1007]: RateLimitedError, - [1004]: RequestConflictError, - [2004]: ResourceExhaustedError, - [1002]: SessionExpiredError, - [1900]: TimeoutError, - [1000]: UnauthorizedError, - [1105]: UnauthorizedUserError, - [2002]: UnavailableError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/indexer/tsconfig.json b/packages/services/indexer/tsconfig.json deleted file mode 100644 index 8623be1c02..0000000000 --- a/packages/services/indexer/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"], - // TODO: enable when webrpc codegen handles noUncheckedIndexedAccess - "noUncheckedIndexedAccess": false - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/marketplace/CHANGELOG.md b/packages/services/marketplace/CHANGELOG.md deleted file mode 100644 index 5a97b94e85..0000000000 --- a/packages/services/marketplace/CHANGELOG.md +++ /dev/null @@ -1,407 +0,0 @@ -# @0xsequence/marketplace - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support diff --git a/packages/services/marketplace/README.md b/packages/services/marketplace/README.md deleted file mode 100644 index aa6a9d87bf..0000000000 --- a/packages/services/marketplace/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/marketplace - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/marketplace/eslint.config.js b/packages/services/marketplace/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/marketplace/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/marketplace/package.json b/packages/services/marketplace/package.json deleted file mode 100644 index 865e0e5b44..0000000000 --- a/packages/services/marketplace/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/marketplace", - "version": "3.0.5", - "description": "marketplace sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/marketplace", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/marketplace/src/index.ts b/packages/services/marketplace/src/index.ts deleted file mode 100644 index 3b67f6f2d6..0000000000 --- a/packages/services/marketplace/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export * from './marketplace.gen.js' - -import { Marketplace as MarketplaceRpc } from './marketplace.gen.js' - -export class MarketplaceIndexer extends MarketplaceRpc { - constructor( - hostname: string, - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} diff --git a/packages/services/marketplace/src/marketplace.gen.ts b/packages/services/marketplace/src/marketplace.gen.ts deleted file mode 100644 index 6a316623df..0000000000 --- a/packages/services/marketplace/src/marketplace.gen.ts +++ /dev/null @@ -1,3462 +0,0 @@ -/* eslint-disable */ -// marketplace-api 652676d9951ceb12f6846907c7c4b5160c73c57a -// -- -// Code generated by webrpc-gen@v0.30.2 with github.com/webrpc/gen-typescript@v0.19.0 generator. DO NOT EDIT. -// -// webrpc-gen -schema=./schema/schema.ridl -target=github.com/webrpc/gen-typescript@v0.19.0 -client -out=./clients/marketplace.gen.ts - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = - 'webrpc@v0.30.2;gen-typescript@v0.19.0;marketplace-api@v0.0.0-652676d9951ceb12f6846907c7c4b5160c73c57a' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' - -// Schema version of your RIDL schema -export const WebRPCSchemaVersion = '' - -// Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '652676d9951ceb12f6846907c7c4b5160c73c57a' - -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} - -// -// Types -// - -export enum SortOrder { - ASC = 'ASC', - DESC = 'DESC', -} - -export enum PropertyType { - INT = 'INT', - STRING = 'STRING', - ARRAY = 'ARRAY', - GENERIC = 'GENERIC', -} - -export enum MarketplaceKind { - unknown = 'unknown', - sequence_marketplace_v1 = 'sequence_marketplace_v1', - sequence_marketplace_v2 = 'sequence_marketplace_v2', - blur = 'blur', - zerox = 'zerox', - opensea = 'opensea', - looks_rare = 'looks_rare', - x2y2 = 'x2y2', - alienswap = 'alienswap', - payment_processor = 'payment_processor', - mintify = 'mintify', - magic_eden = 'magic_eden', -} - -export enum OrderbookKind { - unknown = 'unknown', - sequence_marketplace_v1 = 'sequence_marketplace_v1', - sequence_marketplace_v2 = 'sequence_marketplace_v2', - blur = 'blur', - opensea = 'opensea', - looks_rare = 'looks_rare', - reservoir = 'reservoir', - x2y2 = 'x2y2', - magic_eden = 'magic_eden', -} - -export enum SourceKind { - unknown = 'unknown', - external = 'external', - sequence_marketplace_v1 = 'sequence_marketplace_v1', - sequence_marketplace_v2 = 'sequence_marketplace_v2', - opensea = 'opensea', - magic_eden = 'magic_eden', -} - -export enum OrderSide { - unknown = 'unknown', - listing = 'listing', - offer = 'offer', -} - -export enum OfferType { - unknown = 'unknown', - item = 'item', - collection = 'collection', -} - -export enum OrderStatus { - unknown = 'unknown', - active = 'active', - inactive = 'inactive', - expired = 'expired', - cancelled = 'cancelled', - filled = 'filled', - decimals_missing = 'decimals_missing', -} - -export enum ContractType { - UNKNOWN = 'UNKNOWN', - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', -} - -export enum CollectionPriority { - unknown = 'unknown', - low = 'low', - normal = 'normal', - high = 'high', -} - -export enum CollectionStatus { - unknown = 'unknown', - created = 'created', - syncing_orders = 'syncing_orders', - active = 'active', - failed = 'failed', - inactive = 'inactive', - incompatible_type = 'incompatible_type', -} - -export enum ProjectStatus { - unknown = 'unknown', - active = 'active', - inactive = 'inactive', -} - -export enum ItemsContractStatus { - unknown = 'unknown', - created = 'created', - syncing_contract_metadata = 'syncing_contract_metadata', - synced_contract_metadata = 'synced_contract_metadata', - syncing_tokens = 'syncing_tokens', - synced_tokens = 'synced_tokens', - active = 'active', - inactive = 'inactive', - incompatible_type = 'incompatible_type', -} - -export enum CollectibleStatus { - unknown = 'unknown', - active = 'active', - inactive = 'inactive', -} - -export enum CollectibleSource { - unknown = 'unknown', - indexer = 'indexer', - manual = 'manual', -} - -export enum CurrencyStatus { - unknown = 'unknown', - created = 'created', - syncing_metadata = 'syncing_metadata', - active = 'active', - failed = 'failed', -} - -export enum WalletKind { - unknown = 'unknown', - sequence = 'sequence', -} - -export enum StepType { - unknown = 'unknown', - tokenApproval = 'tokenApproval', - buy = 'buy', - sell = 'sell', - createListing = 'createListing', - createOffer = 'createOffer', - signEIP712 = 'signEIP712', - signEIP191 = 'signEIP191', - cancel = 'cancel', -} - -export enum TransactionCrypto { - none = 'none', - partially = 'partially', - all = 'all', -} - -export enum TransactionNFTCheckoutProvider { - unknown = 'unknown', - transak = 'transak', - sardine = 'sardine', -} - -export enum TransactionOnRampProvider { - unknown = 'unknown', - transak = 'transak', - sardine = 'sardine', -} - -export enum TransactionSwapProvider { - unknown = 'unknown', - lifi = 'lifi', -} - -export enum ExecuteType { - unknown = 'unknown', - order = 'order', - createListing = 'createListing', - createItemOffer = 'createItemOffer', - createTraitOffer = 'createTraitOffer', -} - -export enum ActivityAction { - unknown = 'unknown', - listing = 'listing', - offer = 'offer', - mint = 'mint', - sale = 'sale', - listingCancel = 'listingCancel', - offerCancel = 'offerCancel', - transfer = 'transfer', -} - -export enum PrimarySaleContractStatus { - unknown = 'unknown', - created = 'created', - syncing_items = 'syncing_items', - active = 'active', - inactive = 'inactive', - incompatible_type = 'incompatible_type', - failed = 'failed', -} - -export enum PrimarySaleVersion { - v0 = 'v0', - v1 = 'v1', -} - -export enum PrimarySaleItemDetailType { - unknown = 'unknown', - global = 'global', - individual = 'individual', -} - -export enum MetadataStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', -} - -export interface Page { - page: number - pageSize: number - more?: boolean - sort?: Array -} - -export interface SortBy { - column: string - order: SortOrder -} - -export interface Filter { - text?: string - properties?: Array -} - -export interface PropertyFilter { - name: string - type: PropertyType - min?: number - max?: number - values?: Array -} - -export interface CollectiblesFilter { - includeEmpty: boolean - searchText?: string - properties?: Array - marketplaces?: Array - inAccounts?: Array - notInAccounts?: Array - ordersCreatedBy?: Array - ordersNotCreatedBy?: Array - inCurrencyAddresses?: Array - notInCurrencyAddresses?: Array - prices?: Array -} - -export interface OrdersFilter { - searchText?: string - properties?: Array - marketplaces?: Array - inAccounts?: Array - notInAccounts?: Array - ordersCreatedBy?: Array - ordersNotCreatedBy?: Array - inCurrencyAddresses?: Array - notInCurrencyAddresses?: Array - prices?: Array -} - -export interface PriceFilter { - contractAddress: string - min?: string - max?: string -} - -export interface Order { - orderId: string - marketplace: MarketplaceKind - side: OrderSide - status: OrderStatus - chainId: number - originName: string - slug: string - collectionContractAddress: string - tokenId?: string - createdBy: string - priceAmount: string - priceAmountFormatted: string - priceAmountNet: string - priceAmountNetFormatted: string - priceCurrencyAddress: string - priceDecimals: number - priceUSD: number - priceUSDFormatted: string - quantityInitial: string - quantityInitialFormatted: string - quantityRemaining: string - quantityRemainingFormatted: string - quantityAvailable: string - quantityAvailableFormatted: string - quantityDecimals: number - feeBps: number - feeBreakdown: Array - validFrom: string - validUntil: string - blockNumber: number - orderCreatedAt?: string - orderUpdatedAt?: string - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface FeeBreakdown { - kind: string - recipientAddress: string - bps: number -} - -export interface CollectibleOrder { - metadata: TokenMetadata - order?: Order - listing?: Order - offer?: Order -} - -export interface OrderFilter { - createdBy?: Array - marketplace?: Array - currencies?: Array -} - -export interface Collection { - status: CollectionStatus - chainId: number - contractAddress: string - contractType: ContractType - priority: CollectionPriority - tokenQuantityDecimals: number - config: CollectionConfig - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface CollectionConfig { - lastSynced: { [key: string]: CollectionLastSynced } - collectiblesSynced: string - activitiesSynced: string - activitiesSyncedContinuity: string -} - -export interface CollectionLastSynced { - allOrders: string - newOrders: string - names: Array - cursors: { [key: string]: string } -} - -export interface Project { - projectId: number - chainId: number - contractAddress: string - status: ProjectStatus - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface ItemsContract { - status: ItemsContractStatus - chainId: number - contractAddress: string - contractType: ContractType - lastSynced: string - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface Collectible { - status: CollectibleStatus - tokenId: string - decimals: number - source: CollectibleSource - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface Currency { - chainId: number - contractAddress: string - status: CurrencyStatus - name: string - symbol: string - decimals: number - imageUrl: string - exchangeRate: number - defaultChainCurrency: boolean - nativeCurrency: boolean - openseaListing: boolean - openseaOffer: boolean - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface OrderData { - orderId: string - quantity: string - tokenId?: string -} - -export interface AdditionalFee { - amount: string - receiver: string -} - -export interface Step { - id: StepType - data: string - to: string - value: string - price: string - signature?: Signature - post?: PostRequest - executeType?: ExecuteType -} - -export interface PostRequest { - endpoint: string - method: string - body: any -} - -export interface CreateReq { - tokenId: string - quantity: string - expiry: string - currencyAddress: string - pricePerToken: string -} - -export interface GetOrdersInput { - contractAddress: string - orderId: string - marketplace: MarketplaceKind -} - -export interface Signature { - domain: Domain - types: any - primaryType: string - value: any -} - -export interface Domain { - name: string - version: string - chainId: number - verifyingContract: string -} - -export interface CheckoutOptionsMarketplaceOrder { - contractAddress: string - orderId: string - marketplace: MarketplaceKind -} - -export interface CheckoutOptionsItem { - tokenId: string - quantity: string -} - -export interface CheckoutOptions { - crypto: TransactionCrypto - swap: Array - nftCheckout: Array - onRamp: Array -} - -export interface ExecuteInput { - chainId: string - signature: string - method: string - endpoint: string - executeType: ExecuteType - body: any - slug?: string -} - -export interface Activity { - chainId: number - contractAddress: string - tokenId: string - action: ActivityAction - txHash: string - from: string - to?: string - quantity: string - quantityDecimals: number - priceAmount?: string - priceAmountFormatted?: string - priceCurrencyAddress?: string - priceDecimals?: number - activityCreatedAt: string - uniqueHash: string - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface PrimarySaleContract { - chainId: number - contractAddress: string - collectionAddress: string - contractType: ContractType - version: PrimarySaleVersion - currencyAddress: string - priceDecimals: number - status: PrimarySaleContractStatus - lastSynced: string - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface PrimarySaleItem { - itemAddress: string - contractType: ContractType - tokenId: string - itemType: PrimarySaleItemDetailType - startDate: string - endDate: string - currencyAddress: string - priceDecimals: number - priceAmount: string - priceAmountFormatted: string - priceUsd: number - priceUsdFormatted: string - supply: string - supplyCap: string - unlimitedSupply: boolean - createdAt: string - updatedAt: string - deletedAt?: string -} - -export interface CollectiblePrimarySaleItem { - metadata: TokenMetadata - primarySaleItem: PrimarySaleItem -} - -export interface PrimarySaleItemsFilter { - includeEmpty: boolean - searchText?: string - properties?: Array - detailTypes?: Array - startDateAfter?: string - startDateBefore?: string - endDateAfter?: string - endDateBefore?: string -} - -export interface TokenMetadata { - tokenId: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: MetadataStatus -} - -export interface Asset { - id: number - collectionId: number - tokenId: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string -} - -export interface Admin { - createCollection(args: CreateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - getCollection(args: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise - updateCollection(args: UpdateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - listCollections(args: ListCollectionsArgs, headers?: object, signal?: AbortSignal): Promise - deleteCollection(args: DeleteCollectionArgs, headers?: object, signal?: AbortSignal): Promise - /** - * determine what should happen here - */ - syncCollection(args: SyncCollectionArgs, headers?: object, signal?: AbortSignal): Promise - createPrimarySaleContract( - args: CreatePrimarySaleContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - deletePrimarySaleContract( - args: DeletePrimarySaleContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - createCurrency(args: CreateCurrencyArgs, headers?: object, signal?: AbortSignal): Promise - createCurrencies(args: CreateCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise - updateCurrency(args: UpdateCurrencyArgs, headers?: object, signal?: AbortSignal): Promise - listCurrencies(args: ListCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise - deleteCurrency(args: DeleteCurrencyArgs, headers?: object, signal?: AbortSignal): Promise - /** - * This for manual adding of non minted ERC1155 tokens, it's used for purposes of Shop. - */ - addCollectibles(args: AddCollectiblesArgs, headers?: object, signal?: AbortSignal): Promise -} - -export interface CreateCollectionArgs { - chainId: string - projectId: number - contractAddress: string -} - -export interface CreateCollectionReturn { - collection: Collection -} -export interface GetCollectionArgs { - chainId: string - projectId: number - contractAddress: string -} - -export interface GetCollectionReturn { - collection: Collection -} -export interface UpdateCollectionArgs { - chainId: string - collection: Collection -} - -export interface UpdateCollectionReturn { - collection: Collection -} -export interface ListCollectionsArgs { - chainId: string - projectId: number - page?: Page -} - -export interface ListCollectionsReturn { - collections: Array - page?: Page -} -export interface DeleteCollectionArgs { - chainId: string - projectId: number - contractAddress: string -} - -export interface DeleteCollectionReturn { - collection: Collection -} -export interface SyncCollectionArgs { - chainId: string - contractAddress: string -} - -export interface SyncCollectionReturn {} -export interface CreatePrimarySaleContractArgs { - chainId: string - projectId: number - primarySaleContractAddress: string - itemsContractAddress: string -} - -export interface CreatePrimarySaleContractReturn { - primarySaleContract: PrimarySaleContract -} -export interface DeletePrimarySaleContractArgs { - chainId: string - projectId: number - primarySaleContractAddress: string -} - -export interface DeletePrimarySaleContractReturn {} -export interface CreateCurrencyArgs { - chainId: string - currency: Currency -} - -export interface CreateCurrencyReturn { - currency: Currency -} -export interface CreateCurrenciesArgs { - chainId: string - currencies: Array -} - -export interface CreateCurrenciesReturn { - currency: { [key: string]: Currency } -} -export interface UpdateCurrencyArgs { - chainId: string - currency: Currency -} - -export interface UpdateCurrencyReturn { - currency: Currency -} -export interface ListCurrenciesArgs { - chainId: string -} - -export interface ListCurrenciesReturn { - currencies: Array -} -export interface DeleteCurrencyArgs { - chainId: string - contractAddress: string -} - -export interface DeleteCurrencyReturn { - currency: Currency -} -export interface AddCollectiblesArgs { - chainId: string - itemsContractAddress: string - tokenIds: Array -} - -export interface AddCollectiblesReturn {} - -export interface Marketplace { - listCurrencies(args: ListCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise - getCollectionDetail( - args: GetCollectionDetailArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCollectionActiveListingsCurrencies( - args: GetCollectionActiveListingsCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCollectionActiveOffersCurrencies( - args: GetCollectionActiveOffersCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCollectible(args: GetCollectibleArgs, headers?: object, signal?: AbortSignal): Promise - getLowestPriceOfferForCollectible( - args: GetLowestPriceOfferForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getHighestPriceOfferForCollectible( - args: GetHighestPriceOfferForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getLowestPriceListingForCollectible( - args: GetLowestPriceListingForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getHighestPriceListingForCollectible( - args: GetHighestPriceListingForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listListingsForCollectible( - args: ListListingsForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listOffersForCollectible( - args: ListOffersForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listOrdersWithCollectibles( - args: ListOrdersWithCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCountOfAllOrders( - args: GetCountOfAllOrdersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCountOfFilteredOrders( - args: GetCountOfFilteredOrdersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listListings(args: ListListingsArgs, headers?: object, signal?: AbortSignal): Promise - listOffers(args: ListOffersArgs, headers?: object, signal?: AbortSignal): Promise - getCountOfListingsForCollectible( - args: GetCountOfListingsForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCountOfOffersForCollectible( - args: GetCountOfOffersForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use GetLowestPriceOfferForCollectible instead. - */ - getCollectibleLowestOffer( - args: GetCollectibleLowestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use GetHighestPriceOfferForCollectible instead. - */ - getCollectibleHighestOffer( - args: GetCollectibleHighestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use GetLowestPriceListingForCollectible instead. - */ - getCollectibleLowestListing( - args: GetCollectibleLowestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use GetHighestPriceListingForCollectible instead. - */ - getCollectibleHighestListing( - args: GetCollectibleHighestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use ListListingsForCollectible instead. - */ - listCollectibleListings( - args: ListCollectibleListingsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * @deprecated Please use ListOffersForCollectible instead. - */ - listCollectibleOffers( - args: ListCollectibleOffersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * checkout process - */ - generateBuyTransaction( - args: GenerateBuyTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - generateSellTransaction( - args: GenerateSellTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - generateListingTransaction( - args: GenerateListingTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - generateOfferTransaction( - args: GenerateOfferTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - generateCancelTransaction( - args: GenerateCancelTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * only used in a case of external transactions ( when we create off-chain transactions ) for instance opensea market, use only ExecuteInput params and leave other root inputs empty, they are depracated and kept only for backward compatibility - */ - execute(args: ExecuteArgs, headers?: object, signal?: AbortSignal): Promise - /** - * list of collectibles with best order for each collectible, by default this only returns collectibles with an order - */ - listCollectibles(args: ListCollectiblesArgs, headers?: object, signal?: AbortSignal): Promise - getCountOfAllCollectibles( - args: GetCountOfAllCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCountOfFilteredCollectibles( - args: GetCountOfFilteredCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getFloorOrder(args: GetFloorOrderArgs, headers?: object, signal?: AbortSignal): Promise - listCollectionActivities( - args: ListCollectionActivitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listCollectibleActivities( - args: ListCollectibleActivitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listCollectiblesWithLowestListing( - args: ListCollectiblesWithLowestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listCollectiblesWithHighestOffer( - args: ListCollectiblesWithHighestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - syncOrder(args: SyncOrderArgs, headers?: object, signal?: AbortSignal): Promise - syncOrders(args: SyncOrdersArgs, headers?: object, signal?: AbortSignal): Promise - getOrders(args: GetOrdersArgs, headers?: object, signal?: AbortSignal): Promise - checkoutOptionsMarketplace( - args: CheckoutOptionsMarketplaceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - checkoutOptionsSalesContract( - args: CheckoutOptionsSalesContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - supportedMarketplaces( - args: SupportedMarketplacesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getPrimarySaleItem( - args: GetPrimarySaleItemArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listPrimarySaleItems( - args: ListPrimarySaleItemsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getCountOfPrimarySaleItems( - args: GetCountOfPrimarySaleItemsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -export interface ListCurrenciesArgs { - chainId: string -} - -export interface ListCurrenciesReturn { - currencies: Array -} -export interface GetCollectionDetailArgs { - chainId: string - contractAddress: string -} - -export interface GetCollectionDetailReturn { - collection: Collection -} -export interface GetCollectionActiveListingsCurrenciesArgs { - chainId: string - contractAddress: string -} - -export interface GetCollectionActiveListingsCurrenciesReturn { - currencies: Array -} -export interface GetCollectionActiveOffersCurrenciesArgs { - chainId: string - contractAddress: string -} - -export interface GetCollectionActiveOffersCurrenciesReturn { - currencies: Array -} -export interface GetCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string -} - -export interface GetCollectibleReturn { - metadata: TokenMetadata -} -export interface GetLowestPriceOfferForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetLowestPriceOfferForCollectibleReturn { - order: Order -} -export interface GetHighestPriceOfferForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetHighestPriceOfferForCollectibleReturn { - order: Order -} -export interface GetLowestPriceListingForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetLowestPriceListingForCollectibleReturn { - order: Order -} -export interface GetHighestPriceListingForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetHighestPriceListingForCollectibleReturn { - order: Order -} -export interface ListListingsForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter - page?: Page -} - -export interface ListListingsForCollectibleReturn { - listings: Array - page?: Page -} -export interface ListOffersForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter - page?: Page -} - -export interface ListOffersForCollectibleReturn { - offers: Array - page?: Page -} -export interface ListOrdersWithCollectiblesArgs { - chainId: string - side: OrderSide - contractAddress: string - filter?: OrdersFilter - page?: Page -} - -export interface ListOrdersWithCollectiblesReturn { - collectibles: Array - page?: Page -} -export interface GetCountOfAllOrdersArgs { - chainId: string - side: OrderSide - contractAddress: string -} - -export interface GetCountOfAllOrdersReturn { - count: number -} -export interface GetCountOfFilteredOrdersArgs { - chainId: string - side: OrderSide - contractAddress: string - filter?: OrdersFilter -} - -export interface GetCountOfFilteredOrdersReturn { - count: number -} -export interface ListListingsArgs { - chainId: string - contractAddress: string - filter?: OrderFilter - page?: Page -} - -export interface ListListingsReturn { - listings: Array - page?: Page -} -export interface ListOffersArgs { - chainId: string - contractAddress: string - filter?: OrderFilter - page?: Page -} - -export interface ListOffersReturn { - offers: Array - page?: Page -} -export interface GetCountOfListingsForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCountOfListingsForCollectibleReturn { - count: number -} -export interface GetCountOfOffersForCollectibleArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCountOfOffersForCollectibleReturn { - count: number -} -export interface GetCollectibleLowestOfferArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCollectibleLowestOfferReturn { - order?: Order -} -export interface GetCollectibleHighestOfferArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCollectibleHighestOfferReturn { - order?: Order -} -export interface GetCollectibleLowestListingArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCollectibleLowestListingReturn { - order?: Order -} -export interface GetCollectibleHighestListingArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter -} - -export interface GetCollectibleHighestListingReturn { - order?: Order -} -export interface ListCollectibleListingsArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter - page?: Page -} - -export interface ListCollectibleListingsReturn { - listings: Array - page?: Page -} -export interface ListCollectibleOffersArgs { - chainId: string - contractAddress: string - tokenId: string - filter?: OrderFilter - page?: Page -} - -export interface ListCollectibleOffersReturn { - offers: Array - page?: Page -} -export interface GenerateBuyTransactionArgs { - chainId: string - collectionAddress: string - buyer: string - marketplace: MarketplaceKind - ordersData: Array - additionalFees: Array - walletType?: WalletKind -} - -export interface GenerateBuyTransactionReturn { - steps: Array -} -export interface GenerateSellTransactionArgs { - chainId: string - collectionAddress: string - seller: string - marketplace: MarketplaceKind - ordersData: Array - additionalFees: Array - walletType?: WalletKind -} - -export interface GenerateSellTransactionReturn { - steps: Array -} -export interface GenerateListingTransactionArgs { - chainId: string - collectionAddress: string - owner: string - contractType: ContractType - orderbook: OrderbookKind - listing: CreateReq - additionalFees: Array - walletType?: WalletKind -} - -export interface GenerateListingTransactionReturn { - steps: Array -} -export interface GenerateOfferTransactionArgs { - chainId: string - collectionAddress: string - maker: string - contractType: ContractType - orderbook: OrderbookKind - offer: CreateReq - additionalFees: Array - walletType?: WalletKind - offerType: OfferType -} - -export interface GenerateOfferTransactionReturn { - steps: Array -} -export interface GenerateCancelTransactionArgs { - chainId: string - collectionAddress: string - maker: string - marketplace: MarketplaceKind - orderId: string -} - -export interface GenerateCancelTransactionReturn { - steps: Array -} -export interface ExecuteArgs { - params: ExecuteInput - chainId?: string - signature?: string - method?: string - endpoint?: string - executeType?: ExecuteType - body?: any -} - -export interface ExecuteReturn { - orderId: string -} -export interface ListCollectiblesArgs { - chainId: string - side: OrderSide - contractAddress: string - filter?: CollectiblesFilter - page?: Page -} - -export interface ListCollectiblesReturn { - collectibles: Array - page?: Page -} -export interface GetCountOfAllCollectiblesArgs { - chainId: string - contractAddress: string -} - -export interface GetCountOfAllCollectiblesReturn { - count: number -} -export interface GetCountOfFilteredCollectiblesArgs { - chainId: string - side: OrderSide - contractAddress: string - filter?: CollectiblesFilter -} - -export interface GetCountOfFilteredCollectiblesReturn { - count: number -} -export interface GetFloorOrderArgs { - chainId: string - contractAddress: string - filter?: CollectiblesFilter -} - -export interface GetFloorOrderReturn { - collectible: CollectibleOrder -} -export interface ListCollectionActivitiesArgs { - chainId: string - contractAddress: string - page?: Page -} - -export interface ListCollectionActivitiesReturn { - activities: Array - page?: Page -} -export interface ListCollectibleActivitiesArgs { - chainId: string - contractAddress: string - tokenId: string - page?: Page -} - -export interface ListCollectibleActivitiesReturn { - activities: Array - page?: Page -} -export interface ListCollectiblesWithLowestListingArgs { - chainId: string - contractAddress: string - filter?: CollectiblesFilter - page?: Page -} - -export interface ListCollectiblesWithLowestListingReturn { - collectibles: Array - page?: Page -} -export interface ListCollectiblesWithHighestOfferArgs { - chainId: string - contractAddress: string - filter?: CollectiblesFilter - page?: Page -} - -export interface ListCollectiblesWithHighestOfferReturn { - collectibles: Array - page?: Page -} -export interface SyncOrderArgs { - chainId: string - order: Order -} - -export interface SyncOrderReturn {} -export interface SyncOrdersArgs { - chainId: string - orders: Array -} - -export interface SyncOrdersReturn {} -export interface GetOrdersArgs { - chainId: string - input: Array - page?: Page -} - -export interface GetOrdersReturn { - orders: Array - page?: Page -} -export interface CheckoutOptionsMarketplaceArgs { - chainId: string - wallet: string - orders: Array - additionalFee: number -} - -export interface CheckoutOptionsMarketplaceReturn { - options: CheckoutOptions -} -export interface CheckoutOptionsSalesContractArgs { - chainId: string - wallet: string - contractAddress: string - collectionAddress: string - items: Array -} - -export interface CheckoutOptionsSalesContractReturn { - options: CheckoutOptions -} -export interface SupportedMarketplacesArgs { - chainId: string -} - -export interface SupportedMarketplacesReturn { - marketplaces: Array -} -export interface GetPrimarySaleItemArgs { - chainId: string - primarySaleContractAddress: string - tokenId: string -} - -export interface GetPrimarySaleItemReturn { - item: CollectiblePrimarySaleItem -} -export interface ListPrimarySaleItemsArgs { - chainId: string - primarySaleContractAddress: string - filter?: PrimarySaleItemsFilter - page?: Page -} - -export interface ListPrimarySaleItemsReturn { - primarySaleItems: Array - page?: Page -} -export interface GetCountOfPrimarySaleItemsArgs { - chainId: string - primarySaleContractAddress: string - filter?: PrimarySaleItemsFilter -} - -export interface GetCountOfPrimarySaleItemsReturn { - count: number -} - -// -// Client -// -export class Admin implements Admin { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Admin/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - createCollection = ( - args: CreateCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CreateCollection'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollection = (args: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetCollection'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateCollection = ( - args: UpdateCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateCollection'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollections = ( - args: ListCollectionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollections'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collections: >_data.collections, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteCollection = ( - args: DeleteCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('DeleteCollection'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - syncCollection = ( - args: SyncCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SyncCollection'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createPrimarySaleContract = ( - args: CreatePrimarySaleContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CreatePrimarySaleContract'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - primarySaleContract: _data.primarySaleContract, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deletePrimarySaleContract = ( - args: DeletePrimarySaleContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('DeletePrimarySaleContract'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createCurrency = ( - args: CreateCurrencyArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CreateCurrency'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currency: _data.currency, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createCurrencies = ( - args: CreateCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CreateCurrencies'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currency: <{ [key: string]: Currency }>_data.currency, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateCurrency = ( - args: UpdateCurrencyArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateCurrency'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currency: _data.currency, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCurrencies = ( - args: ListCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCurrencies'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteCurrency = ( - args: DeleteCurrencyArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('DeleteCurrency'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currency: _data.currency, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addCollectibles = ( - args: AddCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddCollectibles'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} -export class Marketplace implements Marketplace { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Marketplace/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - listCurrencies = ( - args: ListCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCurrencies'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectionDetail = ( - args: GetCollectionDetailArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectionDetail'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectionActiveListingsCurrencies = ( - args: GetCollectionActiveListingsCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectionActiveListingsCurrencies'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectionActiveOffersCurrencies = ( - args: GetCollectionActiveOffersCurrenciesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectionActiveOffersCurrencies'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectible = ( - args: GetCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - metadata: _data.metadata, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLowestPriceOfferForCollectible = ( - args: GetLowestPriceOfferForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetLowestPriceOfferForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getHighestPriceOfferForCollectible = ( - args: GetHighestPriceOfferForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetHighestPriceOfferForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getLowestPriceListingForCollectible = ( - args: GetLowestPriceListingForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetLowestPriceListingForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getHighestPriceListingForCollectible = ( - args: GetHighestPriceListingForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetHighestPriceListingForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listListingsForCollectible = ( - args: ListListingsForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListListingsForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - listings: >_data.listings, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listOffersForCollectible = ( - args: ListOffersForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListOffersForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - offers: >_data.offers, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listOrdersWithCollectibles = ( - args: ListOrdersWithCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListOrdersWithCollectibles'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collectibles: >_data.collectibles, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfAllOrders = ( - args: GetCountOfAllOrdersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfAllOrders'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfFilteredOrders = ( - args: GetCountOfFilteredOrdersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfFilteredOrders'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listListings = (args: ListListingsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListListings'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - listings: >_data.listings, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listOffers = (args: ListOffersArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListOffers'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - offers: >_data.offers, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfListingsForCollectible = ( - args: GetCountOfListingsForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfListingsForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfOffersForCollectible = ( - args: GetCountOfOffersForCollectibleArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfOffersForCollectible'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectibleLowestOffer = ( - args: GetCollectibleLowestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectibleLowestOffer'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectibleHighestOffer = ( - args: GetCollectibleHighestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectibleHighestOffer'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectibleLowestListing = ( - args: GetCollectibleLowestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectibleLowestListing'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollectibleHighestListing = ( - args: GetCollectibleHighestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectibleHighestListing'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - order: _data.order, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectibleListings = ( - args: ListCollectibleListingsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectibleListings'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - listings: >_data.listings, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectibleOffers = ( - args: ListCollectibleOffersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectibleOffers'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - offers: >_data.offers, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateBuyTransaction = ( - args: GenerateBuyTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateBuyTransaction'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - steps: >_data.steps, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateSellTransaction = ( - args: GenerateSellTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateSellTransaction'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - steps: >_data.steps, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateListingTransaction = ( - args: GenerateListingTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateListingTransaction'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - steps: >_data.steps, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateOfferTransaction = ( - args: GenerateOfferTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateOfferTransaction'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - steps: >_data.steps, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - generateCancelTransaction = ( - args: GenerateCancelTransactionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateCancelTransaction'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - steps: >_data.steps, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - execute = (args: ExecuteArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Execute'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - orderId: _data.orderId, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectibles = ( - args: ListCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectibles'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collectibles: >_data.collectibles, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfAllCollectibles = ( - args: GetCountOfAllCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfAllCollectibles'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfFilteredCollectibles = ( - args: GetCountOfFilteredCollectiblesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfFilteredCollectibles'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getFloorOrder = (args: GetFloorOrderArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetFloorOrder'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collectible: _data.collectible, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectionActivities = ( - args: ListCollectionActivitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectionActivities'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - activities: >_data.activities, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectibleActivities = ( - args: ListCollectibleActivitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectibleActivities'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - activities: >_data.activities, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectiblesWithLowestListing = ( - args: ListCollectiblesWithLowestListingArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectiblesWithLowestListing'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collectibles: >_data.collectibles, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollectiblesWithHighestOffer = ( - args: ListCollectiblesWithHighestOfferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListCollectiblesWithHighestOffer'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - collectibles: >_data.collectibles, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - syncOrder = (args: SyncOrderArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SyncOrder'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - syncOrders = (args: SyncOrdersArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SyncOrders'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getOrders = (args: GetOrdersArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetOrders'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - orders: >_data.orders, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - checkoutOptionsMarketplace = ( - args: CheckoutOptionsMarketplaceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CheckoutOptionsMarketplace'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - options: _data.options, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - checkoutOptionsSalesContract = ( - args: CheckoutOptionsSalesContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CheckoutOptionsSalesContract'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - options: _data.options, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - supportedMarketplaces = ( - args: SupportedMarketplacesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SupportedMarketplaces'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - marketplaces: >_data.marketplaces, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getPrimarySaleItem = ( - args: GetPrimarySaleItemArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetPrimarySaleItem'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - item: _data.item, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listPrimarySaleItems = ( - args: ListPrimarySaleItemsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListPrimarySaleItems'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - primarySaleItems: >_data.primarySaleItems, - page: _data.page, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCountOfPrimarySaleItems = ( - args: GetCountOfPrimarySaleItemsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCountOfPrimarySaleItems'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - count: _data.count, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, - } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -// -// Errors -// - -export class WebrpcError extends Error { - name: string - code: number - message: string - status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) - } -} - -// Webrpc errors - -export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 404, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 405, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 500, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 500, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 500, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientAborted', - code: number = -8, - message: string = `request aborted by client`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 200, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// Schema errors - -export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = `Unauthorized access`, - status: number = 401, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = `Permission denied`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = `Session expired`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = `Method not found`, - status: number = 404, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = `Conflict with target resource`, - status: number = 409, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = `Request aborted`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = `Geoblocked region`, - status: number = 451, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = `Rate-limited. Please slow down.`, - status: number = 429, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 1008, - message: string = `Project not found`, - status: number = 401, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class SecretKeyCorsDisallowedError extends WebrpcError { - constructor( - name: string = 'SecretKeyCorsDisallowed', - code: number = 1009, - message: string = `CORS disallowed. Admin API Secret Key can't be used from a web app.`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SecretKeyCorsDisallowedError.prototype) - } -} - -export class AccessKeyNotFoundError extends WebrpcError { - constructor( - name: string = 'AccessKeyNotFound', - code: number = 1101, - message: string = `Access key not found`, - status: number = 401, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) - } -} - -export class AccessKeyMismatchError extends WebrpcError { - constructor( - name: string = 'AccessKeyMismatch', - code: number = 1102, - message: string = `Access key mismatch`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) - } -} - -export class InvalidOriginError extends WebrpcError { - constructor( - name: string = 'InvalidOrigin', - code: number = 1103, - message: string = `Invalid origin for Access Key`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidOriginError.prototype) - } -} - -export class InvalidServiceError extends WebrpcError { - constructor( - name: string = 'InvalidService', - code: number = 1104, - message: string = `Service not enabled for Access key`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidServiceError.prototype) - } -} - -export class UnauthorizedUserError extends WebrpcError { - constructor( - name: string = 'UnauthorizedUser', - code: number = 1105, - message: string = `Unauthorized user`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) - } -} - -export class InvalidChainError extends WebrpcError { - constructor( - name: string = 'InvalidChain', - code: number = 1106, - message: string = `Network not enabled for Access key`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidChainError.prototype) - } -} - -export class QuotaExceededError extends WebrpcError { - constructor( - name: string = 'QuotaExceeded', - code: number = 1200, - message: string = `Quota request exceeded`, - status: number = 429, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QuotaExceededError.prototype) - } -} - -export class QuotaRateLimitError extends WebrpcError { - constructor( - name: string = 'QuotaRateLimit', - code: number = 1201, - message: string = `Quota rate limit exceeded`, - status: number = 429, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QuotaRateLimitError.prototype) - } -} - -export class NoDefaultKeyError extends WebrpcError { - constructor( - name: string = 'NoDefaultKey', - code: number = 1300, - message: string = `No default access key found`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) - } -} - -export class MaxAccessKeysError extends WebrpcError { - constructor( - name: string = 'MaxAccessKeys', - code: number = 1301, - message: string = `Access keys limit reached`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) - } -} - -export class AtLeastOneKeyError extends WebrpcError { - constructor( - name: string = 'AtLeastOneKey', - code: number = 1302, - message: string = `You need at least one Access Key`, - status: number = 403, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 1900, - message: string = `Request timed out`, - status: number = 408, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 2000, - message: string = `Resource not found`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = `Invalid argument`, - status: number = 400, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class NotImplementedError extends WebrpcError { - constructor( - name: string = 'NotImplemented', - code: number = 9999, - message: string = `Not Implemented`, - status: number = 500, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotImplementedError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - SecretKeyCorsDisallowed = 'SecretKeyCorsDisallowed', - AccessKeyNotFound = 'AccessKeyNotFound', - AccessKeyMismatch = 'AccessKeyMismatch', - InvalidOrigin = 'InvalidOrigin', - InvalidService = 'InvalidService', - UnauthorizedUser = 'UnauthorizedUser', - InvalidChain = 'InvalidChain', - QuotaExceeded = 'QuotaExceeded', - QuotaRateLimit = 'QuotaRateLimit', - NoDefaultKey = 'NoDefaultKey', - MaxAccessKeys = 'MaxAccessKeys', - AtLeastOneKey = 'AtLeastOneKey', - Timeout = 'Timeout', - NotFound = 'NotFound', - InvalidArgument = 'InvalidArgument', - NotImplemented = 'NotImplemented', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Aborted = 1005, - Geoblocked = 1006, - RateLimited = 1007, - ProjectNotFound = 1008, - SecretKeyCorsDisallowed = 1009, - AccessKeyNotFound = 1101, - AccessKeyMismatch = 1102, - InvalidOrigin = 1103, - InvalidService = 1104, - UnauthorizedUser = 1105, - InvalidChain = 1106, - QuotaExceeded = 1200, - QuotaRateLimit = 1201, - NoDefaultKey = 1300, - MaxAccessKeys = 1301, - AtLeastOneKey = 1302, - Timeout = 1900, - NotFound = 2000, - InvalidArgument = 2001, - NotImplemented = 9999, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1008]: ProjectNotFoundError, - [1009]: SecretKeyCorsDisallowedError, - [1101]: AccessKeyNotFoundError, - [1102]: AccessKeyMismatchError, - [1103]: InvalidOriginError, - [1104]: InvalidServiceError, - [1105]: UnauthorizedUserError, - [1106]: InvalidChainError, - [1200]: QuotaExceededError, - [1201]: QuotaRateLimitError, - [1300]: NoDefaultKeyError, - [1301]: MaxAccessKeysError, - [1302]: AtLeastOneKeyError, - [1900]: TimeoutError, - [2000]: NotFoundError, - [2001]: InvalidArgumentError, - [9999]: NotImplementedError, -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/services/marketplace/tsconfig.json b/packages/services/marketplace/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/marketplace/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/metadata/CHANGELOG.md b/packages/services/metadata/CHANGELOG.md deleted file mode 100644 index 3362b424e0..0000000000 --- a/packages/services/metadata/CHANGELOG.md +++ /dev/null @@ -1,1956 +0,0 @@ -# @0xsequence/metadata - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet - -## 2.0.12 - -### Patch Changes - -- api: update bindings - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints - -## 2.0.0 - -### Major Changes - -- ethers v6 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api - -## 1.10.9 - -### Patch Changes - -- waas minor update - -## 1.10.8 - -### Patch Changes - -- update metadata bindings - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia - -## 1.10.3 - -### Patch Changes - -- typing fix - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants - -## 1.9.36 - -### Patch Changes - -- guard: export client - -## 1.9.35 - -### Patch Changes - -- guard: update bindings - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email - -## 1.9.33 - -### Patch Changes - -- waas: umd build - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes - -## 1.9.30 - -### Patch Changes - -- update - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore - -## 1.9.23 - -### Patch Changes - -- update api client bindings - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings - -## 1.9.21 - -### Patch Changes - -- api client bindings - -## 1.9.20 - -### Patch Changes - -- api client bindings update - -## 1.9.19 - -### Patch Changes - -- waas update - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client - -## 1.9.8 - -### Patch Changes - -- waas client update - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer - -## 1.9.6 - -### Patch Changes - -- waas package update - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia - -## 1.9.1 - -### Patch Changes - -- analytics fix - -## 1.9.0 - -### Minor Changes - -- waas release - -## 1.8.8 - -### Patch Changes - -- update metadata bindings - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested - -## 1.8.1 - -### Patch Changes - -- update to analytics provider - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks - -## 1.6.3 - -### Patch Changes - -- network list update - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods - -## 1.4.2 - -### Patch Changes - -- guard: update bindings - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic - -## 1.4.0 - -### Minor Changes - -- project access key support - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions - -## 1.1.11 - -### Patch Changes - -- add homeverse configs - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object - -## 0.43.28 - -### Patch Changes - -- update api bindings - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM - -## 0.43.22 - -### Patch Changes - -- add zkevm chain - -## 0.43.21 - -### Patch Changes - -- api: update client bindings - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings - -## 0.43.19 - -### Patch Changes - -- session proof update - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods - -## 0.43.14 - -### Patch Changes - -- bump - -## 0.43.13 - -### Patch Changes - -- update rpc bindings - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter - -## 0.43.10 - -### Patch Changes - -- various improvements - -## 0.43.9 - -### Patch Changes - -- update deps - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings - -## 0.42.6 - -### Patch Changes - -- api bindings update - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options - -## 0.42.3 - -### Patch Changes - -- update api bindings - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' - -## 0.41.3 - -### Patch Changes - -- api bindings update - -## 0.41.2 - -### Patch Changes - -- api bindings update - -## 0.41.1 - -### Patch Changes - -- update default networks - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain - -## 0.40.5 - -### Patch Changes - -- api: update bindings - -## 0.40.4 - -### Patch Changes - -- add unreal transport - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option - -## 0.39.4 - -### Patch Changes - -- api: update client bindings - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider - -## 0.39.2 - -### Patch Changes - -- update umd name - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) - -## 0.36.7 - -### Patch Changes - -- fix missing break - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation - -## 0.35.10 - -### Patch Changes - -- upgrade deps - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -## 0.31.3 - -### Patch Changes - -- update metadata bindings - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -## 0.29.8 - -### Patch Changes - -- update api - -## 0.29.1 - -### Patch Changes - -- metadata: ContractInfo.decimals is now optional, i.e. may be undefined - - api: new APIs for user storage and isUsingGoogleMail - -## 0.29.0 - -### Minor Changes - -- major architectural changes in Sequence design - - only one API instance, API is no longer a per-chain service - - separate per-chain indexer service, API no longer handles indexing - - single contract metadata service, API no longer serves metadata - - chaind package has been removed, indexer and metadata packages have been added - - stronger typing with new explicit ChainId type - - multicall fixes and improvements - - forbid "wait" transactions in sendTransactionBatch calls diff --git a/packages/services/metadata/README.md b/packages/services/metadata/README.md deleted file mode 100644 index ab2b0b9ea3..0000000000 --- a/packages/services/metadata/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/metadata - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/metadata/eslint.config.js b/packages/services/metadata/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/metadata/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/metadata/package.json b/packages/services/metadata/package.json deleted file mode 100644 index acb19b042c..0000000000 --- a/packages/services/metadata/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/metadata", - "version": "3.0.5", - "publishConfig": { - "access": "public" - }, - "description": "metadata sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/metadata", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/metadata/src/index.ts b/packages/services/metadata/src/index.ts deleted file mode 100644 index f9a1a600cc..0000000000 --- a/packages/services/metadata/src/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -export * from './metadata.gen.js' - -import { Metadata as MetadataRpc, Collections as CollectionsRpc } from './metadata.gen.js' - -export class SequenceMetadata extends MetadataRpc { - constructor( - hostname: string = 'https://metadata.sequence.app', - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} - -export class SequenceCollections extends CollectionsRpc { - constructor( - hostname: string = 'https://metadata.sequence.app', - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt auth header to requests - // if its been set on the client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } - - // TODO: add uploadAsset() method similar to, - // https://github.com/0xsequence/go-sequence/blob/master/metadata/collections.go#L52 -} diff --git a/packages/services/metadata/src/metadata.gen.ts b/packages/services/metadata/src/metadata.gen.ts deleted file mode 100644 index 9390aee762..0000000000 --- a/packages/services/metadata/src/metadata.gen.ts +++ /dev/null @@ -1,3132 +0,0 @@ -/* eslint-disable */ -// sequence-metadata v0.4.0 673a5fa528008c7f9558810fbb24aad978ed7a84 -// -- -// Code generated by Webrpc-gen@v0.31.0 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=metadata.ridl -target=typescript -client -ignore=@deprecated -compat -out=./clients/metadata.gen.ts - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.4.0' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '673a5fa528008c7f9558810fbb24aad978ed7a84' - -// -// Client interface -// - -export interface MetadataClient { - ping(headers?: object, signal?: AbortSignal): Promise - - version(headers?: object, signal?: AbortSignal): Promise - - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - - getTask(req: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise - - getTaskStatus(req: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * Contract Info -- returns contract meta-info for contracts found in registered chain's token-lists - */ - getContractInfo(req: GetContractInfoArgs, headers?: object, signal?: AbortSignal): Promise - - getContractInfoBatch( - req: GetContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Find Contract Info across all chains token-lists. Similar to GetContractInfo above, - * but it will traverse all chains and results from all. - */ - findContractInfo(req: FindContractInfoArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * map of contractAddress :: []ContractInfo - */ - findContractInfoBatch( - req: FindContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Refresh Contract Info -- refresh contract meta-info - */ - refreshContractInfo( - req: RefreshContractInfoArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - refreshContractInfoBatch( - req: RefreshContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Search for contract infos using a query string - */ - searchContractsByQuery( - req: SearchContractsByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * GetTokenMetadata - fetch token metadata for a particular contract and respective tokenIDs - */ - getTokenMetadata(req: GetTokenMetadataArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * GetTokenMetadataBatch allows you to query the token metadata of a batch of contracts and respective tokenIDs - * where map is contractAddress::[]tokenID => contractAddress::[]TokenMetadata - * - * Note, we limit each request to 50 contracts max and 50 tokens max per contract. - */ - getTokenMetadataBatch( - req: GetTokenMetadataBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * RefreshTokenMetadata allows you to refresh a contract metadata for contract-level and token-level metadata. - */ - refreshTokenMetadata( - req: RefreshTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Search ERC721 & ERC1155 token metadata by query string 'q' - */ - searchTokenMetadataByQuery( - req: SearchTokenMetadataByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Search ERC721 & ERC1155 token metadata by filter object 'filter' - * which allows to search by text or properties. - */ - searchTokenMetadata( - req: SearchTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Search ERC721 & ERC1155 for token IDs by filter object 'filter' - * which allows to search by text or properties. - */ - searchTokenMetadataTokenIDs( - req: SearchTokenMetadataTokenIDsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Get token metadata property filters for a contract address - */ - getTokenMetadataPropertyFilters( - req: GetTokenMetadataPropertyFiltersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Gets Token Directory supported networks - */ - getTokenDirectoryNetworks( - req: GetTokenDirectoryNetworksArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Gets Token Directory entries - */ - getTokenDirectory( - req: GetTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Search in Token Directory - */ - searchTokenDirectory( - req: SearchTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Niftyswap querying data - */ - getNiftyswapTokenQuantity( - req: GetNiftyswapTokenQuantityArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * map of tokenID :: quantity - */ - getNiftyswapUnitPrices( - req: GetNiftyswapUnitPricesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * map of tokenID :: price - */ - getNiftyswapUnitPricesWithQuantities( - req: GetNiftyswapUnitPricesWithQuantitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} -export interface CollectionsClient { - createCollection(req: CreateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - - getCollection(req: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise - - listCollections(req: ListCollectionsArgs, headers?: object, signal?: AbortSignal): Promise - - updateCollection(req: UpdateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - - deleteCollection(req: DeleteCollectionArgs, headers?: object, signal?: AbortSignal): Promise - - publishCollection( - req: PublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - unpublishCollection( - req: UnpublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - createContractCollection( - req: CreateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - getContractCollection( - req: GetContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - listContractCollections( - req: ListContractCollectionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - updateContractCollection( - req: UpdateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteContractCollection( - req: DeleteContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - createToken(req: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise - - getToken(req: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise - - listTokens(req: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise - - updateToken(req: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise - - deleteToken(req: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise - - createAsset(req: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise - - getAsset(req: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise - - updateAsset(req: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise - - deleteAsset(req: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise -} -export interface AdminClient { - /** - * ContractInfo - */ - refreshContractInfoUpdatedBefore( - req: RefreshContractInfoUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * TokenMetadata - */ - refreshTokenMetadataUpdatedBefore( - req: RefreshTokenMetadataUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Contract Info Overrides - */ - getContractInfoOverride( - req: GetContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - getContractInfoOverrides( - req: GetContractInfoOverridesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - addContractInfoOverride( - req: AddContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - updateContractInfoOverride( - req: UpdateContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeContractInfoOverride( - req: RemoveContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Token Directory - */ - isInTokenDirectory( - req: IsInTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - setTokenDirectoryFeatureIndex( - req: SetTokenDirectoryFeatureIndexArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - addContractToTokenDirectory( - req: AddContractToTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeContractFromTokenDirectory( - req: RemoveContractFromTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - refreshTokenDirectory(headers?: object, signal?: AbortSignal): Promise -} - -// -// Schema types -// - -export enum ContractType { - UNKNOWN = 'UNKNOWN', - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', - ERC6909 = 'ERC6909', - MISC = 'MISC', -} - -export enum Source { - UNKNOWN = 'UNKNOWN', - FETCHER = 'FETCHER', - FETCHER_OPENSEA_API = 'FETCHER_OPENSEA_API', - FETCHER_ENS_API = 'FETCHER_ENS_API', - FETCHER_ON_CHAIN_ERC20_INTERFACE = 'FETCHER_ON_CHAIN_ERC20_INTERFACE', - FETCHER_ON_CHAIN_TOKEN_URI = 'FETCHER_ON_CHAIN_TOKEN_URI', - FETCHER_ON_CHAIN_CONTRACT_URI = 'FETCHER_ON_CHAIN_CONTRACT_URI', - FETCHER_TOKEN_DIRECTORY_ADMIN = 'FETCHER_TOKEN_DIRECTORY_ADMIN', - TOKEN_DIRECTORY = 'TOKEN_DIRECTORY', - TOKEN_DIRECTORY_PUBLIC_TOKEN_LIST = 'TOKEN_DIRECTORY_PUBLIC_TOKEN_LIST', - TOKEN_DIRECTORY_3RD_PARTY = 'TOKEN_DIRECTORY_3RD_PARTY', - TOKEN_DIRECTORY_SEQUENCE_GITHUB = 'TOKEN_DIRECTORY_SEQUENCE_GITHUB', - TOKEN_DIRECTORY_SEQUENCE_BUILDER = 'TOKEN_DIRECTORY_SEQUENCE_BUILDER', - SEQUENCE_BUILDER = 'SEQUENCE_BUILDER', - SEQUENCE_BUILDER_DEPLOYED = 'SEQUENCE_BUILDER_DEPLOYED', - SEQUENCE_BUILDER_COLLECTIONS = 'SEQUENCE_BUILDER_COLLECTIONS', - SEQUENCE_BUILDER_ADMIN = 'SEQUENCE_BUILDER_ADMIN', -} - -export enum ResourceStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', -} - -export enum PropertyType { - INT = 'INT', - STRING = 'STRING', - ARRAY = 'ARRAY', - GENERIC = 'GENERIC', -} - -export enum SwapType { - UNKNOWN = 'UNKNOWN', - BUY = 'BUY', - SELL = 'SELL', -} - -export enum TaskStatus { - QUEUED = 'QUEUED', - PAUSED = 'PAUSED', - FAILED = 'FAILED', - DONE = 'DONE', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - uptimeString: string - ver: string - branch: string - commitHash: string - runnable: { [key: string]: RunnableStatus } -} - -export interface RunnableStatus { - running: boolean - restarts: number - startTime: string - endTime?: string - lastError: any -} - -export interface ContractIndex { - chainId: number - address: string - type: ContractType - source: Source - metadata: { [key: string]: any } - contentHash: number - deployed: boolean - bytecodeHash: string - notFound: boolean - updatedAt: string - queuedAt?: string - status: ResourceStatus -} - -export interface TokenIndex { - chainId: number - contractAddress: string - tokenId: string - source: Source - metadata: { [key: string]: any } - notFound?: boolean - lastFetched?: string - fetchCount?: number - updatedAt: string - queuedAt?: string -} - -export interface ContractInfo { - chainId: number - address: string - source: string - name: string - type: string - symbol: string - decimals?: number - logoURI: string - deployed: boolean - bytecodeHash: string - extensions: ContractInfoExtensions - updatedAt: string - queuedAt?: string - status: ResourceStatus -} - -export interface ContractInfoExtensions { - link?: string - description?: string - categories?: Array - bridgeInfo?: { [key: string]: ContractInfoExtensionBridgeInfo } - ogImage?: string - ogName?: string - originChainId?: number - originAddress?: string - blacklist?: boolean - verified?: boolean - verifiedBy?: string - featured?: boolean - featureIndex?: number -} - -export interface ContractInfoExtensionBridgeInfo { - tokenAddress: string -} - -export interface ContractInfoOverride { - name?: string - type?: string - symbol?: string - decimals?: number - logoURI?: string - extensions: ContractInfoExtensionsOverride -} - -export interface ContractInfoExtensionsOverride { - link?: string - description?: string - categories?: Array - ogImage?: string - ogName?: string - originChainId?: number - originAddress?: string - blacklist?: boolean - verified?: boolean - verifiedBy?: string - featureIndex?: number -} - -export interface TokenMetadata { - chainId?: number - contractAddress?: string - tokenId: string - source: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: ResourceStatus - queuedAt?: string - lastFetched?: string -} - -export interface PropertyFilter { - name: string - type: PropertyType - min?: number - max?: number - values?: Array -} - -export interface Filter { - text?: string - properties?: Array -} - -export interface Collection { - id: number - projectId: number - metadata: CollectionMetadata - private: boolean - revealKey?: string - tokenCount?: number - createdAt?: string - updatedAt?: string - deletedAt?: string - baseURIs?: CollectionBaseURIs - assets?: Array -} - -export interface CollectionMetadata { - name: string - description?: string - image?: string - external_link?: string - properties?: { [key: string]: any } - attributes?: Array<{ [key: string]: any }> -} - -export interface CollectionBaseURIs { - contractMetadataURI: string - tokenMetadataURI: string -} - -export interface ContractCollection { - id: number - chainId: number - contractAddress: string - collectionId: number -} - -export interface Asset { - id: number - collectionId: number - tokenId?: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string -} - -export interface Token { - collectionId: number - tokenId: string - metadata: TokenMetadata - private: boolean - updatedAt?: string -} - -export interface GetNiftyswapUnitPricesRequest { - swapType: SwapType - ids: Array - amounts: Array -} - -export interface GetNiftyswapUnitPricesResponse { - unitPrice: string - unitAmount: string - availableAmount: string -} - -export interface Page { - page?: number - column?: string - before?: any - after?: any - pageSize?: number - more?: boolean -} - -export interface Task { - id: number - queue: string - status: TaskStatus - try: number - runAt?: string - lastRanAt?: string - createdAt?: string - payload: Array - result: Array -} - -export interface PingArgs {} - -export interface PingReturn { - status: boolean -} - -export interface VersionArgs {} - -export interface VersionReturn { - version: Version -} - -export interface RuntimeStatusArgs {} - -export interface RuntimeStatusReturn { - status: RuntimeStatus -} - -export interface GetTaskArgs { - taskId: number -} - -export interface GetTaskReturn { - task: Task -} - -export interface GetTaskStatusArgs { - taskId: number -} - -export interface GetTaskStatusReturn { - status?: TaskStatus -} - -export interface GetContractInfoArgs { - chainID: string - contractAddress: string -} - -export interface GetContractInfoReturn { - contractInfo: ContractInfo - taskID?: number -} - -export interface GetContractInfoBatchArgs { - chainID: string - contractAddresses: Array -} - -export interface GetContractInfoBatchReturn { - contractInfoMap: { [key: string]: ContractInfo } - taskID?: number -} - -export interface FindContractInfoArgs { - contractAddress: string -} - -export interface FindContractInfoReturn { - contractInfoList: Array -} - -export interface FindContractInfoBatchArgs { - contractAddresses: Array -} - -export interface FindContractInfoBatchReturn { - contractInfoByChain: { [key: string]: Array } -} - -export interface RefreshContractInfoArgs { - chainID: string - contractAddress: string -} - -export interface RefreshContractInfoReturn { - taskID?: number -} - -export interface RefreshContractInfoBatchArgs { - chainID: string - contractAddresses: Array -} - -export interface RefreshContractInfoBatchReturn { - taskID?: number -} - -export interface SearchContractsByQueryArgs { - q: string - chainID?: string - chainIDs?: Array - types?: Array - page?: Page -} - -export interface SearchContractsByQueryReturn { - contractInfo: Array - nextPage: Page -} - -export interface GetTokenMetadataArgs { - chainID: string - contractAddress: string - tokenIDs: Array -} - -export interface GetTokenMetadataReturn { - tokenMetadata: Array - taskID?: number -} - -export interface GetTokenMetadataBatchArgs { - chainID: string - contractTokenMap: { [key: string]: Array } -} - -export interface GetTokenMetadataBatchReturn { - contractTokenMetadata: { [key: string]: Array } - taskID?: number -} - -export interface RefreshTokenMetadataArgs { - chainID: string - contractAddress: string - tokenIDs?: Array - newMints?: boolean -} - -export interface RefreshTokenMetadataReturn { - taskID: number -} - -export interface SearchTokenMetadataByQueryArgs { - q: string - chainID?: string - contractAddress?: string - page?: Page -} - -export interface SearchTokenMetadataByQueryReturn { - tokenMetadata: Array - nextPage: Page -} - -export interface SearchTokenMetadataArgs { - chainID: string - contractAddress: string - filter: Filter - page?: Page -} - -export interface SearchTokenMetadataReturn { - page: Page - tokenMetadata: Array -} - -export interface SearchTokenMetadataTokenIDsArgs { - chainID: string - contractAddress: string - filter: Filter - page?: Page -} - -export interface SearchTokenMetadataTokenIDsReturn { - page: Page - tokenIDs: Array -} - -export interface GetTokenMetadataPropertyFiltersArgs { - chainID: string - contractAddress: string - excludeProperties: Array - excludePropertyValues?: boolean -} - -export interface GetTokenMetadataPropertyFiltersReturn { - filters: Array -} - -export interface GetTokenDirectoryNetworksArgs { - includeTestnets?: boolean - onlyFeatured?: boolean -} - -export interface GetTokenDirectoryNetworksReturn { - chainIDs: Array - networks: Array -} - -export interface GetTokenDirectoryArgs { - chainID?: string - includeTestnets?: boolean - onlyFeatured?: boolean - page?: Page -} - -export interface GetTokenDirectoryReturn { - contracts: Array - page: Page -} - -export interface SearchTokenDirectoryArgs { - query: string - chainID?: number - includeTestnets?: boolean - onlyFeatured?: boolean - page?: Page -} - -export interface SearchTokenDirectoryReturn { - contracts: Array - page: Page -} - -export interface GetNiftyswapTokenQuantityArgs { - chainID: string - contractAddress: string - tokenIDs: Array -} - -export interface GetNiftyswapTokenQuantityReturn { - quantity: { [key: string]: string } -} - -export interface GetNiftyswapUnitPricesArgs { - chainID: string - contractAddress: string - req: GetNiftyswapUnitPricesRequest - fresh: boolean -} - -export interface GetNiftyswapUnitPricesReturn { - prices: { [key: string]: string } -} - -export interface GetNiftyswapUnitPricesWithQuantitiesArgs { - chainID: string - contractAddress: string - req: GetNiftyswapUnitPricesRequest - fresh: boolean -} - -export interface GetNiftyswapUnitPricesWithQuantitiesReturn { - prices: { [key: string]: GetNiftyswapUnitPricesResponse } -} - -export interface CreateCollectionArgs { - projectId?: number - collection: Collection -} - -export interface CreateCollectionReturn { - collection: Collection -} - -export interface GetCollectionArgs { - projectId?: number - collectionId: number -} - -export interface GetCollectionReturn { - collection: Collection -} - -export interface ListCollectionsArgs { - projectId?: number - page?: Page -} - -export interface ListCollectionsReturn { - page: Page - collections: Array -} - -export interface UpdateCollectionArgs { - projectId?: number - collection: Collection -} - -export interface UpdateCollectionReturn { - collection: Collection -} - -export interface DeleteCollectionArgs { - projectId?: number - collectionId: number -} - -export interface DeleteCollectionReturn { - status: boolean -} - -export interface PublishCollectionArgs { - projectId?: number - collectionId: number - recursive?: boolean -} - -export interface PublishCollectionReturn { - collection: Collection -} - -export interface UnpublishCollectionArgs { - projectId?: number - collectionId: number -} - -export interface UnpublishCollectionReturn { - collection: Collection -} - -export interface CreateContractCollectionArgs { - projectId: number - contractCollection: ContractCollection -} - -export interface CreateContractCollectionReturn { - contractCollection: ContractCollection -} - -export interface GetContractCollectionArgs { - projectId: number - chainId: number - contractAddress: string -} - -export interface GetContractCollectionReturn { - contractCollection: ContractCollection -} - -export interface ListContractCollectionsArgs { - projectId: number - collectionId?: number - page?: Page -} - -export interface ListContractCollectionsReturn { - contractCollections: Array - collections: Array - page: Page -} - -export interface UpdateContractCollectionArgs { - projectId: number - contractCollection: ContractCollection -} - -export interface UpdateContractCollectionReturn { - ok: boolean -} - -export interface DeleteContractCollectionArgs { - projectId: number - chainId: number - contractAddress: string -} - -export interface DeleteContractCollectionReturn { - ok: boolean -} - -export interface CreateTokenArgs { - projectId?: number - collectionId: number - token: TokenMetadata - private?: boolean -} - -export interface CreateTokenReturn { - token: TokenMetadata - assets: Array -} - -export interface GetTokenArgs { - projectId?: number - collectionId: number - tokenId: string -} - -export interface GetTokenReturn { - token: TokenMetadata - assets: Array -} - -export interface ListTokensArgs { - projectId?: number - collectionId: number - page?: Page -} - -export interface ListTokensReturn { - page: Page - tokens: Array -} - -export interface UpdateTokenArgs { - projectId?: number - collectionId: number - tokenId: string - token: TokenMetadata - private?: boolean -} - -export interface UpdateTokenReturn { - token: TokenMetadata -} - -export interface DeleteTokenArgs { - projectId?: number - collectionId: number - tokenId: string -} - -export interface DeleteTokenReturn { - status: boolean -} - -export interface CreateAssetArgs { - projectId?: number - asset: Asset -} - -export interface CreateAssetReturn { - asset: Asset -} - -export interface GetAssetArgs { - projectId?: number - assetId: number -} - -export interface GetAssetReturn { - asset: Asset -} - -export interface UpdateAssetArgs { - projectId?: number - asset: Asset -} - -export interface UpdateAssetReturn { - asset: Asset -} - -export interface DeleteAssetArgs { - projectId?: number - assetId: number -} - -export interface DeleteAssetReturn { - status: boolean -} - -export interface RefreshContractInfoUpdatedBeforeArgs { - before: string - maxContractNumber: number -} - -export interface RefreshContractInfoUpdatedBeforeReturn { - taskIDs: Array -} - -export interface RefreshTokenMetadataUpdatedBeforeArgs { - before: string - maxTokenNumber: number -} - -export interface RefreshTokenMetadataUpdatedBeforeReturn { - taskIDs: Array -} - -export interface GetContractInfoOverrideArgs { - chainID: string - contractAddress: string -} - -export interface GetContractInfoOverrideReturn { - contractInfoOverride: ContractInfoOverride -} - -export interface GetContractInfoOverridesArgs { - chainID?: string - page?: Page -} - -export interface GetContractInfoOverridesReturn { - contractInfoOverrides: Array - page: Page -} - -export interface AddContractInfoOverrideArgs { - chainID: string - contractAddress: string - contractInfoOverride: ContractInfoOverride -} - -export interface AddContractInfoOverrideReturn { - ok: boolean -} - -export interface UpdateContractInfoOverrideArgs { - chainID: string - contractAddress: string - contractInfoOverride: ContractInfoOverride -} - -export interface UpdateContractInfoOverrideReturn { - ok: boolean -} - -export interface RemoveContractInfoOverrideArgs { - chainID: string - contractAddress: string -} - -export interface RemoveContractInfoOverrideReturn { - ok: boolean -} - -export interface IsInTokenDirectoryArgs { - chainID: string - contractAddress: string -} - -export interface IsInTokenDirectoryReturn { - ok: boolean - featureIndex: number -} - -export interface SetTokenDirectoryFeatureIndexArgs { - chainID: string - contractAddress: string - featureIndex: number -} - -export interface SetTokenDirectoryFeatureIndexReturn { - ok: boolean -} - -export interface AddContractToTokenDirectoryArgs { - chainID: string - contractAddress: string -} - -export interface AddContractToTokenDirectoryReturn { - ok: boolean -} - -export interface RemoveContractFromTokenDirectoryArgs { - chainID: string - contractAddress: string -} - -export interface RemoveContractFromTokenDirectoryReturn { - ok: boolean -} - -export interface RefreshTokenDirectoryArgs {} - -export interface RefreshTokenDirectoryReturn { - taskID: number -} - -// -// Client -// - -export class Metadata implements MetadataClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Metadata/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - ping: () => ['Metadata', 'ping'] as const, - version: () => ['Metadata', 'version'] as const, - runtimeStatus: () => ['Metadata', 'runtimeStatus'] as const, - getTask: (req: GetTaskArgs) => ['Metadata', 'getTask', req] as const, - getTaskStatus: (req: GetTaskStatusArgs) => ['Metadata', 'getTaskStatus', req] as const, - getContractInfo: (req: GetContractInfoArgs) => ['Metadata', 'getContractInfo', req] as const, - getContractInfoBatch: (req: GetContractInfoBatchArgs) => ['Metadata', 'getContractInfoBatch', req] as const, - findContractInfo: (req: FindContractInfoArgs) => ['Metadata', 'findContractInfo', req] as const, - findContractInfoBatch: (req: FindContractInfoBatchArgs) => ['Metadata', 'findContractInfoBatch', req] as const, - refreshContractInfo: (req: RefreshContractInfoArgs) => ['Metadata', 'refreshContractInfo', req] as const, - refreshContractInfoBatch: (req: RefreshContractInfoBatchArgs) => - ['Metadata', 'refreshContractInfoBatch', req] as const, - searchContractsByQuery: (req: SearchContractsByQueryArgs) => ['Metadata', 'searchContractsByQuery', req] as const, - getTokenMetadata: (req: GetTokenMetadataArgs) => ['Metadata', 'getTokenMetadata', req] as const, - getTokenMetadataBatch: (req: GetTokenMetadataBatchArgs) => ['Metadata', 'getTokenMetadataBatch', req] as const, - refreshTokenMetadata: (req: RefreshTokenMetadataArgs) => ['Metadata', 'refreshTokenMetadata', req] as const, - searchTokenMetadataByQuery: (req: SearchTokenMetadataByQueryArgs) => - ['Metadata', 'searchTokenMetadataByQuery', req] as const, - searchTokenMetadata: (req: SearchTokenMetadataArgs) => ['Metadata', 'searchTokenMetadata', req] as const, - searchTokenMetadataTokenIDs: (req: SearchTokenMetadataTokenIDsArgs) => - ['Metadata', 'searchTokenMetadataTokenIDs', req] as const, - getTokenMetadataPropertyFilters: (req: GetTokenMetadataPropertyFiltersArgs) => - ['Metadata', 'getTokenMetadataPropertyFilters', req] as const, - getTokenDirectoryNetworks: (req: GetTokenDirectoryNetworksArgs) => - ['Metadata', 'getTokenDirectoryNetworks', req] as const, - getTokenDirectory: (req: GetTokenDirectoryArgs) => ['Metadata', 'getTokenDirectory', req] as const, - searchTokenDirectory: (req: SearchTokenDirectoryArgs) => ['Metadata', 'searchTokenDirectory', req] as const, - getNiftyswapTokenQuantity: (req: GetNiftyswapTokenQuantityArgs) => - ['Metadata', 'getNiftyswapTokenQuantity', req] as const, - getNiftyswapUnitPrices: (req: GetNiftyswapUnitPricesArgs) => ['Metadata', 'getNiftyswapUnitPrices', req] as const, - getNiftyswapUnitPricesWithQuantities: (req: GetNiftyswapUnitPricesWithQuantitiesArgs) => - ['Metadata', 'getNiftyswapUnitPricesWithQuantities', req] as const, - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PingReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'VersionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RuntimeStatusReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTask = (req: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTask'), createHttpRequest(JsonEncode(req, 'GetTaskArgs'), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTaskReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTaskStatus = (req: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetTaskStatus'), - createHttpRequest(JsonEncode(req, 'GetTaskStatusArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTaskStatusReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getContractInfo = ( - req: GetContractInfoArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetContractInfo'), - createHttpRequest(JsonEncode(req, 'GetContractInfoArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetContractInfoReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getContractInfoBatch = ( - req: GetContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetContractInfoBatch'), - createHttpRequest(JsonEncode(req, 'GetContractInfoBatchArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetContractInfoBatchReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - findContractInfo = ( - req: FindContractInfoArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('FindContractInfo'), - createHttpRequest(JsonEncode(req, 'FindContractInfoArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FindContractInfoReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - findContractInfoBatch = ( - req: FindContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('FindContractInfoBatch'), - createHttpRequest(JsonEncode(req, 'FindContractInfoBatchArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FindContractInfoBatchReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - refreshContractInfo = ( - req: RefreshContractInfoArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RefreshContractInfo'), - createHttpRequest(JsonEncode(req, 'RefreshContractInfoArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshContractInfoReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - refreshContractInfoBatch = ( - req: RefreshContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RefreshContractInfoBatch'), - createHttpRequest(JsonEncode(req, 'RefreshContractInfoBatchArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshContractInfoBatchReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchContractsByQuery = ( - req: SearchContractsByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchContractsByQuery'), - createHttpRequest(JsonEncode(req, 'SearchContractsByQueryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchContractsByQueryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenMetadata = ( - req: GetTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTokenMetadata'), - createHttpRequest(JsonEncode(req, 'GetTokenMetadataArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenMetadataReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenMetadataBatch = ( - req: GetTokenMetadataBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTokenMetadataBatch'), - createHttpRequest(JsonEncode(req, 'GetTokenMetadataBatchArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenMetadataBatchReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - refreshTokenMetadata = ( - req: RefreshTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RefreshTokenMetadata'), - createHttpRequest(JsonEncode(req, 'RefreshTokenMetadataArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshTokenMetadataReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchTokenMetadataByQuery = ( - req: SearchTokenMetadataByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchTokenMetadataByQuery'), - createHttpRequest(JsonEncode(req, 'SearchTokenMetadataByQueryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchTokenMetadataByQueryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchTokenMetadata = ( - req: SearchTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchTokenMetadata'), - createHttpRequest(JsonEncode(req, 'SearchTokenMetadataArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchTokenMetadataReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchTokenMetadataTokenIDs = ( - req: SearchTokenMetadataTokenIDsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchTokenMetadataTokenIDs'), - createHttpRequest(JsonEncode(req, 'SearchTokenMetadataTokenIDsArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchTokenMetadataTokenIDsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenMetadataPropertyFilters = ( - req: GetTokenMetadataPropertyFiltersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTokenMetadataPropertyFilters'), - createHttpRequest(JsonEncode(req, 'GetTokenMetadataPropertyFiltersArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenMetadataPropertyFiltersReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenDirectoryNetworks = ( - req: GetTokenDirectoryNetworksArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTokenDirectoryNetworks'), - createHttpRequest(JsonEncode(req, 'GetTokenDirectoryNetworksArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenDirectoryNetworksReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTokenDirectory = ( - req: GetTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetTokenDirectory'), - createHttpRequest(JsonEncode(req, 'GetTokenDirectoryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - searchTokenDirectory = ( - req: SearchTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SearchTokenDirectory'), - createHttpRequest(JsonEncode(req, 'SearchTokenDirectoryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SearchTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getNiftyswapTokenQuantity = ( - req: GetNiftyswapTokenQuantityArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetNiftyswapTokenQuantity'), - createHttpRequest(JsonEncode(req, 'GetNiftyswapTokenQuantityArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetNiftyswapTokenQuantityReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getNiftyswapUnitPrices = ( - req: GetNiftyswapUnitPricesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetNiftyswapUnitPrices'), - createHttpRequest(JsonEncode(req, 'GetNiftyswapUnitPricesArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetNiftyswapUnitPricesReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getNiftyswapUnitPricesWithQuantities = ( - req: GetNiftyswapUnitPricesWithQuantitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetNiftyswapUnitPricesWithQuantities'), - createHttpRequest(JsonEncode(req, 'GetNiftyswapUnitPricesWithQuantitiesArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode( - _data, - 'GetNiftyswapUnitPricesWithQuantitiesReturn', - ) - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} -export class Collections implements CollectionsClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Collections/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - createCollection: (req: CreateCollectionArgs) => ['Collections', 'createCollection', req] as const, - getCollection: (req: GetCollectionArgs) => ['Collections', 'getCollection', req] as const, - listCollections: (req: ListCollectionsArgs) => ['Collections', 'listCollections', req] as const, - updateCollection: (req: UpdateCollectionArgs) => ['Collections', 'updateCollection', req] as const, - deleteCollection: (req: DeleteCollectionArgs) => ['Collections', 'deleteCollection', req] as const, - publishCollection: (req: PublishCollectionArgs) => ['Collections', 'publishCollection', req] as const, - unpublishCollection: (req: UnpublishCollectionArgs) => ['Collections', 'unpublishCollection', req] as const, - createContractCollection: (req: CreateContractCollectionArgs) => - ['Collections', 'createContractCollection', req] as const, - getContractCollection: (req: GetContractCollectionArgs) => ['Collections', 'getContractCollection', req] as const, - listContractCollections: (req: ListContractCollectionsArgs) => - ['Collections', 'listContractCollections', req] as const, - updateContractCollection: (req: UpdateContractCollectionArgs) => - ['Collections', 'updateContractCollection', req] as const, - deleteContractCollection: (req: DeleteContractCollectionArgs) => - ['Collections', 'deleteContractCollection', req] as const, - createToken: (req: CreateTokenArgs) => ['Collections', 'createToken', req] as const, - getToken: (req: GetTokenArgs) => ['Collections', 'getToken', req] as const, - listTokens: (req: ListTokensArgs) => ['Collections', 'listTokens', req] as const, - updateToken: (req: UpdateTokenArgs) => ['Collections', 'updateToken', req] as const, - deleteToken: (req: DeleteTokenArgs) => ['Collections', 'deleteToken', req] as const, - createAsset: (req: CreateAssetArgs) => ['Collections', 'createAsset', req] as const, - getAsset: (req: GetAssetArgs) => ['Collections', 'getAsset', req] as const, - updateAsset: (req: UpdateAssetArgs) => ['Collections', 'updateAsset', req] as const, - deleteAsset: (req: DeleteAssetArgs) => ['Collections', 'deleteAsset', req] as const, - } - - createCollection = ( - req: CreateCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('CreateCollection'), - createHttpRequest(JsonEncode(req, 'CreateCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CreateCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getCollection = (req: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('GetCollection'), - createHttpRequest(JsonEncode(req, 'GetCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listCollections = ( - req: ListCollectionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListCollections'), - createHttpRequest(JsonEncode(req, 'ListCollectionsArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListCollectionsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateCollection = ( - req: UpdateCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdateCollection'), - createHttpRequest(JsonEncode(req, 'UpdateCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteCollection = ( - req: DeleteCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteCollection'), - createHttpRequest(JsonEncode(req, 'DeleteCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - publishCollection = ( - req: PublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PublishCollection'), - createHttpRequest(JsonEncode(req, 'PublishCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PublishCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - unpublishCollection = ( - req: UnpublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UnpublishCollection'), - createHttpRequest(JsonEncode(req, 'UnpublishCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UnpublishCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createContractCollection = ( - req: CreateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('CreateContractCollection'), - createHttpRequest(JsonEncode(req, 'CreateContractCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CreateContractCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getContractCollection = ( - req: GetContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetContractCollection'), - createHttpRequest(JsonEncode(req, 'GetContractCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetContractCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listContractCollections = ( - req: ListContractCollectionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListContractCollections'), - createHttpRequest(JsonEncode(req, 'ListContractCollectionsArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListContractCollectionsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateContractCollection = ( - req: UpdateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdateContractCollection'), - createHttpRequest(JsonEncode(req, 'UpdateContractCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateContractCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteContractCollection = ( - req: DeleteContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteContractCollection'), - createHttpRequest(JsonEncode(req, 'DeleteContractCollectionArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteContractCollectionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createToken = (req: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('CreateToken'), - createHttpRequest(JsonEncode(req, 'CreateTokenArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CreateTokenReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getToken = (req: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetToken'), createHttpRequest(JsonEncode(req, 'GetTokenArgs'), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTokenReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listTokens = (req: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('ListTokens'), - createHttpRequest(JsonEncode(req, 'ListTokensArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListTokensReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateToken = (req: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('UpdateToken'), - createHttpRequest(JsonEncode(req, 'UpdateTokenArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateTokenReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteToken = (req: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('DeleteToken'), - createHttpRequest(JsonEncode(req, 'DeleteTokenArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteTokenReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - createAsset = (req: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('CreateAsset'), - createHttpRequest(JsonEncode(req, 'CreateAssetArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'CreateAssetReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getAsset = (req: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetAsset'), createHttpRequest(JsonEncode(req, 'GetAssetArgs'), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetAssetReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateAsset = (req: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('UpdateAsset'), - createHttpRequest(JsonEncode(req, 'UpdateAssetArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateAssetReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteAsset = (req: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('DeleteAsset'), - createHttpRequest(JsonEncode(req, 'DeleteAssetArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteAssetReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} -export class Admin implements AdminClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Admin/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - refreshContractInfoUpdatedBefore: (req: RefreshContractInfoUpdatedBeforeArgs) => - ['Admin', 'refreshContractInfoUpdatedBefore', req] as const, - refreshTokenMetadataUpdatedBefore: (req: RefreshTokenMetadataUpdatedBeforeArgs) => - ['Admin', 'refreshTokenMetadataUpdatedBefore', req] as const, - getContractInfoOverride: (req: GetContractInfoOverrideArgs) => ['Admin', 'getContractInfoOverride', req] as const, - getContractInfoOverrides: (req: GetContractInfoOverridesArgs) => - ['Admin', 'getContractInfoOverrides', req] as const, - addContractInfoOverride: (req: AddContractInfoOverrideArgs) => ['Admin', 'addContractInfoOverride', req] as const, - updateContractInfoOverride: (req: UpdateContractInfoOverrideArgs) => - ['Admin', 'updateContractInfoOverride', req] as const, - removeContractInfoOverride: (req: RemoveContractInfoOverrideArgs) => - ['Admin', 'removeContractInfoOverride', req] as const, - isInTokenDirectory: (req: IsInTokenDirectoryArgs) => ['Admin', 'isInTokenDirectory', req] as const, - setTokenDirectoryFeatureIndex: (req: SetTokenDirectoryFeatureIndexArgs) => - ['Admin', 'setTokenDirectoryFeatureIndex', req] as const, - addContractToTokenDirectory: (req: AddContractToTokenDirectoryArgs) => - ['Admin', 'addContractToTokenDirectory', req] as const, - removeContractFromTokenDirectory: (req: RemoveContractFromTokenDirectoryArgs) => - ['Admin', 'removeContractFromTokenDirectory', req] as const, - refreshTokenDirectory: () => ['Admin', 'refreshTokenDirectory'] as const, - } - - refreshContractInfoUpdatedBefore = ( - req: RefreshContractInfoUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RefreshContractInfoUpdatedBefore'), - createHttpRequest(JsonEncode(req, 'RefreshContractInfoUpdatedBeforeArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshContractInfoUpdatedBeforeReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - refreshTokenMetadataUpdatedBefore = ( - req: RefreshTokenMetadataUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RefreshTokenMetadataUpdatedBefore'), - createHttpRequest(JsonEncode(req, 'RefreshTokenMetadataUpdatedBeforeArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshTokenMetadataUpdatedBeforeReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getContractInfoOverride = ( - req: GetContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetContractInfoOverride'), - createHttpRequest(JsonEncode(req, 'GetContractInfoOverrideArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetContractInfoOverrideReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getContractInfoOverrides = ( - req: GetContractInfoOverridesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetContractInfoOverrides'), - createHttpRequest(JsonEncode(req, 'GetContractInfoOverridesArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetContractInfoOverridesReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addContractInfoOverride = ( - req: AddContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('AddContractInfoOverride'), - createHttpRequest(JsonEncode(req, 'AddContractInfoOverrideArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddContractInfoOverrideReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateContractInfoOverride = ( - req: UpdateContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('UpdateContractInfoOverride'), - createHttpRequest(JsonEncode(req, 'UpdateContractInfoOverrideArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateContractInfoOverrideReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeContractInfoOverride = ( - req: RemoveContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RemoveContractInfoOverride'), - createHttpRequest(JsonEncode(req, 'RemoveContractInfoOverrideArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveContractInfoOverrideReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - isInTokenDirectory = ( - req: IsInTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('IsInTokenDirectory'), - createHttpRequest(JsonEncode(req, 'IsInTokenDirectoryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'IsInTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - setTokenDirectoryFeatureIndex = ( - req: SetTokenDirectoryFeatureIndexArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('SetTokenDirectoryFeatureIndex'), - createHttpRequest(JsonEncode(req, 'SetTokenDirectoryFeatureIndexArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SetTokenDirectoryFeatureIndexReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addContractToTokenDirectory = ( - req: AddContractToTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('AddContractToTokenDirectory'), - createHttpRequest(JsonEncode(req, 'AddContractToTokenDirectoryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddContractToTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeContractFromTokenDirectory = ( - req: RemoveContractFromTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('RemoveContractFromTokenDirectory'), - createHttpRequest(JsonEncode(req, 'RemoveContractFromTokenDirectoryArgs'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveContractFromTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - refreshTokenDirectory = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RefreshTokenDirectory'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RefreshTokenDirectoryReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { - ...headers, - 'Content-Type': 'application/json', - [WebrpcHeader]: WebrpcHeaderValue, - } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -export const JsonEncode = (obj: T, _typ: string = ''): string => { - return JSON.stringify(obj) -} - -export const JsonDecode = (data: string | any, _typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return parsed as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class FailError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Fail' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request Failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, FailError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class TaskFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'TaskFailed' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Task failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TaskFailedError.prototype) - } -} - -export class DeprecatedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Deprecated' - this.code = typeof error.code === 'number' ? error.code : 1008 - this.message = error.message || `RPC method is deprecated` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, DeprecatedError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Timeout' - this.code = typeof error.code === 'number' ? error.code : 2000 - this.message = error.message || `Request timed out` - this.status = typeof error.status === 'number' ? error.status : 408 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2001 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class RequiredArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequiredArgument' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Required argument missing` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequiredArgumentError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class ValidationFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ValidationFailed' - this.code = typeof error.code === 'number' ? error.code : 2004 - this.message = error.message || `Validation failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ValidationFailedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 2005 - this.message = error.message || `Rate limited` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 3002 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class ChainNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ChainNotFound' - this.code = typeof error.code === 'number' ? error.code : 3003 - this.message = error.message || `Chain not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ChainNotFoundError.prototype) - } -} - -export class TokenDirectoryDisabledError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'TokenDirectoryDisabled' - this.code = typeof error.code === 'number' ? error.code : 4001 - this.message = error.message || `Token Directory is disabled` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TokenDirectoryDisabledError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Fail = 'Fail', - Geoblocked = 'Geoblocked', - TaskFailed = 'TaskFailed', - Deprecated = 'Deprecated', - Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', - RequiredArgument = 'RequiredArgument', - QueryFailed = 'QueryFailed', - ValidationFailed = 'ValidationFailed', - RateLimited = 'RateLimited', - NotFound = 'NotFound', - ProjectNotFound = 'ProjectNotFound', - ChainNotFound = 'ChainNotFound', - TokenDirectoryDisabled = 'TokenDirectoryDisabled', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Fail = 1005, - Geoblocked = 1006, - TaskFailed = 1007, - Deprecated = 1008, - Timeout = 2000, - InvalidArgument = 2001, - RequiredArgument = 2002, - QueryFailed = 2003, - ValidationFailed = 2004, - RateLimited = 2005, - NotFound = 3000, - ProjectNotFound = 3002, - ChainNotFound = 3003, - TokenDirectoryDisabled = 4001, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: FailError, - [1006]: GeoblockedError, - [1007]: TaskFailedError, - [1008]: DeprecatedError, - [2000]: TimeoutError, - [2001]: InvalidArgumentError, - [2002]: RequiredArgumentError, - [2003]: QueryFailedError, - [2004]: ValidationFailedError, - [2005]: RateLimitedError, - [3000]: NotFoundError, - [3002]: ProjectNotFoundError, - [3003]: ChainNotFoundError, - [4001]: TokenDirectoryDisabledError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.31.0;gen-typescript@v0.22.5;sequence-metadata@v0.4.0' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/metadata/tsconfig.json b/packages/services/metadata/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/metadata/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/relayer/CHANGELOG.md b/packages/services/relayer/CHANGELOG.md deleted file mode 100644 index abefb99b79..0000000000 --- a/packages/services/relayer/CHANGELOG.md +++ /dev/null @@ -1,4084 +0,0 @@ -# @0xsequence/relayer - -## 3.0.5 - -### Patch Changes - -- Account federation support -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.5 - -## 3.0.4 - -### Patch Changes - -- id-token login support -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.4 - -## 3.0.3 - -### Patch Changes - -- 3.0.3 -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.2 - -## 3.0.1 - -### Patch Changes - -- Network and session fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.1 - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade -- Updated dependencies [f68be62] -- Updated dependencies [49d8a2f] -- Updated dependencies [3411232] -- Updated dependencies [23cb9e9] -- Updated dependencies [f5f6a7a] -- Updated dependencies [e7de3b1] -- Updated dependencies [493836f] -- Updated dependencies [30e1f1a] -- Updated dependencies [d5017e8] -- Updated dependencies [24a5fab] -- Updated dependencies [e5e1a03] -- Updated dependencies [0b63113] -- Updated dependencies [a89134a] -- Updated dependencies [7c6c811] -- Updated dependencies -- Updated dependencies [98ce38b] -- Updated dependencies [747e6b5] -- Updated dependencies [40c19ff] -- Updated dependencies [6d5de25] -- Updated dependencies [934acd1] - - @0xsequence/wallet-primitives@3.0.0 - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.19 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.18 - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.17 - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.16 - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.15 - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.14 - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.13 - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.12 - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.11 - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.10 - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.9 - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.8 - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.7 - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.6 - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.5 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.4 - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.3 - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.2 - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 -- Updated dependencies - - @0xsequence/wallet-primitives@3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients -- Updated dependencies - - @0xsequence/abi@2.3.8 - - @0xsequence/core@2.3.8 - - @0xsequence/utils@2.3.8 - -## 2.3.7 - -### Patch Changes - -- Metadata updates -- Updated dependencies - - @0xsequence/abi@2.3.7 - - @0xsequence/core@2.3.7 - - @0xsequence/utils@2.3.7 - -## 2.3.6 - -### Patch Changes - -- New chains -- Updated dependencies - - @0xsequence/abi@2.3.6 - - @0xsequence/core@2.3.6 - - @0xsequence/utils@2.3.6 - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet -- Updated dependencies - - @0xsequence/abi@2.3.5 - - @0xsequence/core@2.3.5 - - @0xsequence/utils@2.3.5 - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client -- Updated dependencies - - @0xsequence/abi@2.3.4 - - @0xsequence/core@2.3.4 - - @0xsequence/utils@2.3.4 - -## 2.3.3 - -### Patch Changes - -- metadata: client update -- Updated dependencies - - @0xsequence/abi@2.3.3 - - @0xsequence/core@2.3.3 - - @0xsequence/utils@2.3.3 - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client -- Updated dependencies - - @0xsequence/abi@2.3.2 - - @0xsequence/core@2.3.2 - - @0xsequence/utils@2.3.2 - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client -- Updated dependencies - - @0xsequence/abi@2.3.1 - - @0xsequence/core@2.3.1 - - @0xsequence/utils@2.3.1 - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@2.3.0 - - @0xsequence/core@2.3.0 - - @0xsequence/utils@2.3.0 - -## 2.2.15 - -### Patch Changes - -- API updates -- Updated dependencies - - @0xsequence/abi@2.2.15 - - @0xsequence/core@2.2.15 - - @0xsequence/utils@2.2.15 - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet -- Updated dependencies - - @0xsequence/abi@2.2.14 - - @0xsequence/core@2.2.14 - - @0xsequence/utils@2.2.14 - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks -- Updated dependencies - - @0xsequence/abi@2.2.13 - - @0xsequence/core@2.2.13 - - @0xsequence/utils@2.2.13 - -## 2.2.12 - -### Patch Changes - -- Add XR1 -- Updated dependencies - - @0xsequence/abi@2.2.12 - - @0xsequence/core@2.2.12 - - @0xsequence/utils@2.2.12 - -## 2.2.11 - -### Patch Changes - -- Relayer updates -- Updated dependencies - - @0xsequence/abi@2.2.11 - - @0xsequence/core@2.2.11 - - @0xsequence/utils@2.2.11 - -## 2.2.10 - -### Patch Changes - -- Etherlink support -- Updated dependencies - - @0xsequence/abi@2.2.10 - - @0xsequence/core@2.2.10 - - @0xsequence/utils@2.2.10 - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances -- Updated dependencies - - @0xsequence/abi@2.2.9 - - @0xsequence/core@2.2.9 - - @0xsequence/utils@2.2.9 - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha -- Updated dependencies - - @0xsequence/abi@2.2.8 - - @0xsequence/core@2.2.8 - - @0xsequence/utils@2.2.8 - -## 2.2.7 - -### Patch Changes - -- Update Builder package -- Updated dependencies - - @0xsequence/abi@2.2.7 - - @0xsequence/core@2.2.7 - - @0xsequence/utils@2.2.7 - -## 2.2.6 - -### Patch Changes - -- Update relayer package -- Updated dependencies - - @0xsequence/abi@2.2.6 - - @0xsequence/core@2.2.6 - - @0xsequence/utils@2.2.6 - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.2.5 - - @0xsequence/core@2.2.5 - - @0xsequence/utils@2.2.5 - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.2.4 - - @0xsequence/core@2.2.4 - - @0xsequence/utils@2.2.4 - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist -- Updated dependencies - - @0xsequence/abi@2.2.3 - - @0xsequence/core@2.2.3 - - @0xsequence/utils@2.2.3 - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times -- Updated dependencies - - @0xsequence/abi@2.2.2 - - @0xsequence/core@2.2.2 - - @0xsequence/utils@2.2.2 - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data -- Updated dependencies - - @0xsequence/abi@2.2.1 - - @0xsequence/core@2.2.1 - - @0xsequence/utils@2.2.1 - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -### Patch Changes - -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.2.0 - - @0xsequence/core@2.2.0 - - @0xsequence/utils@2.2.0 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet -- Updated dependencies - - @0xsequence/abi@2.1.8 - - @0xsequence/core@2.1.8 - - @0xsequence/utils@2.1.8 - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests -- Updated dependencies - - @0xsequence/abi@2.1.7 - - @0xsequence/core@2.1.7 - - @0xsequence/utils@2.1.7 - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains -- Updated dependencies - - @0xsequence/abi@2.1.6 - - @0xsequence/core@2.1.6 - - @0xsequence/utils@2.1.6 - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 -- Updated dependencies - - @0xsequence/abi@2.1.5 - - @0xsequence/core@2.1.5 - - @0xsequence/utils@2.1.5 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider -- Updated dependencies - - @0xsequence/abi@2.1.4 - - @0xsequence/core@2.1.4 - - @0xsequence/utils@2.1.4 - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk -- Updated dependencies - - @0xsequence/abi@2.1.3 - - @0xsequence/core@2.1.3 - - @0xsequence/utils@2.1.3 - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly -- Updated dependencies - - @0xsequence/abi@2.1.2 - - @0xsequence/core@2.1.2 - - @0xsequence/utils@2.1.2 - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support -- Updated dependencies - - @0xsequence/abi@2.1.1 - - @0xsequence/core@2.1.1 - - @0xsequence/utils@2.1.1 - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.1.0 - - @0xsequence/core@2.1.0 - - @0xsequence/utils@2.1.0 - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison -- Updated dependencies - - @0xsequence/abi@2.0.26 - - @0xsequence/core@2.0.26 - - @0xsequence/utils@2.0.26 - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m -- Updated dependencies - - @0xsequence/abi@2.0.25 - - @0xsequence/core@2.0.25 - - @0xsequence/utils@2.0.25 - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.0.24 - - @0xsequence/core@2.0.24 - - @0xsequence/utils@2.0.24 - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support -- Updated dependencies - - @0xsequence/abi@2.0.23 - - @0xsequence/core@2.0.23 - - @0xsequence/utils@2.0.23 - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support -- Updated dependencies - - @0xsequence/abi@2.0.22 - - @0xsequence/core@2.0.22 - - @0xsequence/utils@2.0.22 - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor -- Updated dependencies - - @0xsequence/abi@2.0.21 - - @0xsequence/core@2.0.21 - - @0xsequence/utils@2.0.21 - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling -- Updated dependencies - - @0xsequence/abi@2.0.20 - - @0xsequence/core@2.0.20 - - @0xsequence/utils@2.0.20 - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support -- Updated dependencies - - @0xsequence/abi@2.0.19 - - @0xsequence/core@2.0.19 - - @0xsequence/utils@2.0.19 - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.0.18 - - @0xsequence/core@2.0.18 - - @0xsequence/utils@2.0.18 - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn -- Updated dependencies - - @0xsequence/abi@2.0.17 - - @0xsequence/core@2.0.17 - - @0xsequence/utils@2.0.17 - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains -- Updated dependencies - - @0xsequence/abi@2.0.16 - - @0xsequence/core@2.0.16 - - @0xsequence/utils@2.0.16 - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions -- Updated dependencies - - @0xsequence/abi@2.0.15 - - @0xsequence/core@2.0.15 - - @0xsequence/utils@2.0.15 - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.0.14 - - @0xsequence/core@2.0.14 - - @0xsequence/utils@2.0.14 - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet -- Updated dependencies - - @0xsequence/abi@2.0.13 - - @0xsequence/core@2.0.13 - - @0xsequence/utils@2.0.13 - -## 2.0.12 - -### Patch Changes - -- api: update bindings -- Updated dependencies - - @0xsequence/abi@2.0.12 - - @0xsequence/core@2.0.12 - - @0xsequence/utils@2.0.12 - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.0.11 - - @0xsequence/core@2.0.11 - - @0xsequence/utils@2.0.11 - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet -- Updated dependencies - - @0xsequence/abi@2.0.10 - - @0xsequence/core@2.0.10 - - @0xsequence/utils@2.0.10 - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name -- Updated dependencies - - @0xsequence/abi@2.0.9 - - @0xsequence/core@2.0.9 - - @0xsequence/utils@2.0.9 - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/abi@2.0.8 - - @0xsequence/core@2.0.8 - - @0xsequence/utils@2.0.8 - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix -- Updated dependencies - - @0xsequence/abi@2.0.7 - - @0xsequence/core@2.0.7 - - @0xsequence/utils@2.0.7 - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol -- Updated dependencies - - @0xsequence/abi@2.0.6 - - @0xsequence/core@2.0.6 - - @0xsequence/utils@2.0.6 - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 -- Updated dependencies - - @0xsequence/abi@2.0.5 - - @0xsequence/core@2.0.5 - - @0xsequence/utils@2.0.5 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet -- Updated dependencies - - @0xsequence/abi@2.0.4 - - @0xsequence/core@2.0.4 - - @0xsequence/utils@2.0.4 - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() -- Updated dependencies - - @0xsequence/abi@2.0.3 - - @0xsequence/core@2.0.3 - - @0xsequence/utils@2.0.3 - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint -- Updated dependencies - - @0xsequence/abi@2.0.2 - - @0xsequence/core@2.0.2 - - @0xsequence/utils@2.0.2 - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@2.0.1 - - @0xsequence/core@2.0.1 - - @0xsequence/utils@2.0.1 - -## 2.0.0 - -### Major Changes - -- ethers v6 - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@2.0.0 - - @0xsequence/core@2.0.0 - - @0xsequence/utils@2.0.0 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey -- Updated dependencies - - @0xsequence/abi@1.10.15 - - @0xsequence/core@1.10.15 - - @0xsequence/utils@1.10.15 - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks -- Updated dependencies - - @0xsequence/abi@1.10.14 - - @0xsequence/core@1.10.14 - - @0xsequence/utils@1.10.14 - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet -- Updated dependencies - - @0xsequence/abi@1.10.13 - - @0xsequence/core@1.10.13 - - @0xsequence/utils@1.10.13 - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.10.12 - - @0xsequence/core@1.10.12 - - @0xsequence/utils@1.10.12 - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen -- Updated dependencies - - @0xsequence/abi@1.10.11 - - @0xsequence/core@1.10.11 - - @0xsequence/utils@1.10.11 - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api -- Updated dependencies - - @0xsequence/abi@1.10.10 - - @0xsequence/core@1.10.10 - - @0xsequence/utils@1.10.10 - -## 1.10.9 - -### Patch Changes - -- waas minor update -- Updated dependencies - - @0xsequence/abi@1.10.9 - - @0xsequence/core@1.10.9 - - @0xsequence/utils@1.10.9 - -## 1.10.8 - -### Patch Changes - -- update metadata bindings -- Updated dependencies - - @0xsequence/abi@1.10.8 - - @0xsequence/core@1.10.8 - - @0xsequence/utils@1.10.8 - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client -- Updated dependencies - - @0xsequence/abi@1.10.7 - - @0xsequence/core@1.10.7 - - @0xsequence/utils@1.10.7 - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/abi@1.10.6 - - @0xsequence/core@1.10.6 - - @0xsequence/utils@1.10.6 - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet -- Updated dependencies - - @0xsequence/abi@1.10.5 - - @0xsequence/core@1.10.5 - - @0xsequence/utils@1.10.5 - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia -- Updated dependencies - - @0xsequence/abi@1.10.4 - - @0xsequence/core@1.10.4 - - @0xsequence/utils@1.10.4 - -## 1.10.3 - -### Patch Changes - -- typing fix -- Updated dependencies - - @0xsequence/abi@1.10.3 - - @0xsequence/core@1.10.3 - - @0xsequence/utils@1.10.3 - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client -- Updated dependencies - - @0xsequence/abi@1.10.2 - - @0xsequence/core@1.10.2 - - @0xsequence/utils@1.10.2 - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/abi@1.10.1 - - @0xsequence/core@1.10.1 - - @0xsequence/utils@1.10.1 - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.10.0 - - @0xsequence/core@1.10.0 - - @0xsequence/utils@1.10.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants -- Updated dependencies - - @0xsequence/abi@1.9.37 - - @0xsequence/core@1.9.37 - - @0xsequence/utils@1.9.37 - -## 1.9.36 - -### Patch Changes - -- guard: export client -- Updated dependencies - - @0xsequence/abi@1.9.36 - - @0xsequence/core@1.9.36 - - @0xsequence/utils@1.9.36 - -## 1.9.35 - -### Patch Changes - -- guard: update bindings -- Updated dependencies - - @0xsequence/abi@1.9.35 - - @0xsequence/core@1.9.35 - - @0xsequence/utils@1.9.35 - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email -- Updated dependencies - - @0xsequence/abi@1.9.34 - - @0xsequence/core@1.9.34 - - @0xsequence/utils@1.9.34 - -## 1.9.33 - -### Patch Changes - -- waas: umd build -- Updated dependencies - - @0xsequence/abi@1.9.33 - - @0xsequence/core@1.9.33 - - @0xsequence/utils@1.9.33 - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings -- Updated dependencies - - @0xsequence/abi@1.9.32 - - @0xsequence/core@1.9.32 - - @0xsequence/utils@1.9.32 - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes -- Updated dependencies - - @0xsequence/abi@1.9.31 - - @0xsequence/core@1.9.31 - - @0xsequence/utils@1.9.31 - -## 1.9.30 - -### Patch Changes - -- update -- Updated dependencies - - @0xsequence/abi@1.9.30 - - @0xsequence/core@1.9.30 - - @0xsequence/utils@1.9.30 - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain -- Updated dependencies - - @0xsequence/abi@1.9.29 - - @0xsequence/core@1.9.29 - - @0xsequence/utils@1.9.29 - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree -- Updated dependencies - - @0xsequence/abi@1.9.28 - - @0xsequence/core@1.9.28 - - @0xsequence/utils@1.9.28 - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.9.27 - - @0xsequence/core@1.9.27 - - @0xsequence/utils@1.9.27 - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 -- Updated dependencies - - @0xsequence/abi@1.9.26 - - @0xsequence/core@1.9.26 - - @0xsequence/utils@1.9.26 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types -- Updated dependencies - - @0xsequence/abi@1.9.25 - - @0xsequence/core@1.9.25 - - @0xsequence/utils@1.9.25 - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore -- Updated dependencies - - @0xsequence/abi@1.9.24 - - @0xsequence/core@1.9.24 - - @0xsequence/utils@1.9.24 - -## 1.9.23 - -### Patch Changes - -- update api client bindings -- Updated dependencies - - @0xsequence/abi@1.9.23 - - @0xsequence/core@1.9.23 - - @0xsequence/utils@1.9.23 - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings -- Updated dependencies - - @0xsequence/abi@1.9.22 - - @0xsequence/core@1.9.22 - - @0xsequence/utils@1.9.22 - -## 1.9.21 - -### Patch Changes - -- api client bindings -- Updated dependencies - - @0xsequence/abi@1.9.21 - - @0xsequence/core@1.9.21 - - @0xsequence/utils@1.9.21 - -## 1.9.20 - -### Patch Changes - -- api client bindings update -- Updated dependencies - - @0xsequence/abi@1.9.20 - - @0xsequence/core@1.9.20 - - @0xsequence/utils@1.9.20 - -## 1.9.19 - -### Patch Changes - -- waas update -- Updated dependencies - - @0xsequence/abi@1.9.19 - - @0xsequence/core@1.9.19 - - @0xsequence/utils@1.9.19 - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions -- Updated dependencies - - @0xsequence/abi@1.9.18 - - @0xsequence/core@1.9.18 - - @0xsequence/utils@1.9.18 - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia -- Updated dependencies - - @0xsequence/abi@1.9.17 - - @0xsequence/core@1.9.17 - - @0xsequence/utils@1.9.17 - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions -- Updated dependencies - - @0xsequence/abi@1.9.16 - - @0xsequence/core@1.9.16 - - @0xsequence/utils@1.9.16 - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix -- Updated dependencies - - @0xsequence/abi@1.9.15 - - @0xsequence/core@1.9.15 - - @0xsequence/utils@1.9.15 - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.9.14 - - @0xsequence/core@1.9.14 - - @0xsequence/utils@1.9.14 - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency -- Updated dependencies - - @0xsequence/abi@1.9.13 - - @0xsequence/core@1.9.13 - - @0xsequence/utils@1.9.13 - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.9.12 - - @0xsequence/core@1.9.12 - - @0xsequence/utils@1.9.12 - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings -- Updated dependencies - - @0xsequence/abi@1.9.11 - - @0xsequence/core@1.9.11 - - @0xsequence/utils@1.9.11 - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings -- Updated dependencies - - @0xsequence/abi@1.9.10 - - @0xsequence/core@1.9.10 - - @0xsequence/utils@1.9.10 - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client -- Updated dependencies - - @0xsequence/abi@1.9.9 - - @0xsequence/core@1.9.9 - - @0xsequence/utils@1.9.9 - -## 1.9.8 - -### Patch Changes - -- waas client update -- Updated dependencies - - @0xsequence/abi@1.9.8 - - @0xsequence/core@1.9.8 - - @0xsequence/utils@1.9.8 - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer -- Updated dependencies - - @0xsequence/abi@1.9.7 - - @0xsequence/core@1.9.7 - - @0xsequence/utils@1.9.7 - -## 1.9.6 - -### Patch Changes - -- waas package update -- Updated dependencies - - @0xsequence/abi@1.9.6 - - @0xsequence/core@1.9.6 - - @0xsequence/utils@1.9.6 - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key -- Updated dependencies - - @0xsequence/abi@1.9.5 - - @0xsequence/core@1.9.5 - - @0xsequence/utils@1.9.5 - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency -- Updated dependencies - - @0xsequence/abi@1.9.4 - - @0xsequence/core@1.9.4 - - @0xsequence/utils@1.9.4 - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it -- Updated dependencies - - @0xsequence/abi@1.9.3 - - @0xsequence/core@1.9.3 - - @0xsequence/utils@1.9.3 - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia -- Updated dependencies - - @0xsequence/abi@1.9.2 - - @0xsequence/core@1.9.2 - - @0xsequence/utils@1.9.2 - -## 1.9.1 - -### Patch Changes - -- analytics fix -- Updated dependencies - - @0xsequence/abi@1.9.1 - - @0xsequence/core@1.9.1 - - @0xsequence/utils@1.9.1 - -## 1.9.0 - -### Minor Changes - -- waas release - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.9.0 - - @0xsequence/core@1.9.0 - - @0xsequence/utils@1.9.0 - -## 1.8.8 - -### Patch Changes - -- update metadata bindings -- Updated dependencies - - @0xsequence/abi@1.8.8 - - @0xsequence/core@1.8.8 - - @0xsequence/utils@1.8.8 - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 -- Updated dependencies - - @0xsequence/abi@1.8.7 - - @0xsequence/core@1.8.7 - - @0xsequence/utils@1.8.7 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof -- Updated dependencies - - @0xsequence/abi@1.8.6 - - @0xsequence/core@1.8.6 - - @0xsequence/utils@1.8.6 - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof -- Updated dependencies - - @0xsequence/abi@1.8.5 - - @0xsequence/core@1.8.5 - - @0xsequence/utils@1.8.5 - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list -- Updated dependencies - - @0xsequence/abi@1.8.4 - - @0xsequence/core@1.8.4 - - @0xsequence/utils@1.8.4 - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support -- Updated dependencies - - @0xsequence/abi@1.8.3 - - @0xsequence/core@1.8.3 - - @0xsequence/utils@1.8.3 - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested -- Updated dependencies - - @0xsequence/abi@1.8.2 - - @0xsequence/core@1.8.2 - - @0xsequence/utils@1.8.2 - -## 1.8.1 - -### Patch Changes - -- update to analytics provider -- Updated dependencies - - @0xsequence/abi@1.8.1 - - @0xsequence/core@1.8.1 - - @0xsequence/utils@1.8.1 - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.8.0 - - @0xsequence/core@1.8.0 - - @0xsequence/utils@1.8.0 - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.7.2 - - @0xsequence/core@1.7.2 - - @0xsequence/utils@1.7.2 - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI -- Updated dependencies - - @0xsequence/abi@1.7.1 - - @0xsequence/core@1.7.1 - - @0xsequence/utils@1.7.1 - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.7.0 - - @0xsequence/core@1.7.0 - - @0xsequence/utils@1.7.0 - -## 1.6.3 - -### Patch Changes - -- network list update -- Updated dependencies - - @0xsequence/abi@1.6.3 - - @0xsequence/core@1.6.3 - - @0xsequence/utils@1.6.3 - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.6.2 - - @0xsequence/core@1.6.2 - - @0xsequence/utils@1.6.2 - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.6.1 - - @0xsequence/core@1.6.1 - - @0xsequence/utils@1.6.1 - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.6.0 - - @0xsequence/core@1.6.0 - - @0xsequence/utils@1.6.0 - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.5.0 - - @0xsequence/core@1.5.0 - - @0xsequence/utils@1.5.0 - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata -- Updated dependencies - - @0xsequence/abi@1.4.9 - - @0xsequence/core@1.4.9 - - @0xsequence/utils@1.4.9 - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners -- Updated dependencies - - @0xsequence/abi@1.4.8 - - @0xsequence/core@1.4.8 - - @0xsequence/utils@1.4.8 - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings -- Updated dependencies - - @0xsequence/abi@1.4.7 - - @0xsequence/core@1.4.7 - - @0xsequence/utils@1.4.7 - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings -- Updated dependencies - - @0xsequence/abi@1.4.6 - - @0xsequence/core@1.4.6 - - @0xsequence/utils@1.4.6 - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.4.5 - - @0xsequence/core@1.4.5 - - @0xsequence/utils@1.4.5 - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.4.4 - - @0xsequence/core@1.4.4 - - @0xsequence/utils@1.4.4 - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods -- Updated dependencies - - @0xsequence/abi@1.4.3 - - @0xsequence/core@1.4.3 - - @0xsequence/utils@1.4.3 - -## 1.4.2 - -### Patch Changes - -- guard: update bindings -- Updated dependencies - - @0xsequence/abi@1.4.2 - - @0xsequence/core@1.4.2 - - @0xsequence/utils@1.4.2 - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.4.1 - - @0xsequence/core@1.4.1 - - @0xsequence/utils@1.4.1 - -## 1.4.0 - -### Minor Changes - -- project access key support - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.4.0 - - @0xsequence/core@1.4.0 - - @0xsequence/utils@1.4.0 - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.3.0 - - @0xsequence/core@1.3.0 - - @0xsequence/utils@1.3.0 - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.2.9 - - @0xsequence/core@1.2.9 - - @0xsequence/utils@1.2.9 - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key -- Updated dependencies - - @0xsequence/abi@1.2.8 - - @0xsequence/core@1.2.8 - - @0xsequence/utils@1.2.8 - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients -- Updated dependencies - - @0xsequence/abi@1.2.7 - - @0xsequence/core@1.2.7 - - @0xsequence/utils@1.2.7 - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider -- Updated dependencies - - @0xsequence/abi@1.2.6 - - @0xsequence/core@1.2.6 - - @0xsequence/utils@1.2.6 - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes -- Updated dependencies - - @0xsequence/abi@1.2.5 - - @0xsequence/core@1.2.5 - - @0xsequence/utils@1.2.5 - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.2.4 - - @0xsequence/core@1.2.4 - - @0xsequence/utils@1.2.4 - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce -- Updated dependencies - - @0xsequence/abi@1.2.3 - - @0xsequence/core@1.2.3 - - @0xsequence/utils@1.2.3 - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.2.2 - - @0xsequence/core@1.2.2 - - @0xsequence/utils@1.2.2 - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available -- Updated dependencies - - @0xsequence/abi@1.2.1 - - @0xsequence/core@1.2.1 - - @0xsequence/utils@1.2.1 - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.2.0 - - @0xsequence/core@1.2.0 - - @0xsequence/utils@1.2.0 - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering -- Updated dependencies - - @0xsequence/abi@1.1.15 - - @0xsequence/core@1.1.15 - - @0xsequence/utils@1.1.15 - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError -- Updated dependencies - - @0xsequence/abi@1.1.14 - - @0xsequence/core@1.1.14 - - @0xsequence/utils@1.1.14 - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.1.13 - - @0xsequence/core@1.1.13 - - @0xsequence/utils@1.1.13 - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions -- Updated dependencies - - @0xsequence/abi@1.1.12 - - @0xsequence/core@1.1.12 - - @0xsequence/utils@1.1.12 - -## 1.1.11 - -### Patch Changes - -- add homeverse configs -- Updated dependencies - - @0xsequence/abi@1.1.11 - - @0xsequence/core@1.1.11 - - @0xsequence/utils@1.1.11 - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send -- Updated dependencies - - @0xsequence/abi@1.1.10 - - @0xsequence/core@1.1.10 - - @0xsequence/utils@1.1.10 - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client -- Updated dependencies - - @0xsequence/abi@1.1.9 - - @0xsequence/core@1.1.9 - - @0xsequence/utils@1.1.9 - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter -- Updated dependencies - - @0xsequence/abi@1.1.8 - - @0xsequence/core@1.1.8 - - @0xsequence/utils@1.1.8 - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow -- Updated dependencies - - @0xsequence/abi@1.1.7 - - @0xsequence/core@1.1.7 - - @0xsequence/utils@1.1.7 - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters -- Updated dependencies - - @0xsequence/abi@1.1.6 - - @0xsequence/core@1.1.6 - - @0xsequence/utils@1.1.6 - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions -- Updated dependencies - - @0xsequence/abi@1.1.5 - - @0xsequence/core@1.1.5 - - @0xsequence/utils@1.1.5 - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.1.4 - - @0xsequence/core@1.1.4 - - @0xsequence/utils@1.1.4 - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.1.3 - - @0xsequence/core@1.1.3 - - @0xsequence/utils@1.1.3 - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes -- Updated dependencies - - @0xsequence/abi@1.1.2 - - @0xsequence/core@1.1.2 - - @0xsequence/utils@1.1.2 - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.1.1 - - @0xsequence/core@1.1.1 - - @0xsequence/utils@1.1.1 - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.1.0 - - @0xsequence/core@1.1.0 - - @0xsequence/utils@1.1.0 - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@1.0.5 - - @0xsequence/core@1.0.5 - - @0xsequence/utils@1.0.5 - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId -- Updated dependencies - - @0xsequence/abi@1.0.4 - - @0xsequence/core@1.0.4 - - @0xsequence/utils@1.0.4 - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers -- Updated dependencies - - @0xsequence/abi@1.0.3 - - @0xsequence/core@1.0.3 - - @0xsequence/utils@1.0.3 - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods -- Updated dependencies - - @0xsequence/abi@1.0.2 - - @0xsequence/core@1.0.2 - - @0xsequence/utils@1.0.2 - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet -- Updated dependencies - - @0xsequence/abi@1.0.1 - - @0xsequence/core@1.0.1 - - @0xsequence/utils@1.0.1 - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@1.0.0 - - @0xsequence/core@1.0.0 - - @0xsequence/utils@1.0.0 - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer -- Updated dependencies - - @0xsequence/abi@0.43.34 - - @0xsequence/config@0.43.34 - - @0xsequence/network@0.43.34 - - @0xsequence/transactions@0.43.34 - - @0xsequence/utils@0.43.34 - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler -- Updated dependencies - - @0xsequence/abi@0.43.33 - - @0xsequence/config@0.43.33 - - @0xsequence/network@0.43.33 - - @0xsequence/transactions@0.43.33 - - @0xsequence/utils@0.43.33 - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network -- Updated dependencies - - @0xsequence/abi@0.43.32 - - @0xsequence/config@0.43.32 - - @0xsequence/network@0.43.32 - - @0xsequence/transactions@0.43.32 - - @0xsequence/utils@0.43.32 - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect -- Updated dependencies - - @0xsequence/abi@0.43.31 - - @0xsequence/config@0.43.31 - - @0xsequence/network@0.43.31 - - @0xsequence/transactions@0.43.31 - - @0xsequence/utils@0.43.31 - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet -- Updated dependencies - - @0xsequence/abi@0.43.30 - - @0xsequence/config@0.43.30 - - @0xsequence/network@0.43.30 - - @0xsequence/transactions@0.43.30 - - @0xsequence/utils@0.43.30 - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object -- Updated dependencies - - @0xsequence/abi@0.43.29 - - @0xsequence/config@0.43.29 - - @0xsequence/network@0.43.29 - - @0xsequence/transactions@0.43.29 - - @0xsequence/utils@0.43.29 - -## 0.43.28 - -### Patch Changes - -- update api bindings -- Updated dependencies - - @0xsequence/abi@0.43.28 - - @0xsequence/config@0.43.28 - - @0xsequence/network@0.43.28 - - @0xsequence/transactions@0.43.28 - - @0xsequence/utils@0.43.28 - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method -- Updated dependencies - - @0xsequence/abi@0.43.27 - - @0xsequence/config@0.43.27 - - @0xsequence/network@0.43.27 - - @0xsequence/transactions@0.43.27 - - @0xsequence/utils@0.43.27 - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum -- Updated dependencies - - @0xsequence/abi@0.43.26 - - @0xsequence/config@0.43.26 - - @0xsequence/network@0.43.26 - - @0xsequence/transactions@0.43.26 - - @0xsequence/utils@0.43.26 - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks -- Updated dependencies - - @0xsequence/abi@0.43.25 - - @0xsequence/config@0.43.25 - - @0xsequence/network@0.43.25 - - @0xsequence/transactions@0.43.25 - - @0xsequence/utils@0.43.25 - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm -- Updated dependencies - - @0xsequence/abi@0.43.24 - - @0xsequence/config@0.43.24 - - @0xsequence/network@0.43.24 - - @0xsequence/transactions@0.43.24 - - @0xsequence/utils@0.43.24 - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM -- Updated dependencies - - @0xsequence/abi@0.43.23 - - @0xsequence/config@0.43.23 - - @0xsequence/network@0.43.23 - - @0xsequence/transactions@0.43.23 - - @0xsequence/utils@0.43.23 - -## 0.43.22 - -### Patch Changes - -- add zkevm chain -- Updated dependencies - - @0xsequence/abi@0.43.22 - - @0xsequence/config@0.43.22 - - @0xsequence/network@0.43.22 - - @0xsequence/transactions@0.43.22 - - @0xsequence/utils@0.43.22 - -## 0.43.21 - -### Patch Changes - -- api: update client bindings -- Updated dependencies - - @0xsequence/abi@0.43.21 - - @0xsequence/config@0.43.21 - - @0xsequence/network@0.43.21 - - @0xsequence/transactions@0.43.21 - - @0xsequence/utils@0.43.21 - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings -- Updated dependencies - - @0xsequence/abi@0.43.20 - - @0xsequence/config@0.43.20 - - @0xsequence/network@0.43.20 - - @0xsequence/transactions@0.43.20 - - @0xsequence/utils@0.43.20 - -## 0.43.19 - -### Patch Changes - -- session proof update -- Updated dependencies - - @0xsequence/abi@0.43.19 - - @0xsequence/config@0.43.19 - - @0xsequence/network@0.43.19 - - @0xsequence/transactions@0.43.19 - - @0xsequence/utils@0.43.19 - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening -- Updated dependencies - - @0xsequence/abi@0.43.18 - - @0xsequence/config@0.43.18 - - @0xsequence/network@0.43.18 - - @0xsequence/transactions@0.43.18 - - @0xsequence/utils@0.43.18 - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined -- Updated dependencies - - @0xsequence/abi@0.43.17 - - @0xsequence/config@0.43.17 - - @0xsequence/network@0.43.17 - - @0xsequence/transactions@0.43.17 - - @0xsequence/utils@0.43.17 - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use -- Updated dependencies - - @0xsequence/abi@0.43.16 - - @0xsequence/config@0.43.16 - - @0xsequence/network@0.43.16 - - @0xsequence/transactions@0.43.16 - - @0xsequence/utils@0.43.16 - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods -- Updated dependencies - - @0xsequence/abi@0.43.15 - - @0xsequence/config@0.43.15 - - @0xsequence/network@0.43.15 - - @0xsequence/transactions@0.43.15 - - @0xsequence/utils@0.43.15 - -## 0.43.14 - -### Patch Changes - -- bump -- Updated dependencies - - @0xsequence/abi@0.43.14 - - @0xsequence/config@0.43.14 - - @0xsequence/network@0.43.14 - - @0xsequence/transactions@0.43.14 - - @0xsequence/utils@0.43.14 - -## 0.43.13 - -### Patch Changes - -- update rpc bindings -- Updated dependencies - - @0xsequence/abi@0.43.13 - - @0xsequence/config@0.43.13 - - @0xsequence/network@0.43.13 - - @0xsequence/transactions@0.43.13 - - @0xsequence/utils@0.43.13 - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method -- Updated dependencies - - @0xsequence/abi@0.43.12 - - @0xsequence/config@0.43.12 - - @0xsequence/network@0.43.12 - - @0xsequence/transactions@0.43.12 - - @0xsequence/utils@0.43.12 - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.43.11 - - @0xsequence/config@0.43.11 - - @0xsequence/network@0.43.11 - - @0xsequence/transactions@0.43.11 - - @0xsequence/utils@0.43.11 - -## 0.43.10 - -### Patch Changes - -- various improvements -- Updated dependencies - - @0xsequence/abi@0.43.10 - - @0xsequence/config@0.43.10 - - @0xsequence/network@0.43.10 - - @0xsequence/transactions@0.43.10 - - @0xsequence/utils@0.43.10 - -## 0.43.9 - -### Patch Changes - -- update deps -- Updated dependencies - - @0xsequence/abi@0.43.9 - - @0xsequence/config@0.43.9 - - @0xsequence/network@0.43.9 - - @0xsequence/transactions@0.43.9 - - @0xsequence/utils@0.43.9 - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching -- Updated dependencies - - @0xsequence/abi@0.43.8 - - @0xsequence/config@0.43.8 - - @0xsequence/network@0.43.8 - - @0xsequence/transactions@0.43.8 - - @0xsequence/utils@0.43.8 - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init -- Updated dependencies - - @0xsequence/abi@0.43.7 - - @0xsequence/config@0.43.7 - - @0xsequence/transactions@0.43.7 - - @0xsequence/utils@0.43.7 - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings -- Updated dependencies - - @0xsequence/abi@0.43.6 - - @0xsequence/config@0.43.6 - - @0xsequence/transactions@0.43.6 - - @0xsequence/utils@0.43.6 - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.43.5 - - @0xsequence/config@0.43.5 - - @0xsequence/transactions@0.43.5 - - @0xsequence/utils@0.43.5 - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build -- Updated dependencies - - @0xsequence/abi@0.43.4 - - @0xsequence/config@0.43.4 - - @0xsequence/transactions@0.43.4 - - @0xsequence/utils@0.43.4 - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings -- Updated dependencies - - @0xsequence/abi@0.43.3 - - @0xsequence/config@0.43.3 - - @0xsequence/transactions@0.43.3 - - @0xsequence/utils@0.43.3 - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked -- Updated dependencies - - @0xsequence/abi@0.43.2 - - @0xsequence/config@0.43.2 - - @0xsequence/transactions@0.43.2 - - @0xsequence/utils@0.43.2 - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep -- Updated dependencies - - @0xsequence/abi@0.43.1 - - @0xsequence/config@0.43.1 - - @0xsequence/transactions@0.43.1 - - @0xsequence/utils@0.43.1 - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.43.0 - - @0xsequence/config@0.43.0 - - @0xsequence/transactions@0.43.0 - - @0xsequence/utils@0.43.0 - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider -- Updated dependencies - - @0xsequence/abi@0.42.10 - - @0xsequence/config@0.42.10 - - @0xsequence/transactions@0.42.10 - - @0xsequence/utils@0.42.10 - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions -- Updated dependencies - - @0xsequence/abi@0.42.9 - - @0xsequence/config@0.42.9 - - @0xsequence/transactions@0.42.9 - - @0xsequence/utils@0.42.9 - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin -- Updated dependencies - - @0xsequence/abi@0.42.8 - - @0xsequence/config@0.42.8 - - @0xsequence/transactions@0.42.8 - - @0xsequence/utils@0.42.8 - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings -- Updated dependencies - - @0xsequence/abi@0.42.7 - - @0xsequence/config@0.42.7 - - @0xsequence/transactions@0.42.7 - - @0xsequence/utils@0.42.7 - -## 0.42.6 - -### Patch Changes - -- api bindings update -- Updated dependencies - - @0xsequence/abi@0.42.6 - - @0xsequence/config@0.42.6 - - @0xsequence/transactions@0.42.6 - - @0xsequence/utils@0.42.6 - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure -- Updated dependencies - - @0xsequence/abi@0.42.5 - - @0xsequence/config@0.42.5 - - @0xsequence/transactions@0.42.5 - - @0xsequence/utils@0.42.5 - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options -- Updated dependencies - - @0xsequence/abi@0.42.4 - - @0xsequence/config@0.42.4 - - @0xsequence/transactions@0.42.4 - - @0xsequence/utils@0.42.4 - -## 0.42.3 - -### Patch Changes - -- update api bindings -- Updated dependencies - - @0xsequence/abi@0.42.3 - - @0xsequence/config@0.42.3 - - @0xsequence/transactions@0.42.3 - - @0xsequence/utils@0.42.3 - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network -- Updated dependencies - - @0xsequence/abi@0.42.2 - - @0xsequence/config@0.42.2 - - @0xsequence/transactions@0.42.2 - - @0xsequence/utils@0.42.2 - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter -- Updated dependencies - - @0xsequence/abi@0.42.1 - - @0xsequence/config@0.42.1 - - @0xsequence/transactions@0.42.1 - - @0xsequence/utils@0.42.1 - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.42.0 - - @0xsequence/config@0.42.0 - - @0xsequence/transactions@0.42.0 - - @0xsequence/utils@0.42.0 - -## 0.41.3 - -### Patch Changes - -- api bindings update -- Updated dependencies - - @0xsequence/abi@0.41.3 - - @0xsequence/config@0.41.3 - - @0xsequence/transactions@0.41.3 - - @0xsequence/utils@0.41.3 - -## 0.41.2 - -### Patch Changes - -- api bindings update -- Updated dependencies - - @0xsequence/abi@0.41.2 - - @0xsequence/config@0.41.2 - - @0xsequence/transactions@0.41.2 - - @0xsequence/utils@0.41.2 - -## 0.41.1 - -### Patch Changes - -- update default networks -- Updated dependencies - - @0xsequence/abi@0.41.1 - - @0xsequence/config@0.41.1 - - @0xsequence/transactions@0.41.1 - - @0xsequence/utils@0.41.1 - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.41.0 - - @0xsequence/config@0.41.0 - - @0xsequence/transactions@0.41.0 - - @0xsequence/utils@0.41.0 - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain -- Updated dependencies - - @0xsequence/abi@0.40.6 - - @0xsequence/config@0.40.6 - - @0xsequence/transactions@0.40.6 - - @0xsequence/utils@0.40.6 - -## 0.40.5 - -### Patch Changes - -- api: update bindings -- Updated dependencies - - @0xsequence/abi@0.40.5 - - @0xsequence/config@0.40.5 - - @0xsequence/transactions@0.40.5 - - @0xsequence/utils@0.40.5 - -## 0.40.4 - -### Patch Changes - -- add unreal transport -- Updated dependencies - - @0xsequence/abi@0.40.4 - - @0xsequence/config@0.40.4 - - @0xsequence/transactions@0.40.4 - - @0xsequence/utils@0.40.4 - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type -- Updated dependencies - - @0xsequence/abi@0.40.3 - - @0xsequence/config@0.40.3 - - @0xsequence/transactions@0.40.3 - - @0xsequence/utils@0.40.3 - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method -- Updated dependencies - - @0xsequence/abi@0.40.2 - - @0xsequence/config@0.40.2 - - @0xsequence/transactions@0.40.2 - - @0xsequence/utils@0.40.2 - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet -- Updated dependencies - - @0xsequence/abi@0.40.1 - - @0xsequence/config@0.40.1 - - @0xsequence/transactions@0.40.1 - - @0xsequence/utils@0.40.1 - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.40.0 - - @0xsequence/config@0.40.0 - - @0xsequence/transactions@0.40.0 - - @0xsequence/utils@0.40.0 - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings -- Updated dependencies - - @0xsequence/abi@0.39.6 - - @0xsequence/config@0.39.6 - - @0xsequence/transactions@0.39.6 - - @0xsequence/utils@0.39.6 - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option -- Updated dependencies - - @0xsequence/abi@0.39.5 - - @0xsequence/config@0.39.5 - - @0xsequence/transactions@0.39.5 - - @0xsequence/utils@0.39.5 - -## 0.39.4 - -### Patch Changes - -- api: update client bindings -- Updated dependencies - - @0xsequence/abi@0.39.4 - - @0xsequence/config@0.39.4 - - @0xsequence/transactions@0.39.4 - - @0xsequence/utils@0.39.4 - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider -- Updated dependencies - - @0xsequence/abi@0.39.3 - - @0xsequence/config@0.39.3 - - @0xsequence/transactions@0.39.3 - - @0xsequence/utils@0.39.3 - -## 0.39.2 - -### Patch Changes - -- update umd name -- Updated dependencies - - @0xsequence/abi@0.39.2 - - @0xsequence/config@0.39.2 - - @0xsequence/transactions@0.39.2 - - @0xsequence/utils@0.39.2 - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.39.1 - - @0xsequence/config@0.39.1 - - @0xsequence/transactions@0.39.1 - - @0xsequence/utils@0.39.1 - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.39.0 - - @0xsequence/config@0.39.0 - - @0xsequence/transactions@0.39.0 - - @0xsequence/utils@0.39.0 - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount -- Updated dependencies - - @0xsequence/abi@0.38.2 - - @0xsequence/config@0.38.2 - - @0xsequence/transactions@0.38.2 - - @0xsequence/utils@0.38.2 - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings -- Updated dependencies - - @0xsequence/abi@0.38.1 - - @0xsequence/config@0.38.1 - - @0xsequence/transactions@0.38.1 - - @0xsequence/utils@0.38.1 - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -### Patch Changes - -- Updated dependencies -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.38.0 - - @0xsequence/config@0.38.0 - - @0xsequence/transactions@0.38.0 - - @0xsequence/utils@0.38.0 - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. -- Updated dependencies - - @0xsequence/abi@0.37.1 - - @0xsequence/config@0.37.1 - - @0xsequence/transactions@0.37.1 - - @0xsequence/utils@0.37.1 - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -### Patch Changes - -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.37.0 - - @0xsequence/config@0.37.0 - - @0xsequence/transactions@0.37.0 - - @0xsequence/utils@0.37.0 - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints -- Updated dependencies - - @0xsequence/abi@0.36.13 - - @0xsequence/config@0.36.13 - - @0xsequence/transactions@0.36.13 - - @0xsequence/utils@0.36.13 - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token -- Updated dependencies -- Updated dependencies - - @0xsequence/abi@0.36.12 - - @0xsequence/config@0.36.12 - - @0xsequence/transactions@0.36.12 - - @0xsequence/utils@0.36.12 - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler -- Updated dependencies - - @0xsequence/abi@0.36.11 - - @0xsequence/config@0.36.11 - - @0xsequence/transactions@0.36.11 - - @0xsequence/utils@0.36.11 - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect -- Updated dependencies - - @0xsequence/abi@0.36.10 - - @0xsequence/config@0.36.10 - - @0xsequence/transactions@0.36.10 - - @0xsequence/utils@0.36.10 - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements -- Updated dependencies - - @0xsequence/abi@0.36.9 - - @0xsequence/config@0.36.9 - - @0xsequence/transactions@0.36.9 - - @0xsequence/utils@0.36.9 - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) -- Updated dependencies - - @0xsequence/abi@0.36.8 - - @0xsequence/config@0.36.8 - - @0xsequence/transactions@0.36.8 - - @0xsequence/utils@0.36.8 - -## 0.36.7 - -### Patch Changes - -- fix missing break -- Updated dependencies - - @0xsequence/abi@0.36.7 - - @0xsequence/config@0.36.7 - - @0xsequence/transactions@0.36.7 - - @0xsequence/utils@0.36.7 - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support -- Updated dependencies - - @0xsequence/abi@0.36.6 - - @0xsequence/config@0.36.6 - - @0xsequence/transactions@0.36.6 - - @0xsequence/utils@0.36.6 - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit -- Updated dependencies - - @0xsequence/abi@0.36.5 - - @0xsequence/config@0.36.5 - - @0xsequence/transactions@0.36.5 - - @0xsequence/utils@0.36.5 - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains -- Updated dependencies - - @0xsequence/abi@0.36.4 - - @0xsequence/config@0.36.4 - - @0xsequence/transactions@0.36.4 - - @0xsequence/utils@0.36.4 - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods -- Updated dependencies - - @0xsequence/abi@0.36.3 - - @0xsequence/config@0.36.3 - - @0xsequence/transactions@0.36.3 - - @0xsequence/utils@0.36.3 - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode -- Updated dependencies - - @0xsequence/abi@0.36.2 - - @0xsequence/config@0.36.2 - - @0xsequence/transactions@0.36.2 - - @0xsequence/utils@0.36.2 - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields -- Updated dependencies - - @0xsequence/abi@0.36.1 - - @0xsequence/config@0.36.1 - - @0xsequence/transactions@0.36.1 - - @0xsequence/utils@0.36.1 - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.36.0 - - @0xsequence/config@0.36.0 - - @0xsequence/transactions@0.36.0 - - @0xsequence/utils@0.36.0 - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils -- Updated dependencies - - @0xsequence/abi@0.35.12 - - @0xsequence/config@0.35.12 - - @0xsequence/transactions@0.35.12 - - @0xsequence/utils@0.35.12 - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation -- Updated dependencies - - @0xsequence/abi@0.35.11 - - @0xsequence/config@0.35.11 - - @0xsequence/transactions@0.35.11 - - @0xsequence/utils@0.35.11 - -## 0.35.10 - -### Patch Changes - -- upgrade deps -- Updated dependencies - - @0xsequence/abi@0.35.10 - - @0xsequence/config@0.35.10 - - @0xsequence/transactions@0.35.10 - - @0xsequence/utils@0.35.10 - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance -- Updated dependencies - - @0xsequence/abi@0.35.9 - - @0xsequence/config@0.35.9 - - @0xsequence/transactions@0.35.9 - - @0xsequence/utils@0.35.9 - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements -- Updated dependencies - - @0xsequence/abi@0.35.8 - - @0xsequence/config@0.35.8 - - @0xsequence/transactions@0.35.8 - - @0xsequence/utils@0.35.8 - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs -- Updated dependencies - - @0xsequence/abi@0.35.7 - - @0xsequence/config@0.35.7 - - @0xsequence/transactions@0.35.7 - - @0xsequence/utils@0.35.7 - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler -- Updated dependencies - - @0xsequence/abi@0.35.6 - - @0xsequence/config@0.35.6 - - @0xsequence/transactions@0.35.6 - - @0xsequence/utils@0.35.6 - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation -- Updated dependencies - - @0xsequence/abi@0.35.5 - - @0xsequence/config@0.35.5 - - @0xsequence/transactions@0.35.5 - - @0xsequence/utils@0.35.5 - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window -- Updated dependencies - - @0xsequence/abi@0.35.4 - - @0xsequence/config@0.35.4 - - @0xsequence/transactions@0.35.4 - - @0xsequence/utils@0.35.4 - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode -- Updated dependencies - - @0xsequence/abi@0.35.3 - - @0xsequence/config@0.35.3 - - @0xsequence/transactions@0.35.3 - - @0xsequence/utils@0.35.3 - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref -- Updated dependencies - - @0xsequence/abi@0.35.2 - - @0xsequence/config@0.35.2 - - @0xsequence/transactions@0.35.2 - - @0xsequence/utils@0.35.2 - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too -- Updated dependencies - - @0xsequence/abi@0.35.1 - - @0xsequence/config@0.35.1 - - @0xsequence/transactions@0.35.1 - - @0xsequence/utils@0.35.1 - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.35.0 - - @0xsequence/config@0.35.0 - - @0xsequence/transactions@0.35.0 - - @0xsequence/utils@0.35.0 - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.34.0 - - @0xsequence/config@0.34.0 - - @0xsequence/transactions@0.34.0 - - @0xsequence/utils@0.34.0 - -## 0.33.2 - -### Patch Changes - -- Updated dependencies - - @0xsequence/transactions@0.33.2 - -## 0.31.1 - -### Patch Changes - -- relayer: add Relayer.simulate - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.31.0 - - @0xsequence/config@0.31.0 - - @0xsequence/transactions@0.31.0 - - @0xsequence/utils@0.31.0 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -### Patch Changes - -- Updated dependencies - - @0xsequence/abi@0.30.0 - - @0xsequence/config@0.30.0 - - @0xsequence/transactions@0.30.0 - - @0xsequence/utils@0.30.0 - -## 0.29.8 - -### Patch Changes - -- update api -- Updated dependencies [undefined] - - @0xsequence/abi@0.29.8 - - @0xsequence/config@0.29.8 - - @0xsequence/transactions@0.29.8 - - @0xsequence/utils@0.29.8 - -## 0.29.6 - -### Patch Changes - -- @0xsequence/config@0.29.6 -- @0xsequence/transactions@0.29.6 - -## 0.29.5 - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/config@0.29.5 - -## 0.29.2 - -### Patch Changes - -- relayer: don't pass nonce to GetMetaTxnNetworkFeeOptions - -## 0.29.0 - -### Minor Changes - -- major architectural changes in Sequence design - - only one API instance, API is no longer a per-chain service - - separate per-chain indexer service, API no longer handles indexing - - single contract metadata service, API no longer serves metadata - - chaind package has been removed, indexer and metadata packages have been added - - stronger typing with new explicit ChainId type - - multicall fixes and improvements - - forbid "wait" transactions in sendTransactionBatch calls - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/config@0.29.0 - - @0xsequence/transactions@0.29.0 - - @0xsequence/abi@0.29.0 - - @0xsequence/utils@0.29.0 - -## 0.28.0 - -### Minor Changes - -- extension provider - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.28.0 - - @0xsequence/chaind@0.28.0 - - @0xsequence/config@0.28.0 - - @0xsequence/transactions@0.28.0 - - @0xsequence/utils@0.28.0 - -## 0.27.1 - -### Patch Changes - -- fix waitReceipt polling logic - -## 0.27.0 - -### Minor Changes - -- Add requireFreshSigner lib to sessions - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.27.0 - - @0xsequence/chaind@0.27.0 - - @0xsequence/config@0.27.0 - - @0xsequence/transactions@0.27.0 - - @0xsequence/utils@0.27.0 - -## 0.26.0 - -### Minor Changes - -- update relayer client bindings - provide the wallet's address for calls to SendMetaTxn - modify the semantics of Relayer.getNonce() to allow relayers to select nonce spaces for clients - -## 0.25.1 - -### Patch Changes - -- Fix build typescrypt issue -- Updated dependencies [undefined] - - @0xsequence/abi@0.25.1 - - @0xsequence/chaind@0.25.1 - - @0xsequence/config@0.25.1 - - @0xsequence/transactions@0.25.1 - - @0xsequence/utils@0.25.1 - -## 0.25.0 - -### Minor Changes - -- 10c8af8: Add estimator package - Fix multicall few calls bug - -### Patch Changes - -- Updated dependencies [10c8af8] - - @0xsequence/abi@0.25.0 - - @0xsequence/chaind@0.25.0 - - @0xsequence/config@0.25.0 - - @0xsequence/transactions@0.25.0 - - @0xsequence/utils@0.25.0 - -## 0.24.1 - -### Patch Changes - -- relayer: wait for queued status instead of unknown - -## 0.24.0 - -### Minor Changes - -- pass wallet config and nonce to GetMetaTxnNetworkFeeOptions - -## 0.23.0 - -### Minor Changes - -- - relayer: offer variety of gas fee options from the relayer service" - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.23.0 - - @0xsequence/chaind@0.23.0 - - @0xsequence/config@0.23.0 - - @0xsequence/transactions@0.23.0 - - @0xsequence/utils@0.23.0 - -## 0.22.2 - -### Patch Changes - -- e1c109e: Fix authProof on expired sessions -- Updated dependencies [e1c109e] - - @0xsequence/abi@0.22.2 - - @0xsequence/chaind@0.22.2 - - @0xsequence/config@0.22.2 - - @0xsequence/transactions@0.22.2 - - @0xsequence/utils@0.22.2 - -## 0.22.1 - -### Patch Changes - -- transport session cache -- Updated dependencies [undefined] - - @0xsequence/abi@0.22.1 - - @0xsequence/chaind@0.22.1 - - @0xsequence/config@0.22.1 - - @0xsequence/transactions@0.22.1 - - @0xsequence/utils@0.22.1 - -## 0.22.0 - -### Minor Changes - -- e667b65: Expose all relayer options on networks - -### Patch Changes - -- Updated dependencies [e667b65] - - @0xsequence/abi@0.22.0 - - @0xsequence/utils@0.22.0 - - @0xsequence/chaind@0.22.0 - - @0xsequence/config@0.22.0 - - @0xsequence/transactions@0.22.0 - -## 0.21.5 - -### Patch Changes - -- Give priority to metaTxnId returned by relayer -- Updated dependencies [undefined] - - @0xsequence/abi@0.21.5 - - @0xsequence/chaind@0.21.5 - - @0xsequence/config@0.21.5 - - @0xsequence/transactions@0.21.5 - - @0xsequence/utils@0.21.5 - -## 0.21.4 - -### Patch Changes - -- Add has enough signers method -- Updated dependencies [undefined] - - @0xsequence/abi@0.21.4 - - @0xsequence/chaind@0.21.4 - - @0xsequence/config@0.21.4 - - @0xsequence/transactions@0.21.4 - - @0xsequence/utils@0.21.4 - -## 0.21.3 - -### Patch Changes - -- add window session cache -- Updated dependencies [undefined] - - @0xsequence/abi@0.21.3 - - @0xsequence/chaind@0.21.3 - - @0xsequence/config@0.21.3 - - @0xsequence/transactions@0.21.3 - - @0xsequence/utils@0.21.3 - -## 0.21.2 - -### Patch Changes - -- exception handlind in relayer -- Updated dependencies [undefined] - - @0xsequence/abi@0.21.2 - - @0xsequence/chaind@0.21.2 - - @0xsequence/config@0.21.2 - - @0xsequence/transactions@0.21.2 - - @0xsequence/utils@0.21.2 - -## 0.21.0 - -### Minor Changes - -- - fix gas estimation on wallets with large number of signers - - update to session handling and wallet config construction upon auth - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.21.0 - - @0xsequence/chaind@0.21.0 - - @0xsequence/config@0.21.0 - - @0xsequence/transactions@0.21.0 - - @0xsequence/utils@0.21.0 - -## 0.19.3 - -### Patch Changes - -- jwtAuth visibility, package version sync -- Updated dependencies [undefined] - - @0xsequence/abi@0.19.3 - - @0xsequence/chaind@0.19.3 - - @0xsequence/config@0.19.3 - - @0xsequence/transactions@0.19.3 - - @0xsequence/utils@0.19.3 - -## 0.19.2 - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.19.2 - - @0xsequence/config@0.19.2 - - @0xsequence/transactions@0.19.2 - -## 0.19.0 - -### Minor Changes - -- - provider, improve dapp / wallet transport io - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.19.0 - - @0xsequence/chaind@0.19.0 - - @0xsequence/config@0.19.0 - - @0xsequence/transactions@0.19.0 - - @0xsequence/utils@0.19.0 - -## 0.18.0 - -### Minor Changes - -- relayer improvements and pending transaction handling - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.18.0 - - @0xsequence/chaind@0.18.0 - - @0xsequence/config@0.18.0 - - @0xsequence/transactions@0.18.0 - - @0xsequence/utils@0.18.0 - -## 0.16.0 - -### Minor Changes - -- relayer as its own service separate from chaind - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.16.0 - - @0xsequence/chaind@0.16.0 - - @0xsequence/config@0.16.0 - - @0xsequence/transactions@0.16.0 - - @0xsequence/utils@0.16.0 - -## 0.15.1 - -### Patch Changes - -- update api clients -- Updated dependencies [undefined] - - @0xsequence/abi@0.15.1 - - @0xsequence/chaind@0.15.1 - - @0xsequence/config@0.15.1 - - @0xsequence/transactions@0.15.1 - - @0xsequence/utils@0.15.1 - -## 0.15.0 - -### Minor Changes - -- - update chaind and api bindings - - replace EstimateMetaTxnGasReceipt with UpdateMetaTxnGasLimits and GetMetaTxnNetworkFeeOptions - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/chaind@0.15.0 - - @0xsequence/transactions@0.15.0 - -## 0.14.3 - -### Patch Changes - -- Fix 0xSequence relayer dependencies -- Updated dependencies [undefined] - - @0xsequence/abi@0.14.3 - - @0xsequence/chaind@0.14.3 - - @0xsequence/config@0.14.3 - - @0xsequence/transactions@0.14.3 - - @0xsequence/utils@0.14.3 - -## 0.14.2 - -### Patch Changes - -- Add debug logs to rpc-relayer -- Updated dependencies [undefined] - - @0xsequence/abi@0.14.2 - - @0xsequence/chaind@0.14.2 - - @0xsequence/config@0.14.2 - - @0xsequence/transactions@0.14.2 - -## 0.14.0 - -### Minor Changes - -- update sequence utils finder which includes optimization - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.14.0 - - @0xsequence/chaind@0.14.0 - - @0xsequence/config@0.14.0 - - @0xsequence/transactions@0.14.0 - -## 0.13.0 - -### Minor Changes - -- Update SequenceUtils deployed contract - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.13.0 - - @0xsequence/chaind@0.13.0 - - @0xsequence/config@0.13.0 - - @0xsequence/transactions@0.13.0 - -## 0.12.1 - -### Patch Changes - -- npm bump -- Updated dependencies [undefined] - - @0xsequence/abi@0.12.1 - - @0xsequence/chaind@0.12.1 - - @0xsequence/config@0.12.1 - - @0xsequence/transactions@0.12.1 - -## 0.12.0 - -### Minor Changes - -- provider: improvements to window transport - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.12.0 - - @0xsequence/chaind@0.12.0 - - @0xsequence/config@0.12.0 - - @0xsequence/transactions@0.12.0 - -## 0.11.4 - -### Patch Changes - -- update api client -- Updated dependencies [undefined] - - @0xsequence/abi@0.11.4 - - @0xsequence/chaind@0.11.4 - - @0xsequence/config@0.11.4 - - @0xsequence/transactions@0.11.4 - -## 0.11.3 - -### Patch Changes - -- improve openWindow state options handling -- Updated dependencies [undefined] - - @0xsequence/abi@0.11.3 - - @0xsequence/chaind@0.11.3 - - @0xsequence/config@0.11.3 - - @0xsequence/transactions@0.11.3 - -## 0.11.2 - -### Patch Changes - -- Fix multicall proxy scopes -- Updated dependencies [undefined] - - @0xsequence/abi@0.11.2 - - @0xsequence/chaind@0.11.2 - - @0xsequence/config@0.11.2 - - @0xsequence/transactions@0.11.2 - -## 0.11.1 - -### Patch Changes - -- Add support for dynamic and nested signatures -- Updated dependencies [undefined] - - @0xsequence/abi@0.11.1 - - @0xsequence/chaind@0.11.1 - - @0xsequence/config@0.11.1 - - @0xsequence/transactions@0.11.1 - -## 0.11.0 - -### Minor Changes - -- Update wallet context to 1.7 contracts - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.11.0 - - @0xsequence/chaind@0.11.0 - - @0xsequence/config@0.11.0 - - @0xsequence/transactions@0.11.0 - -## 0.10.9 - -### Patch Changes - -- add support for public addresses as signers in session.open -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.9 - - @0xsequence/chaind@0.10.9 - - @0xsequence/config@0.10.9 - - @0xsequence/transactions@0.10.9 - -## 0.10.8 - -### Patch Changes - -- Multicall production configuration -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.8 - - @0xsequence/chaind@0.10.8 - - @0xsequence/config@0.10.8 - - @0xsequence/transactions@0.10.8 - -## 0.10.7 - -### Patch Changes - -- allow provider transport to force disconnect -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.7 - - @0xsequence/chaind@0.10.7 - - @0xsequence/config@0.10.7 - - @0xsequence/transactions@0.10.7 - -## 0.10.6 - -### Patch Changes - -- - fix getWalletState method -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.6 - - @0xsequence/chaind@0.10.6 - - @0xsequence/config@0.10.6 - - @0xsequence/transactions@0.10.6 - -## 0.10.5 - -### Patch Changes - -- update relayer gas refund options -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.5 - - @0xsequence/chaind@0.10.5 - - @0xsequence/config@0.10.5 - - @0xsequence/transactions@0.10.5 - -## 0.10.4 - -### Patch Changes - -- Update api proto -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.4 - - @0xsequence/chaind@0.10.4 - - @0xsequence/config@0.10.4 - - @0xsequence/transactions@0.10.4 - -## 0.10.3 - -### Patch Changes - -- Fix loading config cross-chain -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.3 - - @0xsequence/chaind@0.10.3 - - @0xsequence/config@0.10.3 - - @0xsequence/transactions@0.10.3 - -## 0.10.2 - -### Patch Changes - -- - message digest fix -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.2 - - @0xsequence/chaind@0.10.2 - - @0xsequence/config@0.10.2 - - @0xsequence/transactions@0.10.2 - -## 0.10.1 - -### Patch Changes - -- upgrade deps -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.1 - - @0xsequence/chaind@0.10.1 - - @0xsequence/config@0.10.1 - - @0xsequence/transactions@0.10.1 - -## 0.10.0 - -### Minor Changes - -- Deployed new contracts with ERC1271 signer support - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.10.0 - - @0xsequence/chaind@0.10.0 - - @0xsequence/config@0.10.0 - - @0xsequence/transactions@0.10.0 - -## 0.9.6 - -### Patch Changes - -- Update ABIs for latest sequence contracts -- Updated dependencies [undefined] - - @0xsequence/config@0.9.6 - - @0xsequence/transactions@0.9.6 - - @0xsequence/abi@0.9.6 - - @0xsequence/chaind@0.9.6 - -## 0.9.5 - -### Patch Changes - -- Implemented session class -- Updated dependencies [undefined] - - @0xsequence/config@0.9.5 - - @0xsequence/transactions@0.9.5 - -## 0.9.3 - -### Patch Changes - -- - minor improvements -- Updated dependencies [undefined] - - @0xsequence/abi@0.9.3 - - @0xsequence/chaind@0.9.3 - - @0xsequence/config@0.9.3 - - @0xsequence/transactions@0.9.3 - -## 0.9.1 - -### Patch Changes - -- - patch bump -- Updated dependencies [undefined] - - @0xsequence/abi@0.9.1 - - @0xsequence/chaind@0.9.1 - - @0xsequence/config@0.9.1 - - @0xsequence/transactions@0.9.1 - -## 0.9.0 - -### Minor Changes - -- - provider transport hardening - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.9.0 - - @0xsequence/chaind@0.9.0 - - @0xsequence/config@0.9.0 - - @0xsequence/transactions@0.9.0 - -## 0.8.5 - -### Patch Changes - -- - use latest wallet-contracts -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.5 - - @0xsequence/chaind@0.8.5 - - @0xsequence/config@0.8.5 - - @0xsequence/transactions@0.8.5 - -## 0.8.4 - -### Patch Changes - -- - minor improvements, name updates and comments -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.4 - - @0xsequence/chaind@0.8.4 - - @0xsequence/config@0.8.4 - - @0xsequence/transactions@0.8.4 - -## 0.8.3 - -### Patch Changes - -- - refinements - - - normalize signer address in config - - - provider: getWalletState() method to WalletProvider - -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.3 - - @0xsequence/chaind@0.8.3 - - @0xsequence/config@0.8.3 - - @0xsequence/transactions@0.8.3 - -## 0.8.2 - -### Patch Changes - -- - field rename and ethauth dependency bump -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.2 - - @0xsequence/chaind@0.8.2 - - @0xsequence/config@0.8.2 - - @0xsequence/transactions@0.8.2 - -## 0.8.1 - -### Patch Changes - -- - variety of optimizations -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.1 - - @0xsequence/chaind@0.8.1 - - @0xsequence/config@0.8.1 - - @0xsequence/transactions@0.8.1 - -## 0.8.0 - -### Minor Changes - -- - changeset fix - -### Patch Changes - -- Updated dependencies [undefined] - - @0xsequence/abi@0.8.0 - - @0xsequence/chaind@0.8.0 - - @0xsequence/config@0.8.0 - - @0xsequence/transactions@0.8.0 - -## 0.7.1 - -### Patch Changes - -- 02377ab: Minor improvements - -## 0.7.0 - -### Patch Changes - -- 6f11ed7: sequence.js, init release -- Updated dependencies [6f11ed7] - - @0xsequence/abi@0.7.0 - - @0xsequence/chaind@0.7.0 - - @0xsequence/config@0.7.0 - - @0xsequence/transactions@0.7.0 diff --git a/packages/services/relayer/README.md b/packages/services/relayer/README.md deleted file mode 100644 index f736cc8d32..0000000000 --- a/packages/services/relayer/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/relayer - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/relayer/eslint.config.js b/packages/services/relayer/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/relayer/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/relayer/package.json b/packages/services/relayer/package.json deleted file mode 100644 index 0c1422d970..0000000000 --- a/packages/services/relayer/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@0xsequence/relayer", - "version": "3.0.5", - "type": "module", - "publishConfig": { - "access": "public" - }, - "description": "relayer sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/relayer", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "old-test": "pnpm test:concurrently 'pnpm test:run'", - "old-test:run": "pnpm test:file tests/**/*.spec.ts", - "old-test:file": "NODE_OPTIONS='--import tsx' mocha --timeout 60000", - "old-test:concurrently": "concurrently -k --success first 'pnpm start:hardhat > /dev/null' ", - "start:hardhat": "pnpm hardhat node --port 9547", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "@0xsequence/wallet-primitives": "workspace:^", - "mipd": "^0.0.7", - "ox": "^0.9.17", - "viem": "^2.40.3" - } -} diff --git a/packages/services/relayer/src/index.ts b/packages/services/relayer/src/index.ts deleted file mode 100644 index dc28bfc771..0000000000 --- a/packages/services/relayer/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * as Relayer from './relayer/index.js' -export * as RpcRelayerGen from './relayer/rpc-relayer/relayer.gen.js' -export * as Preconditions from './preconditions/index.js' diff --git a/packages/services/relayer/src/preconditions/codec.ts b/packages/services/relayer/src/preconditions/codec.ts deleted file mode 100644 index b59fae6d67..0000000000 --- a/packages/services/relayer/src/preconditions/codec.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { Address } from 'ox' -import { - Precondition, - NativeBalancePrecondition, - Erc20BalancePrecondition, - Erc20ApprovalPrecondition, - Erc721OwnershipPrecondition, - Erc721ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc1155ApprovalPrecondition, -} from './types.js' - -export interface TransactionPrecondition { - type: string - chainId: number - ownerAddress: string - tokenAddress: string - minAmount: bigint -} - -export function decodePreconditions(preconditions: TransactionPrecondition[]): Precondition[] { - const decodedPreconditions: Precondition[] = [] - - for (const p of preconditions) { - const decoded = decodePrecondition(p) - if (decoded) { - decodedPreconditions.push(decoded) - } - } - - return decodedPreconditions -} - -export function decodePrecondition(p: TransactionPrecondition): Precondition | undefined { - if (!p) { - return undefined - } - - if (typeof p.minAmount !== 'bigint') { - console.warn(`Failed to decode precondition: minAmount must be a bigint`) - return undefined - } - - let precondition: Precondition | undefined - - try { - switch (p.type) { - case 'native-balance': - precondition = new NativeBalancePrecondition(Address.from(p.ownerAddress), p.minAmount, undefined) - break - - case 'erc20-balance': - precondition = new Erc20BalancePrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - p.minAmount, - undefined, - ) - break - - case 'erc20-approval': - precondition = new Erc20ApprovalPrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - Address.from(p.ownerAddress), - p.minAmount, - ) - break - - case 'erc721-ownership': - precondition = new Erc721OwnershipPrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - BigInt(0), - true, - ) - break - - case 'erc721-approval': - precondition = new Erc721ApprovalPrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - BigInt(0), - Address.from(p.ownerAddress), - ) - break - - case 'erc1155-balance': - precondition = new Erc1155BalancePrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - BigInt(0), - p.minAmount, - undefined, - ) - break - - case 'erc1155-approval': - precondition = new Erc1155ApprovalPrecondition( - Address.from(p.ownerAddress), - Address.from(p.tokenAddress), - BigInt(0), - Address.from(p.ownerAddress), - p.minAmount, - ) - break - - default: - return undefined - } - - const error = precondition.isValid() - if (error) { - console.warn(`Invalid precondition: ${error.message}`) - return undefined - } - - return precondition - } catch (e) { - console.warn(`Failed to decode precondition: ${e}`) - return undefined - } -} - -export function encodePrecondition(p: Precondition): string { - switch (p.type()) { - case 'native-balance': { - const native = p as NativeBalancePrecondition - const data = { - address: native.address.toString(), - ...(native.min !== undefined && { min: native.min.toString() }), - ...(native.max !== undefined && { max: native.max.toString() }), - } - - return JSON.stringify(data) - } - - case 'erc20-balance': { - const erc20 = p as Erc20BalancePrecondition - const data = { - address: erc20.address.toString(), - token: erc20.token.toString(), - ...(erc20.min !== undefined && { min: erc20.min.toString() }), - ...(erc20.max !== undefined && { max: erc20.max.toString() }), - } - - return JSON.stringify(data) - } - - case 'erc20-approval': { - const erc20 = p as Erc20ApprovalPrecondition - const data = { - address: erc20.address.toString(), - token: erc20.token.toString(), - operator: erc20.operator.toString(), - min: erc20.min.toString(), - } - - return JSON.stringify(data) - } - - case 'erc721-ownership': { - const erc721 = p as Erc721OwnershipPrecondition - const data = { - address: erc721.address.toString(), - token: erc721.token.toString(), - tokenId: erc721.tokenId.toString(), - ...(erc721.owned !== undefined && { owned: erc721.owned }), - } - - return JSON.stringify(data) - } - - case 'erc721-approval': { - const erc721 = p as Erc721ApprovalPrecondition - const data = { - address: erc721.address.toString(), - token: erc721.token.toString(), - tokenId: erc721.tokenId.toString(), - operator: erc721.operator.toString(), - } - - return JSON.stringify(data) - } - - case 'erc1155-balance': { - const erc1155 = p as Erc1155BalancePrecondition - const data = { - address: erc1155.address.toString(), - token: erc1155.token.toString(), - tokenId: erc1155.tokenId.toString(), - ...(erc1155.min !== undefined && { min: erc1155.min.toString() }), - ...(erc1155.max !== undefined && { max: erc1155.max.toString() }), - } - - return JSON.stringify(data) - } - - case 'erc1155-approval': { - const erc1155 = p as Erc1155ApprovalPrecondition - const data = { - address: erc1155.address.toString(), - token: erc1155.token.toString(), - tokenId: erc1155.tokenId.toString(), - operator: erc1155.operator.toString(), - min: erc1155.min.toString(), - } - - return JSON.stringify(data) - } - } - - return JSON.stringify({}) -} diff --git a/packages/services/relayer/src/preconditions/index.ts b/packages/services/relayer/src/preconditions/index.ts deleted file mode 100644 index 6bb6376ef6..0000000000 --- a/packages/services/relayer/src/preconditions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './types.js' -export * from './codec.js' -export * from './selectors.js' diff --git a/packages/services/relayer/src/preconditions/selectors.ts b/packages/services/relayer/src/preconditions/selectors.ts deleted file mode 100644 index d5985a8622..0000000000 --- a/packages/services/relayer/src/preconditions/selectors.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Precondition, NativeBalancePrecondition, Erc20BalancePrecondition } from './types.js' -import { TransactionPrecondition, decodePreconditions } from './codec.js' - -export function extractChainID(precondition: TransactionPrecondition): number | undefined { - if (!precondition) { - return undefined - } - - return precondition.chainId -} - -export function extractSupportedPreconditions(preconditions: TransactionPrecondition[]): Precondition[] { - if (!preconditions || preconditions.length === 0) { - return [] - } - - return decodePreconditions(preconditions) -} - -export function extractNativeBalancePreconditions( - preconditions: TransactionPrecondition[], -): NativeBalancePrecondition[] { - if (!preconditions || preconditions.length === 0) { - return [] - } - - const decoded = decodePreconditions(preconditions) - return decoded.filter((p): p is NativeBalancePrecondition => p.type() === 'native-balance') -} - -export function extractERC20BalancePreconditions(preconditions: TransactionPrecondition[]): Erc20BalancePrecondition[] { - if (!preconditions || preconditions.length === 0) { - return [] - } - - const decoded = decodePreconditions(preconditions) - return decoded.filter((p): p is Erc20BalancePrecondition => p.type() === 'erc20-balance') -} diff --git a/packages/services/relayer/src/preconditions/types.ts b/packages/services/relayer/src/preconditions/types.ts deleted file mode 100644 index 23a9db22c7..0000000000 --- a/packages/services/relayer/src/preconditions/types.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { Address } from 'ox' - -export interface Precondition { - type(): string - isValid(): Error | undefined -} - -export class NativeBalancePrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly min?: bigint, - public readonly max?: bigint, - ) {} - - type(): string { - return 'native-balance' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (this.min !== undefined && this.max !== undefined && this.min > this.max) { - return new Error('min balance cannot be greater than max balance') - } - return undefined - } -} - -export class Erc20BalancePrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly min?: bigint, - public readonly max?: bigint, - ) {} - - type(): string { - return 'erc20-balance' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (this.min !== undefined && this.max !== undefined && this.min > this.max) { - return new Error('min balance cannot be greater than max balance') - } - return undefined - } -} - -export class Erc20ApprovalPrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly operator: Address.Address, - public readonly min: bigint, - ) {} - - type(): string { - return 'erc20-approval' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (!this.operator) { - return new Error('operator address is required') - } - if (this.min === undefined) { - return new Error('min approval amount is required') - } - return undefined - } -} - -export class Erc721OwnershipPrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly tokenId: bigint, - public readonly owned?: boolean, - ) {} - - type(): string { - return 'erc721-ownership' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (this.tokenId === undefined) { - return new Error('tokenId is required') - } - return undefined - } -} - -export class Erc721ApprovalPrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly tokenId: bigint, - public readonly operator: Address.Address, - ) {} - - type(): string { - return 'erc721-approval' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (this.tokenId === undefined) { - return new Error('tokenId is required') - } - if (!this.operator) { - return new Error('operator address is required') - } - return undefined - } -} - -export class Erc1155BalancePrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly tokenId: bigint, - public readonly min?: bigint, - public readonly max?: bigint, - ) {} - - type(): string { - return 'erc1155-balance' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (this.tokenId === undefined) { - return new Error('tokenId is required') - } - if (this.min !== undefined && this.max !== undefined && this.min > this.max) { - return new Error('min balance cannot be greater than max balance') - } - return undefined - } -} - -export class Erc1155ApprovalPrecondition implements Precondition { - constructor( - public readonly address: Address.Address, - public readonly token: Address.Address, - public readonly tokenId: bigint, - public readonly operator: Address.Address, - public readonly min: bigint, - ) {} - - type(): string { - return 'erc1155-approval' - } - - isValid(): Error | undefined { - if (!this.address) { - return new Error('address is required') - } - if (!this.token) { - return new Error('token address is required') - } - if (this.tokenId === undefined) { - return new Error('tokenId is required') - } - if (!this.operator) { - return new Error('operator address is required') - } - if (this.min === undefined) { - return new Error('min approval amount is required') - } - return undefined - } -} diff --git a/packages/services/relayer/src/relayer/index.ts b/packages/services/relayer/src/relayer/index.ts deleted file mode 100644 index 52362d5c95..0000000000 --- a/packages/services/relayer/src/relayer/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Hex } from 'ox' -import type { FeeToken, GetMetaTxnReceiptReturn } from './rpc-relayer/relayer.gen.js' - -export * from './rpc-relayer/index.js' -export * from './standard/index.js' -export * from './relayer.js' -export type { FeeToken } from './rpc-relayer/relayer.gen.js' - -export interface FeeOption { - token: FeeToken - to: string - value: string - gasLimit: number -} - -export interface FeeQuote { - _tag: 'FeeQuote' - _quote: unknown -} - -export type OperationUnknownStatus = { - status: 'unknown' - reason?: string -} - -export type OperationQueuedStatus = { - status: 'queued' - reason?: string -} - -export type OperationPendingStatus = { - status: 'pending' - reason?: string -} - -export type OperationPendingPreconditionStatus = { - status: 'pending-precondition' - reason?: string -} - -export type OperationConfirmedStatus = { - status: 'confirmed' - transactionHash: Hex.Hex - data?: GetMetaTxnReceiptReturn -} - -export type OperationFailedStatus = { - status: 'failed' - transactionHash?: Hex.Hex - reason: string - data?: GetMetaTxnReceiptReturn -} - -export type OperationStatus = - | OperationUnknownStatus - | OperationQueuedStatus - | OperationPendingStatus - | OperationPendingPreconditionStatus - | OperationConfirmedStatus - | OperationFailedStatus diff --git a/packages/services/relayer/src/relayer/relayer.ts b/packages/services/relayer/src/relayer/relayer.ts deleted file mode 100644 index f685368200..0000000000 --- a/packages/services/relayer/src/relayer/relayer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Address, Hex } from 'ox' -import { FeeToken } from './rpc-relayer/relayer.gen.js' -import { FeeOption, FeeQuote, OperationStatus } from './index.js' -import { Payload, Precondition } from '@0xsequence/wallet-primitives' - -export interface Relayer { - kind: 'relayer' - - type: string - id: string - - isAvailable(wallet: Address.Address, chainId: number): Promise - - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> - - feeOptions( - wallet: Address.Address, - chainId: number, - to: Address.Address, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> - - relay(to: Address.Address, data: Hex.Hex, chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }> - - status(opHash: Hex.Hex, chainId: number): Promise - - checkPrecondition(precondition: Precondition.Precondition): Promise -} - -export function isRelayer(relayer: unknown): relayer is Relayer { - return ( - typeof relayer === 'object' && - relayer !== null && - 'isAvailable' in relayer && - 'feeOptions' in relayer && - 'relay' in relayer && - 'status' in relayer && - 'checkPrecondition' in relayer - ) -} diff --git a/packages/services/relayer/src/relayer/rpc-relayer/index.ts b/packages/services/relayer/src/relayer/rpc-relayer/index.ts deleted file mode 100644 index e045b996e0..0000000000 --- a/packages/services/relayer/src/relayer/rpc-relayer/index.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { - Relayer as GenRelayer, - SendMetaTxnReturn as RpcSendMetaTxnReturn, - MetaTxn as RpcMetaTxn, - FeeTokenType, - FeeToken as RpcFeeToken, - TransactionPrecondition, - ETHTxnStatus, -} from './relayer.gen.js' -import { Address, Hex, AbiFunction } from 'ox' -import { Constants, Payload, Network } from '@0xsequence/wallet-primitives' -import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { - decodePrecondition, - Erc1155ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc20ApprovalPrecondition, - Erc20BalancePrecondition, - Erc721ApprovalPrecondition, - Erc721OwnershipPrecondition, - NativeBalancePrecondition, -} from '../../preconditions/index.js' -import { - erc20BalanceOf, - erc20Allowance, - erc721OwnerOf, - erc721GetApproved, - erc1155BalanceOf, - erc1155IsApprovedForAll, -} from '../standard/abi.js' -import { PublicClient, createPublicClient, http, Chain } from 'viem' -import * as chains from 'viem/chains' - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -/** - * Convert a Sequence Network to a viem Chain - */ -const networkToChain = (network: Network.Network): Chain => { - return { - id: network.chainId, - name: network.title || network.name, - nativeCurrency: { - name: network.nativeCurrency.name, - symbol: network.nativeCurrency.symbol, - decimals: network.nativeCurrency.decimals, - }, - rpcUrls: { - default: { - http: [network.rpcUrl], - }, - }, - blockExplorers: network.blockExplorer - ? { - default: { - name: network.blockExplorer.name || 'Explorer', - url: network.blockExplorer.url, - }, - } - : undefined, - contracts: network.contracts - ? Object.entries(network.contracts).reduce( - (acc, [name, address]) => { - acc[name] = { address } - return acc - }, - {} as Record, - ) - : undefined, - } as Chain -} - -export const getChain = (chainId: number): Chain => { - // First try to get the chain from Sequence's network configurations - const sequenceNetwork = Network.getNetworkFromChainId(chainId) - if (sequenceNetwork) { - return networkToChain(sequenceNetwork) - } - - // Fall back to viem's built-in chains - const viemChain = Object.values(chains).find( - (c: unknown) => typeof c === 'object' && c !== null && 'id' in c && c.id === chainId, - ) - if (viemChain) { - return viemChain as Chain - } - - throw new Error(`Chain with id ${chainId} not found in Sequence networks or viem chains`) -} - -export class RpcRelayer implements Relayer { - public readonly kind = 'relayer' - public readonly type = 'rpc' - public readonly id: string - public readonly chainId: number - private client: GenRelayer - private fetch: Fetch - private provider: PublicClient - private readonly projectAccessKey?: string - - constructor(hostname: string, chainId: number, rpcUrl: string, fetchImpl?: Fetch, projectAccessKey?: string) { - this.id = `rpc:${hostname}` - this.chainId = chainId - this.projectAccessKey = projectAccessKey - const effectiveFetch = fetchImpl || (typeof window !== 'undefined' ? window.fetch.bind(window) : undefined) - if (!effectiveFetch) { - throw new Error('Fetch implementation is required but not available in this environment.') - } - this.fetch = effectiveFetch - this.client = new GenRelayer(hostname, this.fetch) - - // Get the chain from the chainId - const chain = getChain(chainId) - - // Create viem PublicClient with the provided RPC URL - this.provider = createPublicClient({ - chain, - transport: http(rpcUrl), - }) - } - - isAvailable(_wallet: Address.Address, chainId: number): Promise { - return Promise.resolve(this.chainId === chainId) - } - - async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: RpcFeeToken[]; paymentAddress?: Address.Address }> { - try { - const { isFeeRequired, tokens, paymentAddress } = await this.client.feeTokens() - if (isFeeRequired) { - Address.assert(paymentAddress) - return { - isFeeRequired, - tokens, - paymentAddress, - } - } - // Not required - return { - isFeeRequired, - } - } catch (e) { - console.warn('RpcRelayer.feeTokens failed:', e) - return { isFeeRequired: false } - } - } - - async feeOptions( - wallet: Address.Address, - chainId: number, - to: Address.Address, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - // IMPORTANT: - // The relayer FeeOptions endpoint simulates `eth_call(to, data)`. - // wallet-webapp-v3 requests FeeOptions with `to = wallet` and `data = Payload.encode(calls, self=wallet)`. - // This works for undeployed wallets and avoids guest-module simulation pitfalls. - const callsStruct: Payload.Calls = { type: 'call', space: 0n, nonce: 0n, calls: calls } - - const feeOptionsTo = wallet - const data = Payload.encode(callsStruct, wallet) - - try { - const result = await this.client.feeOptions( - { - wallet, - to: feeOptionsTo, - data: Hex.fromBytes(data), - }, - { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) }, - ) - - const quote = result.quote ? ({ _tag: 'FeeQuote', _quote: result.quote } as FeeQuote) : undefined - const options = result.options.map((option) => ({ - token: { - ...option.token, - contractAddress: this.mapRpcFeeTokenToAddress(option.token), - }, - to: option.to, - value: option.value, - gasLimit: option.gasLimit, - })) - - return { options, quote } - } catch (e) { - console.warn('RpcRelayer.feeOptions failed:', e) - return { options: [] } - } - } - - async sendMetaTxn( - walletAddress: Address.Address, - to: Address.Address, - data: Hex.Hex, - chainId: number, - quote?: FeeQuote, - preconditions?: TransactionPrecondition[], - ): Promise<{ opHash: Hex.Hex }> { - console.log('sendMetaTxn', walletAddress, to, data, chainId, quote, preconditions) - const rpcCall: RpcMetaTxn = { - walletAddress: walletAddress, - contract: to, - input: data, - } - - const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn( - { - call: rpcCall, - quote: quote ? JSON.stringify(quote._quote) : undefined, - preconditions: preconditions, - }, - { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) }, - ) - - if (!result.status) { - console.error('RpcRelayer.relay failed', result) - throw new Error(`Relay failed: TxnHash ${result.txnHash}`) - } - - return { opHash: Hex.fromString(result.txnHash) } - } - - async relay( - to: Address.Address, - data: Hex.Hex, - chainId: number, - quote?: FeeQuote, - preconditions?: TransactionPrecondition[], - ): Promise<{ opHash: Hex.Hex }> { - console.log('relay', to, data, chainId, quote, preconditions) - const rpcCall: RpcMetaTxn = { - walletAddress: to, - contract: to, - input: data, - } - - const result: RpcSendMetaTxnReturn = await this.client.sendMetaTxn( - { - call: rpcCall, - quote: quote ? JSON.stringify(quote._quote) : undefined, - preconditions: preconditions, - }, - { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) }, - ) - - if (!result.status) { - console.error('RpcRelayer.relay failed', result) - throw new Error(`Relay failed: TxnHash ${result.txnHash}`) - } - - return { opHash: `0x${result.txnHash}` } - } - - async status(opHash: Hex.Hex, _chainId: number): Promise { - try { - const cleanedOpHash = opHash.startsWith('0x') ? opHash.substring(2) : opHash - const result = await this.client.getMetaTxnReceipt({ metaTxID: cleanedOpHash }) - const receipt = result.receipt - - if (!receipt) { - console.warn(`RpcRelayer.status: receipt not found for opHash ${opHash}`) - return { status: 'unknown' } - } - - if (!receipt.status) { - console.warn(`RpcRelayer.status: receipt status not found for opHash ${opHash}`) - return { status: 'unknown' } - } - - switch (receipt.status as ETHTxnStatus) { - case ETHTxnStatus.QUEUED: - case ETHTxnStatus.PENDING_PRECONDITION: - case ETHTxnStatus.SENT: - return { status: 'pending' } - case ETHTxnStatus.SUCCEEDED: - return { status: 'confirmed', transactionHash: receipt.txnHash as Hex.Hex, data: result } - case ETHTxnStatus.FAILED: - case ETHTxnStatus.PARTIALLY_FAILED: - return { - status: 'failed', - transactionHash: receipt.txnHash ? (receipt.txnHash as Hex.Hex) : undefined, - reason: receipt.revertReason || 'Relayer reported failure', - data: result, - } - case ETHTxnStatus.DROPPED: - return { status: 'failed', reason: 'Transaction dropped' } - case ETHTxnStatus.UNKNOWN: - default: - return { status: 'unknown' } - } - } catch (error) { - console.error(`RpcRelayer.status failed for opHash ${opHash}:`, error) - return { status: 'failed', reason: 'Failed to fetch status' } - } - } - - async checkPrecondition(precondition: TransactionPrecondition): Promise { - const decoded = decodePrecondition(precondition) - - if (!decoded) { - return false - } - - switch (decoded.type()) { - case 'native-balance': { - const native = decoded as NativeBalancePrecondition - try { - const balance = await this.provider.getBalance({ address: native.address }) - const minWei = native.min !== undefined ? BigInt(native.min) : undefined - const maxWei = native.max !== undefined ? BigInt(native.max) : undefined - - if (minWei !== undefined && maxWei !== undefined) { - return balance >= minWei && balance <= maxWei - } - if (minWei !== undefined) { - return balance >= minWei - } - if (maxWei !== undefined) { - return balance <= maxWei - } - // If no min or max specified, this is an invalid precondition - console.warn('Native balance precondition has neither min nor max specified') - return false - } catch (error) { - console.error('Error checking native balance:', error) - return false - } - } - - case 'erc20-balance': { - const erc20 = decoded as Erc20BalancePrecondition - try { - const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address]) - const result = await this.provider.call({ - to: erc20.token.toString() as `0x${string}`, - data: data as `0x${string}`, - }) - const balance = BigInt(result.toString()) - const minWei = erc20.min !== undefined ? BigInt(erc20.min) : undefined - const maxWei = erc20.max !== undefined ? BigInt(erc20.max) : undefined - - if (minWei !== undefined && maxWei !== undefined) { - return balance >= minWei && balance <= maxWei - } - if (minWei !== undefined) { - return balance >= minWei - } - if (maxWei !== undefined) { - return balance <= maxWei - } - console.warn('ERC20 balance precondition has neither min nor max specified') - return false - } catch (error) { - console.error('Error checking ERC20 balance:', error) - return false - } - } - - case 'erc20-approval': { - const erc20 = decoded as Erc20ApprovalPrecondition - try { - const data = AbiFunction.encodeData(erc20Allowance, [erc20.address, erc20.operator]) - const result = await this.provider.call({ - to: erc20.token.toString() as `0x${string}`, - data: data as `0x${string}`, - }) - const allowance = BigInt(result.toString()) - const minAllowance = BigInt(erc20.min) - return allowance >= minAllowance - } catch (error) { - console.error('Error checking ERC20 approval:', error) - return false - } - } - - case 'erc721-ownership': { - const erc721 = decoded as Erc721OwnershipPrecondition - try { - const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId]) - const result = await this.provider.call({ - to: erc721.token, - data: data, - }) - const resultHex = result.toString() as `0x${string}` - const owner = resultHex.slice(-40) - const isOwner = owner.toLowerCase() === erc721.address.toString().slice(2).toLowerCase() - const expectedOwnership = erc721.owned !== undefined ? erc721.owned : true - return isOwner === expectedOwnership - } catch (error) { - console.error('Error checking ERC721 ownership:', error) - return false - } - } - - case 'erc721-approval': { - const erc721 = decoded as Erc721ApprovalPrecondition - try { - const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId]) - const result = await this.provider.call({ - to: erc721.token.toString() as `0x${string}`, - data: data as `0x${string}`, - }) - const resultHex = result.toString() as `0x${string}` - const approved = resultHex.slice(-40) - return approved.toLowerCase() === erc721.operator.toString().slice(2).toLowerCase() - } catch (error) { - console.error('Error checking ERC721 approval:', error) - return false - } - } - - case 'erc1155-balance': { - const erc1155 = decoded as Erc1155BalancePrecondition - try { - const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address, erc1155.tokenId]) - const result = await this.provider.call({ - to: erc1155.token.toString() as `0x${string}`, - data: data as `0x${string}`, - }) - const balance = BigInt(result.toString()) - const minWei = erc1155.min !== undefined ? BigInt(erc1155.min) : undefined - const maxWei = erc1155.max !== undefined ? BigInt(erc1155.max) : undefined - - if (minWei !== undefined && maxWei !== undefined) { - return balance >= minWei && balance <= maxWei - } - if (minWei !== undefined) { - return balance >= minWei - } - if (maxWei !== undefined) { - return balance <= maxWei - } - console.warn('ERC1155 balance precondition has neither min nor max specified') - return false - } catch (error) { - console.error('Error checking ERC1155 balance:', error) - return false - } - } - - case 'erc1155-approval': { - const erc1155 = decoded as Erc1155ApprovalPrecondition - try { - const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [erc1155.address, erc1155.operator]) - const result = await this.provider.call({ - to: erc1155.token, - data: data, - }) - return BigInt(result.toString()) === 1n - } catch (error) { - console.error('Error checking ERC1155 approval:', error) - return false - } - } - - default: - return false - } - } - - private mapRpcFeeTokenToAddress(rpcToken: RpcFeeToken): Address.Address { - if (rpcToken.type === FeeTokenType.ERC20_TOKEN && rpcToken.contractAddress) { - return Address.from(rpcToken.contractAddress) - } - return Constants.ZeroAddress // Default to zero address for native token or unsupported types - } -} diff --git a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts deleted file mode 100644 index 8a9ff4a7b4..0000000000 --- a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts +++ /dev/null @@ -1,2500 +0,0 @@ -/* eslint-disable */ -// sequence-relayer v0.4.1 0a2503bc893179ba968b0015d7580aabf6a88dd4 -// -- -// Code generated by Webrpc-gen@v0.32.2 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts -compat - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.4.1' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '0a2503bc893179ba968b0015d7580aabf6a88dd4' - -// -// Client interface -// - -export interface RelayerClient { - ping(headers?: object, signal?: AbortSignal): Promise - - version(headers?: object, signal?: AbortSignal): Promise - - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - - getSequenceContext(headers?: object, signal?: AbortSignal): Promise - - getChainID(headers?: object, signal?: AbortSignal): Promise - - /** - * - * Transactions - * - * TODO (future): rename this to just, 'SendTransaction(txn: MetaTransaction)' or 'SendTransaction(txn: SignedTransaction)', or something.. - * Project ID is only used by service and admin calls. Other clients must have projectID passed via the context - * TODO: rename return txnHash: string to metaTxnID: string - */ - sendMetaTxn(req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise - - getMetaTxnNonce(req: GetMetaTxnNonceArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * TODO: one day, make GetMetaTxnReceipt respond immediately with receipt or not - * and add WaitTransactionReceipt method, which will block and wait, similar to how GetMetaTxnReceipt - * is implemented now. - * For backwards compat, we can leave the current GetMetaTxnReceipt how it is, an deprecate it, and introduce - * new, GetTransactionReceipt and WaitTransactionReceipt methods - * we can also accept metaTxnId and txnHash .. so can take either or.. I wonder if ERC-4337 has any convention on this? - */ - getMetaTxnReceipt( - req: GetMetaTxnReceiptArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - simulate(req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise - - simulateV3(req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise - - /** - * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date - */ - updateMetaTxnGasLimits( - req: UpdateMetaTxnGasLimitsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - feeTokens(headers?: object, signal?: AbortSignal): Promise - - feeOptions(req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * Bridge gas endpoints for S2S calls - * Used for bridge fees (e.g., LayerZero messaging fees) that require msg.value to be fronted at runtime. - * bridgeGas will be included in fee calculation so the relayer gets reimbursed. - */ - sendMetaTxnWithBridgeGas( - req: SendMetaTxnWithBridgeGasArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - feeOptionsWithBridgeGas( - req: FeeOptionsWithBridgeGasArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date - */ - getMetaTxnNetworkFeeOptions( - req: GetMetaTxnNetworkFeeOptionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * - * Sender administration - * - */ - startSender(req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise - - stopSender(req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise - - repairSender(req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise - - getMetaTransactions( - req: GetMetaTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - getTransactionCost( - req: GetTransactionCostArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Legacy Gas Tank - */ - getGasTank(req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise - - addGasTank(req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise - - updateGasTank(req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * Legacy Gas Adjustment - */ - nextGasTankBalanceAdjustmentNonce( - req: NextGasTankBalanceAdjustmentNonceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - adjustGasTankBalance( - req: AdjustGasTankBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - getGasTankBalanceAdjustment( - req: GetGasTankBalanceAdjustmentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - listGasTankBalanceAdjustments( - req: ListGasTankBalanceAdjustmentsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Project-Level Gas Sponsorship - */ - listGasSponsors(req: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal): Promise - - getGasSponsor(req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - - addGasSponsor(req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - - updateGasSponsor(req: UpdateGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - - removeGasSponsor(req: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - - /** - * Ecosystem-level Gas Sponsorship - */ - listEcosystemGasSponsors( - req: ListEcosystemGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - getEcosystemGasSponsor( - req: GetEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - addEcosystemGasSponsor( - req: AddEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - updateEcosystemGasSponsor( - req: UpdateEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - removeEcosystemGasSponsor( - req: RemoveEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Gas Sponsor Lookup - */ - addressGasSponsors( - req: AddressGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - /** - * Project Balance - */ - getProjectBalance( - req: GetProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - - adjustProjectBalance( - req: AdjustProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -// -// Schema types -// - -export enum RepairOperation { - SKIP = 'SKIP', - REQUEUE = 'REQUEUE', - DROP = 'DROP', -} - -export enum ETHTxnStatus { - UNKNOWN = 'UNKNOWN', - DROPPED = 'DROPPED', - QUEUED = 'QUEUED', - SENT = 'SENT', - SUCCEEDED = 'SUCCEEDED', - PARTIALLY_FAILED = 'PARTIALLY_FAILED', - FAILED = 'FAILED', - PENDING_PRECONDITION = 'PENDING_PRECONDITION', - MINED = 'MINED', -} - -export enum TransferType { - SEND = 'SEND', - RECEIVE = 'RECEIVE', - BRIDGE_DEPOSIT = 'BRIDGE_DEPOSIT', - BRIDGE_WITHDRAW = 'BRIDGE_WITHDRAW', - BURN = 'BURN', - UNKNOWN = 'UNKNOWN', -} - -export enum SimulateStatus { - SKIPPED = 'SKIPPED', - SUCCEEDED = 'SUCCEEDED', - FAILED = 'FAILED', - ABORTED = 'ABORTED', - REVERTED = 'REVERTED', - NOT_ENOUGH_GAS = 'NOT_ENOUGH_GAS', -} - -export enum FeeTokenType { - UNKNOWN = 'UNKNOWN', - ERC20_TOKEN = 'ERC20_TOKEN', - ERC1155_TOKEN = 'ERC1155_TOKEN', -} - -export enum Order { - DESC = 'DESC', - ASC = 'ASC', -} - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - useEIP1559: boolean - senders: Array - checks: RuntimeChecks -} - -export interface SenderStatus { - index: number - address: string - etherBalance: number - enabled: boolean - active: boolean - nonce?: NonceStatus - current?: CurrentStatus -} - -export interface NonceStatus { - chain: number - mempool: number -} - -export interface CurrentStatus { - transaction: string - first: TransactionStatus - latest?: TransactionStatus -} - -export interface TransactionStatus { - transaction: string - gas: number - gasPrice: string - priorityFee: string - time: string - age: string - error?: string -} - -export interface RuntimeChecks {} - -export interface SequenceContext { - factory: string - mainModule: string - mainModuleUpgradable: string - guestModule: string - utils: string -} - -export interface GasTank { - id: number - chainId: number - name: string - currentBalance: number - unlimited: boolean - feeMarkupFactor: number - updatedAt: string - createdAt: string -} - -export interface GasTankBalanceAdjustment { - gasTankId: number - nonce: number - amount: number - totalBalance: number - balanceTimestamp: string - createdAt: string -} - -export interface GasSponsor { - id: number - gasTankId: number - projectId: number - ecosystemId: number - chainId: number - address: string - name: string - active: boolean - updatedAt: string - createdAt: string - deletedAt: string -} - -export interface GasSponsorUsage { - name: string - id: number - totalGasUsed: number - totalTxnFees: number - totalTxnFeesUsd: number - avgGasPrice: number - totalTxns: number - startTime: string - endTime: string -} - -export interface MetaTxn { - walletAddress: string - contract: string - input: string -} - -export interface MetaTxnLog { - id: number - chainId: number - projectId: number - txnHash: string - txnNonce: string - metaTxnID?: string - txnStatus: ETHTxnStatus - txnRevertReason: string - requeues: number - queuedAt: string - sentAt: string - minedAt: string - target: string - input: string - bridgeGas?: string - txnArgs: { [key: string]: any } - txnReceipt?: { [key: string]: any } - walletAddress: string - metaTxnNonce: string - gasLimit: number - gasPrice: string - gasUsed: number - gasEstimated: number - gasFeeMarkup?: number - usdRate: string - creditsUsed: number - cost: string - isWhitelisted: boolean - gasSponsor?: number - gasTank?: number - updatedAt: string - createdAt: string -} - -export interface MetaTxnReceipt { - id: string - status: string - revertReason?: string - index: number - logs: Array - receipts: Array - blockNumber: string - txnHash: string - txnReceipt: string -} - -export interface MetaTxnReceiptLog { - address: string - topics: Array - data: string -} - -export interface Transactions { - chainID: string - transactions: Array - preconditions?: Array -} - -export interface Transaction { - delegateCall: boolean - revertOnError: boolean - gasLimit: string - target: string - value: string - data: string -} - -export interface TransactionPrecondition { - type: string - chainId: number - ownerAddress: string - tokenAddress: string - minAmount: bigint -} - -export interface TxnLogUser { - username: string -} - -export interface TxnLogTransfer { - transferType: TransferType - contractAddress: string - from: string - to: string - ids: Array - amounts: Array -} - -export interface SimulateResult { - executed: boolean - succeeded: boolean - result?: string - reason?: string - gasUsed: number - gasLimit: number -} - -export interface SimulateV3Result { - status: SimulateStatus - result?: string - error?: string - gasUsed: number - gasLimit: number -} - -export interface FeeOption { - token: FeeToken - to: string - value: string - gasLimit: number -} - -export interface FeeToken { - chainId: number - name: string - symbol: string - type: FeeTokenType - decimals?: number - logoURL: string - contractAddress?: string - tokenID?: string -} - -export interface Page { - pageSize: number - page: number - more: boolean - column: string - sort: Array -} - -export interface Sort { - column: string - order: Order -} - -export interface PingArgs {} - -export interface PingReturn { - status: boolean -} - -export interface VersionArgs {} - -export interface VersionReturn { - version: Version -} - -export interface RuntimeStatusArgs {} - -export interface RuntimeStatusReturn { - status: RuntimeStatus -} - -export interface GetSequenceContextArgs {} - -export interface GetSequenceContextReturn { - data: SequenceContext -} - -export interface GetChainIDArgs {} - -export interface GetChainIDReturn { - chainID: number -} - -export interface SendMetaTxnArgs { - call: MetaTxn - quote?: string - projectID?: number - preconditions?: Array -} - -export interface SendMetaTxnReturn { - status: boolean - txnHash: string -} - -export interface GetMetaTxnNonceArgs { - walletContractAddress: string - space?: string -} - -export interface GetMetaTxnNonceReturn { - nonce: string -} - -export interface GetMetaTxnReceiptArgs { - metaTxID: string -} - -export interface GetMetaTxnReceiptReturn { - receipt: MetaTxnReceipt -} - -export interface SimulateArgs { - wallet: string - transactions: string -} - -export interface SimulateReturn { - results: Array -} - -export interface SimulateV3Args { - wallet: string - calls: string -} - -export interface SimulateV3Return { - results: Array -} - -export interface UpdateMetaTxnGasLimitsArgs { - walletAddress: string - walletConfig: any - payload: string -} - -export interface UpdateMetaTxnGasLimitsReturn { - payload: string -} - -export interface FeeTokensArgs {} - -export interface FeeTokensReturn { - isFeeRequired: boolean - tokens: Array - paymentAddress: string -} - -export interface FeeOptionsArgs { - wallet: string - to: string - data: string - simulate?: boolean -} - -export interface FeeOptionsReturn { - options: Array - sponsored: boolean - quote?: string -} - -export interface SendMetaTxnWithBridgeGasArgs { - call: MetaTxn - quote?: string - projectID?: number - bridgeGas: string - preconditions?: Array -} - -export interface SendMetaTxnWithBridgeGasReturn { - status: boolean - txnHash: string -} - -export interface FeeOptionsWithBridgeGasArgs { - wallet: string - to: string - data: string - simulate?: boolean - bridgeGas: string -} - -export interface FeeOptionsWithBridgeGasReturn { - options: Array - sponsored: boolean - quote?: string -} - -export interface GetMetaTxnNetworkFeeOptionsArgs { - walletConfig: any - payload: string -} - -export interface GetMetaTxnNetworkFeeOptionsReturn { - options: Array -} - -export interface StartSenderArgs { - sender: number -} - -export interface StartSenderReturn {} - -export interface StopSenderArgs { - sender: number -} - -export interface StopSenderReturn {} - -export interface RepairSenderArgs { - sender: number - nonce: number - operation: RepairOperation -} - -export interface RepairSenderReturn {} - -export interface GetMetaTransactionsArgs { - projectId: number - page?: Page -} - -export interface GetMetaTransactionsReturn { - page: Page - transactions: Array -} - -export interface GetTransactionCostArgs { - projectId: number - from: string - to: string -} - -export interface GetTransactionCostReturn { - cost: number -} - -export interface GetGasTankArgs { - id: number -} - -export interface GetGasTankReturn { - gasTank: GasTank -} - -export interface AddGasTankArgs { - name: string - feeMarkupFactor: number - unlimited?: boolean -} - -export interface AddGasTankReturn { - status: boolean - gasTank: GasTank -} - -export interface UpdateGasTankArgs { - id: number - name?: string - feeMarkupFactor?: number - unlimited?: boolean -} - -export interface UpdateGasTankReturn { - status: boolean - gasTank: GasTank -} - -export interface NextGasTankBalanceAdjustmentNonceArgs { - id: number -} - -export interface NextGasTankBalanceAdjustmentNonceReturn { - nonce: number -} - -export interface AdjustGasTankBalanceArgs { - id: number - nonce: number - amount: number -} - -export interface AdjustGasTankBalanceReturn { - status: boolean - adjustment: GasTankBalanceAdjustment -} - -export interface GetGasTankBalanceAdjustmentArgs { - id: number - nonce: number -} - -export interface GetGasTankBalanceAdjustmentReturn { - adjustment: GasTankBalanceAdjustment -} - -export interface ListGasTankBalanceAdjustmentsArgs { - id: number - page?: Page -} - -export interface ListGasTankBalanceAdjustmentsReturn { - page: Page - adjustments: Array -} - -export interface ListGasSponsorsArgs { - projectId: number - page?: Page -} - -export interface ListGasSponsorsReturn { - page: Page - gasSponsors: Array -} - -export interface GetGasSponsorArgs { - projectId: number - id: number -} - -export interface GetGasSponsorReturn { - gasSponsor: GasSponsor -} - -export interface AddGasSponsorArgs { - projectId: number - address: string - name?: string - active?: boolean -} - -export interface AddGasSponsorReturn { - status: boolean - gasSponsor: GasSponsor -} - -export interface UpdateGasSponsorArgs { - projectId: number - id: number - name?: string - active?: boolean -} - -export interface UpdateGasSponsorReturn { - status: boolean - gasSponsor: GasSponsor -} - -export interface RemoveGasSponsorArgs { - projectId: number - id: number -} - -export interface RemoveGasSponsorReturn { - status: boolean -} - -export interface ListEcosystemGasSponsorsArgs { - ecosystemId: number - page?: Page -} - -export interface ListEcosystemGasSponsorsReturn { - page: Page - gasSponsors: Array -} - -export interface GetEcosystemGasSponsorArgs { - ecosystemId: number - id: number -} - -export interface GetEcosystemGasSponsorReturn { - gasSponsor: GasSponsor -} - -export interface AddEcosystemGasSponsorArgs { - ecosystemId: number - address: string - name?: string - active?: boolean -} - -export interface AddEcosystemGasSponsorReturn { - status: boolean - gasSponsor: GasSponsor -} - -export interface UpdateEcosystemGasSponsorArgs { - ecosystemId: number - id: number - name?: string - active?: boolean -} - -export interface UpdateEcosystemGasSponsorReturn { - status: boolean - gasSponsor: GasSponsor -} - -export interface RemoveEcosystemGasSponsorArgs { - ecosystemId: number - id: number -} - -export interface RemoveEcosystemGasSponsorReturn { - status: boolean -} - -export interface AddressGasSponsorsArgs { - address: string - page?: Page -} - -export interface AddressGasSponsorsReturn { - page: Page - gasSponsors: Array -} - -export interface GetProjectBalanceArgs { - projectId: number -} - -export interface GetProjectBalanceReturn { - balance: number -} - -export interface AdjustProjectBalanceArgs { - projectId: number - amount: number - identifier: string -} - -export interface AdjustProjectBalanceReturn { - balance: number -} - -// -// Client -// - -export class Relayer implements RelayerClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Relayer/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - ping: () => ['Relayer', 'ping'] as const, - version: () => ['Relayer', 'version'] as const, - runtimeStatus: () => ['Relayer', 'runtimeStatus'] as const, - getSequenceContext: () => ['Relayer', 'getSequenceContext'] as const, - getChainID: () => ['Relayer', 'getChainID'] as const, - sendMetaTxn: (req: SendMetaTxnArgs) => ['Relayer', 'sendMetaTxn', req] as const, - getMetaTxnNonce: (req: GetMetaTxnNonceArgs) => ['Relayer', 'getMetaTxnNonce', req] as const, - getMetaTxnReceipt: (req: GetMetaTxnReceiptArgs) => ['Relayer', 'getMetaTxnReceipt', req] as const, - simulate: (req: SimulateArgs) => ['Relayer', 'simulate', req] as const, - simulateV3: (req: SimulateV3Args) => ['Relayer', 'simulateV3', req] as const, - updateMetaTxnGasLimits: (req: UpdateMetaTxnGasLimitsArgs) => ['Relayer', 'updateMetaTxnGasLimits', req] as const, - feeTokens: () => ['Relayer', 'feeTokens'] as const, - feeOptions: (req: FeeOptionsArgs) => ['Relayer', 'feeOptions', req] as const, - sendMetaTxnWithBridgeGas: (req: SendMetaTxnWithBridgeGasArgs) => - ['Relayer', 'sendMetaTxnWithBridgeGas', req] as const, - feeOptionsWithBridgeGas: (req: FeeOptionsWithBridgeGasArgs) => ['Relayer', 'feeOptionsWithBridgeGas', req] as const, - getMetaTxnNetworkFeeOptions: (req: GetMetaTxnNetworkFeeOptionsArgs) => - ['Relayer', 'getMetaTxnNetworkFeeOptions', req] as const, - startSender: (req: StartSenderArgs) => ['Relayer', 'startSender', req] as const, - stopSender: (req: StopSenderArgs) => ['Relayer', 'stopSender', req] as const, - repairSender: (req: RepairSenderArgs) => ['Relayer', 'repairSender', req] as const, - getMetaTransactions: (req: GetMetaTransactionsArgs) => ['Relayer', 'getMetaTransactions', req] as const, - getTransactionCost: (req: GetTransactionCostArgs) => ['Relayer', 'getTransactionCost', req] as const, - getGasTank: (req: GetGasTankArgs) => ['Relayer', 'getGasTank', req] as const, - addGasTank: (req: AddGasTankArgs) => ['Relayer', 'addGasTank', req] as const, - updateGasTank: (req: UpdateGasTankArgs) => ['Relayer', 'updateGasTank', req] as const, - nextGasTankBalanceAdjustmentNonce: (req: NextGasTankBalanceAdjustmentNonceArgs) => - ['Relayer', 'nextGasTankBalanceAdjustmentNonce', req] as const, - adjustGasTankBalance: (req: AdjustGasTankBalanceArgs) => ['Relayer', 'adjustGasTankBalance', req] as const, - getGasTankBalanceAdjustment: (req: GetGasTankBalanceAdjustmentArgs) => - ['Relayer', 'getGasTankBalanceAdjustment', req] as const, - listGasTankBalanceAdjustments: (req: ListGasTankBalanceAdjustmentsArgs) => - ['Relayer', 'listGasTankBalanceAdjustments', req] as const, - listGasSponsors: (req: ListGasSponsorsArgs) => ['Relayer', 'listGasSponsors', req] as const, - getGasSponsor: (req: GetGasSponsorArgs) => ['Relayer', 'getGasSponsor', req] as const, - addGasSponsor: (req: AddGasSponsorArgs) => ['Relayer', 'addGasSponsor', req] as const, - updateGasSponsor: (req: UpdateGasSponsorArgs) => ['Relayer', 'updateGasSponsor', req] as const, - removeGasSponsor: (req: RemoveGasSponsorArgs) => ['Relayer', 'removeGasSponsor', req] as const, - listEcosystemGasSponsors: (req: ListEcosystemGasSponsorsArgs) => - ['Relayer', 'listEcosystemGasSponsors', req] as const, - getEcosystemGasSponsor: (req: GetEcosystemGasSponsorArgs) => ['Relayer', 'getEcosystemGasSponsor', req] as const, - addEcosystemGasSponsor: (req: AddEcosystemGasSponsorArgs) => ['Relayer', 'addEcosystemGasSponsor', req] as const, - updateEcosystemGasSponsor: (req: UpdateEcosystemGasSponsorArgs) => - ['Relayer', 'updateEcosystemGasSponsor', req] as const, - removeEcosystemGasSponsor: (req: RemoveEcosystemGasSponsorArgs) => - ['Relayer', 'removeEcosystemGasSponsor', req] as const, - addressGasSponsors: (req: AddressGasSponsorsArgs) => ['Relayer', 'addressGasSponsors', req] as const, - getProjectBalance: (req: GetProjectBalanceArgs) => ['Relayer', 'getProjectBalance', req] as const, - adjustProjectBalance: (req: AdjustProjectBalanceArgs) => ['Relayer', 'adjustProjectBalance', req] as const, - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PingReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'VersionReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RuntimeStatusReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetSequenceContext'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetSequenceContextReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getChainID = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetChainID'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetChainIDReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - sendMetaTxn = (req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SendMetaTxn'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SendMetaTxnReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMetaTxnNonce = ( - req: GetMetaTxnNonceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMetaTxnNonce'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMetaTxnNonceReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMetaTxnReceipt = ( - req: GetMetaTxnReceiptArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMetaTxnReceipt'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMetaTxnReceiptReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - simulate = (req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Simulate'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SimulateReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - simulateV3 = (req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SimulateV3'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SimulateV3Return') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateMetaTxnGasLimits = ( - req: UpdateMetaTxnGasLimitsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateMetaTxnGasLimits'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateMetaTxnGasLimitsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - feeTokens = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('FeeTokens'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FeeTokensReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - feeOptions = (req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('FeeOptions'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FeeOptionsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - sendMetaTxnWithBridgeGas = ( - req: SendMetaTxnWithBridgeGasArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SendMetaTxnWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'SendMetaTxnWithBridgeGasReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - feeOptionsWithBridgeGas = ( - req: FeeOptionsWithBridgeGasArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FeeOptionsWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'FeeOptionsWithBridgeGasReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMetaTxnNetworkFeeOptions = ( - req: GetMetaTxnNetworkFeeOptionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetMetaTxnNetworkFeeOptions'), - createHttpRequest(JsonEncode(req), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMetaTxnNetworkFeeOptionsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - startSender = (req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('StartSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'StartSenderReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - stopSender = (req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('StopSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'StopSenderReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - repairSender = (req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RepairSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RepairSenderReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getMetaTransactions = ( - req: GetMetaTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMetaTransactions'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetMetaTransactionsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getTransactionCost = ( - req: GetTransactionCostArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTransactionCost'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetTransactionCostReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getGasTank = (req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetGasTankReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addGasTank = (req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AddGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddGasTankReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateGasTank = (req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('UpdateGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateGasTankReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - nextGasTankBalanceAdjustmentNonce = ( - req: NextGasTankBalanceAdjustmentNonceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('NextGasTankBalanceAdjustmentNonce'), - createHttpRequest(JsonEncode(req), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'NextGasTankBalanceAdjustmentNonceReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - adjustGasTankBalance = ( - req: AdjustGasTankBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AdjustGasTankBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AdjustGasTankBalanceReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getGasTankBalanceAdjustment = ( - req: GetGasTankBalanceAdjustmentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetGasTankBalanceAdjustment'), - createHttpRequest(JsonEncode(req), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetGasTankBalanceAdjustmentReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listGasTankBalanceAdjustments = ( - req: ListGasTankBalanceAdjustmentsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListGasTankBalanceAdjustments'), - createHttpRequest(JsonEncode(req), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListGasTankBalanceAdjustmentsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listGasSponsors = ( - req: ListGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListGasSponsorsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getGasSponsor = (req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addGasSponsor = (req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AddGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateGasSponsor = ( - req: UpdateGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeGasSponsor = ( - req: RemoveGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listEcosystemGasSponsors = ( - req: ListEcosystemGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListEcosystemGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListEcosystemGasSponsorsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getEcosystemGasSponsor = ( - req: GetEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetEcosystemGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addEcosystemGasSponsor = ( - req: AddEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddEcosystemGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - updateEcosystemGasSponsor = ( - req: UpdateEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'UpdateEcosystemGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - removeEcosystemGasSponsor = ( - req: RemoveEcosystemGasSponsorArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'RemoveEcosystemGasSponsorReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - addressGasSponsors = ( - req: AddressGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddressGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AddressGasSponsorsReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getProjectBalance = ( - req: GetProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetProjectBalanceReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - adjustProjectBalance = ( - req: AdjustProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AdjustProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'AdjustProjectBalanceReturn') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { - ...headers, - 'Content-Type': 'application/json', - [WebrpcHeader]: WebrpcHeaderValue, - } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -// -// BigInt helpers -// - -const BIG_INT_FIELDS: { [typ: string]: (string | [string, string])[] } = { - SendMetaTxnArgs: [['preconditions', 'TransactionPrecondition[]']], - SendMetaTxnWithBridgeGasArgs: [['preconditions', 'TransactionPrecondition[]']], - TransactionPrecondition: ['minAmount'], - Transactions: [['preconditions', 'TransactionPrecondition[]']], -} - -// Decode in-place: mutate object graph; throw if expected numeric string is invalid. -function decodeType(typ: string, obj: any): any { - if (obj == null || typeof obj !== 'object') return obj - const descs = BIG_INT_FIELDS[typ] || [] - if (!descs.length) return obj - for (const d of descs) { - if (Array.isArray(d)) { - const [fieldName, nestedType] = d - if (fieldName.endsWith('[]')) { - const base = fieldName.slice(0, -2) - const arr = obj[base] - if (Array.isArray(arr)) { - for (let i = 0; i < arr.length; i++) arr[i] = decodeType(nestedType, arr[i]) - } - } else if (obj[fieldName]) { - // Handle nestedType that might be an array type like 'Message[]' - if (nestedType.endsWith('[]')) { - const baseType = nestedType.slice(0, -2) - const arr = obj[fieldName] - if (Array.isArray(arr)) { - for (let i = 0; i < arr.length; i++) arr[i] = decodeType(baseType, arr[i]) - } - } else { - obj[fieldName] = decodeType(nestedType, obj[fieldName]) - } - } - continue - } - if (d.endsWith('[]')) { - const base = d.slice(0, -2) - const arr = obj[base] - if (Array.isArray(arr)) { - for (let i = 0; i < arr.length; i++) { - const v = arr[i] - if (typeof v === 'string') { - try { - arr[i] = BigInt(v) - } catch (e) { - throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${base}[${i}]: ${v}` }) - } - } - } - } - continue - } - const v = obj[d] - if (typeof v === 'string') { - try { - obj[d] = BigInt(v) - } catch (e) { - throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${d}: ${v}` }) - } - } - } - return obj -} - -// Encode object to JSON with BigInts converted to decimal strings. -export const JsonEncode = (obj: T): string => { - return JSON.stringify(obj, (key, value) => (typeof value === 'bigint' ? value.toString() : value)) -} - -// Decode data (JSON string or already-parsed object) and convert declared BigInt string fields back to BigInt. -export const JsonDecode = (data: string | any, typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return decodeType(typ, parsed) as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class AbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Aborted' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request aborted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Rate-limited. Please slow down.` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 1008 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class AccessKeyNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyNotFound' - this.code = typeof error.code === 'number' ? error.code : 1101 - this.message = error.message || `Access key not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) - } -} - -export class AccessKeyMismatchError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AccessKeyMismatch' - this.code = typeof error.code === 'number' ? error.code : 1102 - this.message = error.message || `Access key mismatch` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) - } -} - -export class InvalidOriginError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidOrigin' - this.code = typeof error.code === 'number' ? error.code : 1103 - this.message = error.message || `Invalid origin for Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidOriginError.prototype) - } -} - -export class InvalidServiceError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidService' - this.code = typeof error.code === 'number' ? error.code : 1104 - this.message = error.message || `Service not enabled for Access key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidServiceError.prototype) - } -} - -export class UnauthorizedUserError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnauthorizedUser' - this.code = typeof error.code === 'number' ? error.code : 1105 - this.message = error.message || `Unauthorized user` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) - } -} - -export class InvalidChainError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidChain' - this.code = typeof error.code === 'number' ? error.code : 1106 - this.message = error.message || `Network not enabled for Access key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidChainError.prototype) - } -} - -export class QuotaExceededError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaExceeded' - this.code = typeof error.code === 'number' ? error.code : 1200 - this.message = error.message || `Quota request exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaExceededError.prototype) - } -} - -export class QuotaRateLimitError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QuotaRateLimit' - this.code = typeof error.code === 'number' ? error.code : 1201 - this.message = error.message || `Quota rate limit exceeded` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QuotaRateLimitError.prototype) - } -} - -export class NoDefaultKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NoDefaultKey' - this.code = typeof error.code === 'number' ? error.code : 1300 - this.message = error.message || `No default access key found` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) - } -} - -export class MaxAccessKeysError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MaxAccessKeys' - this.code = typeof error.code === 'number' ? error.code : 1301 - this.message = error.message || `Access keys limit reached` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) - } -} - -export class AtLeastOneKeyError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'AtLeastOneKey' - this.code = typeof error.code === 'number' ? error.code : 1302 - this.message = error.message || `You need at least one Access Key` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) - } -} - -export class TimeoutError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Timeout' - this.code = typeof error.code === 'number' ? error.code : 1900 - this.message = error.message || `Request timed out` - this.status = typeof error.status === 'number' ? error.status : 408 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, TimeoutError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2001 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unavailable' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Unavailable resource` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class InsufficientFeeError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InsufficientFee' - this.code = typeof error.code === 'number' ? error.code : 3004 - this.message = error.message || `Insufficient fee` - this.status = typeof error.status === 'number' ? error.status : 402 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InsufficientFeeError.prototype) - } -} - -export class NotEnoughBalanceError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotEnoughBalance' - this.code = typeof error.code === 'number' ? error.code : 3005 - this.message = error.message || `Not enough balance` - this.status = typeof error.status === 'number' ? error.status : 402 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotEnoughBalanceError.prototype) - } -} - -export class SimulationFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SimulationFailed' - this.code = typeof error.code === 'number' ? error.code : 3006 - this.message = error.message || `Simulation failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SimulationFailedError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - AccessKeyNotFound = 'AccessKeyNotFound', - AccessKeyMismatch = 'AccessKeyMismatch', - InvalidOrigin = 'InvalidOrigin', - InvalidService = 'InvalidService', - UnauthorizedUser = 'UnauthorizedUser', - InvalidChain = 'InvalidChain', - QuotaExceeded = 'QuotaExceeded', - QuotaRateLimit = 'QuotaRateLimit', - NoDefaultKey = 'NoDefaultKey', - MaxAccessKeys = 'MaxAccessKeys', - AtLeastOneKey = 'AtLeastOneKey', - Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', - Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - NotFound = 'NotFound', - InsufficientFee = 'InsufficientFee', - NotEnoughBalance = 'NotEnoughBalance', - SimulationFailed = 'SimulationFailed', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Aborted = 1005, - Geoblocked = 1006, - RateLimited = 1007, - ProjectNotFound = 1008, - AccessKeyNotFound = 1101, - AccessKeyMismatch = 1102, - InvalidOrigin = 1103, - InvalidService = 1104, - UnauthorizedUser = 1105, - InvalidChain = 1106, - QuotaExceeded = 1200, - QuotaRateLimit = 1201, - NoDefaultKey = 1300, - MaxAccessKeys = 1301, - AtLeastOneKey = 1302, - Timeout = 1900, - InvalidArgument = 2001, - Unavailable = 2002, - QueryFailed = 2003, - NotFound = 3000, - InsufficientFee = 3004, - NotEnoughBalance = 3005, - SimulationFailed = 3006, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1008]: ProjectNotFoundError, - [1101]: AccessKeyNotFoundError, - [1102]: AccessKeyMismatchError, - [1103]: InvalidOriginError, - [1104]: InvalidServiceError, - [1105]: UnauthorizedUserError, - [1106]: InvalidChainError, - [1200]: QuotaExceededError, - [1201]: QuotaRateLimitError, - [1300]: NoDefaultKeyError, - [1301]: MaxAccessKeysError, - [1302]: AtLeastOneKeyError, - [1900]: TimeoutError, - [2001]: InvalidArgumentError, - [2002]: UnavailableError, - [2003]: QueryFailedError, - [3000]: NotFoundError, - [3004]: InsufficientFeeError, - [3005]: NotEnoughBalanceError, - [3006]: SimulationFailedError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.32.2;gen-typescript@v0.23.1;sequence-relayer@v0.4.1' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/relayer/src/relayer/standard/abi.ts b/packages/services/relayer/src/relayer/standard/abi.ts deleted file mode 100644 index ccd965a818..0000000000 --- a/packages/services/relayer/src/relayer/standard/abi.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AbiFunction } from 'ox' - -// ERC20 ABI functions -export const erc20BalanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)') -export const erc20Allowance = AbiFunction.from('function allowance(address,address) returns (uint256)') - -// ERC721 ABI functions -export const erc721OwnerOf = AbiFunction.from('function ownerOf(uint256) returns (address)') -export const erc721GetApproved = AbiFunction.from('function getApproved(uint256) returns (address)') - -// ERC1155 ABI functions -export const erc1155BalanceOf = AbiFunction.from('function balanceOf(address,uint256) returns (uint256)') -export const erc1155IsApprovedForAll = AbiFunction.from('function isApprovedForAll(address,address) returns (bool)') diff --git a/packages/services/relayer/src/relayer/standard/eip6963.ts b/packages/services/relayer/src/relayer/standard/eip6963.ts deleted file mode 100644 index a290b2c6f9..0000000000 --- a/packages/services/relayer/src/relayer/standard/eip6963.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createStore, EIP6963ProviderInfo, EIP6963ProviderDetail } from 'mipd' -import { EIP1193ProviderAdapter, LocalRelayer } from './local.js' -import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { Address, Hex } from 'ox' -import { Payload } from '@0xsequence/wallet-primitives' -import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js' - -export class EIP6963Relayer implements Relayer { - public readonly kind = 'relayer' - public readonly type = 'eip6963' - public readonly id: string - public readonly info: EIP6963ProviderInfo - private readonly relayer: LocalRelayer - - constructor(detail: EIP6963ProviderDetail) { - this.info = detail.info - this.id = detail.info.uuid - - this.relayer = new LocalRelayer(new EIP1193ProviderAdapter(detail.provider)) - } - - isAvailable(wallet: Address.Address, chainId: number): Promise { - return this.relayer.isAvailable(wallet, chainId) - } - - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { - return this.relayer.feeTokens() - } - - feeOptions( - wallet: Address.Address, - chainId: number, - to: Address.Address, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return this.relayer.feeOptions(wallet, chainId, to, calls) - } - - async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> { - return this.relayer.relay(to, data, chainId) - } - - status(opHash: Hex.Hex, chainId: number): Promise { - return this.relayer.status(opHash, chainId) - } - - async checkPrecondition(precondition: TransactionPrecondition): Promise { - return this.relayer.checkPrecondition(precondition) - } -} - -// Global store instance -let store: ReturnType | undefined - -export function getEIP6963Store() { - if (!store) { - store = createStore() - } - return store -} - -const relayers: Map = new Map() - -export function getRelayers(): EIP6963Relayer[] { - const store = getEIP6963Store() - const providers = store.getProviders() - - for (const detail of providers) { - if (!relayers.has(detail.info.uuid)) { - relayers.set(detail.info.uuid, new EIP6963Relayer(detail)) - } - } - - return Array.from(relayers.values()) -} diff --git a/packages/services/relayer/src/relayer/standard/index.ts b/packages/services/relayer/src/relayer/standard/index.ts deleted file mode 100644 index d04527fa03..0000000000 --- a/packages/services/relayer/src/relayer/standard/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './local.js' -export * from './pk-relayer.js' -export * from './sequence.js' -export * as EIP6963 from './eip6963.js' diff --git a/packages/services/relayer/src/relayer/standard/local.ts b/packages/services/relayer/src/relayer/standard/local.ts deleted file mode 100644 index 4135af3b30..0000000000 --- a/packages/services/relayer/src/relayer/standard/local.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { Payload } from '@0xsequence/wallet-primitives' -import { EIP1193Provider } from 'mipd' -import { AbiFunction, Address, Hex, TransactionReceipt } from 'ox' -import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js' -import { - decodePrecondition, - Erc1155ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc20ApprovalPrecondition, - Erc20BalancePrecondition, - Erc721ApprovalPrecondition, - Erc721OwnershipPrecondition, - NativeBalancePrecondition, -} from '../../preconditions/index.js' -import { - erc20BalanceOf, - erc20Allowance, - erc721OwnerOf, - erc721GetApproved, - erc1155BalanceOf, - erc1155IsApprovedForAll, -} from './abi.js' - -type GenericProviderTransactionReceipt = 'success' | 'failed' | 'unknown' - -export interface GenericProvider { - sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number): Promise - getBalance(address: Address.Address): Promise - call(args: { to: Address.Address; data: Hex.Hex }): Promise - getTransactionReceipt(txHash: Hex.Hex, chainId: number): Promise -} - -export class LocalRelayer implements Relayer { - public readonly kind = 'relayer' - public readonly type = 'local' - public readonly id = 'local' - - constructor(public readonly provider: GenericProvider) {} - - isAvailable(_wallet: Address.Address, _chainId: number): Promise { - return Promise.resolve(true) - } - - static createFromWindow(window: Window): LocalRelayer | undefined { - const eth = (window as { ethereum?: EIP1193Provider }).ethereum - if (!eth) { - console.warn('Window.ethereum not found, skipping local relayer') - return undefined - } - - return new LocalRelayer(new EIP1193ProviderAdapter(eth)) - } - - static createFromProvider(provider: EIP1193Provider): LocalRelayer { - return new LocalRelayer(new EIP1193ProviderAdapter(provider)) - } - - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { - return Promise.resolve({ - isFeeRequired: false, - }) - } - - feeOptions( - _wallet: Address.Address, - _chainId: number, - _to: Address.Address, - _calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return Promise.resolve({ options: [] }) - } - - async relay( - to: Address.Address, - data: Hex.Hex, - chainId: number, - quote?: FeeQuote, - preconditions?: TransactionPrecondition[], - checkInterval: number = 5000, - ): Promise<{ opHash: Hex.Hex }> { - // Helper function to check all preconditions - const checkAllPreconditions = async (): Promise => { - if (!preconditions || preconditions.length === 0) { - return true - } - - for (const precondition of preconditions) { - const isValid = await this.checkPrecondition(precondition) - if (!isValid) { - return false - } - } - return true - } - - // Check preconditions immediately - if (await checkAllPreconditions()) { - // If all preconditions are met, relay the transaction - const txHash = await this.provider.sendTransaction( - { - to, - data, - }, - chainId, - ) - - // TODO: Return the opHash instead, but solve the `status` function - // to properly fetch the receipt from an opHash instead of a txHash - return { opHash: txHash as Hex.Hex } - } - - // If not all preconditions are met, set up event listeners and polling - return new Promise((resolve, reject) => { - let timeoutId: NodeJS.Timeout - let isResolved = false - - // Function to check and relay - const checkAndRelay = async () => { - try { - if (isResolved) return - - if (await checkAllPreconditions()) { - isResolved = true - clearTimeout(timeoutId) - const txHash = await this.provider.sendTransaction( - { - to, - data, - }, - chainId, - ) - resolve({ opHash: txHash as Hex.Hex }) - } else { - // Schedule next check - timeoutId = setTimeout(checkAndRelay, checkInterval) - } - } catch (error) { - isResolved = true - clearTimeout(timeoutId) - reject(error) - } - } - - // Start checking - timeoutId = setTimeout(checkAndRelay, checkInterval) - - // Cleanup function - return () => { - isResolved = true - clearTimeout(timeoutId) - } - }) - } - - async status(opHash: Hex.Hex, chainId: number): Promise { - const receipt = await this.provider.getTransactionReceipt(opHash, chainId) - if (receipt === 'unknown') { - // Could be pending but we don't know - return { status: 'unknown' } - } - return receipt === 'success' - ? { status: 'confirmed', transactionHash: opHash } - : { status: 'failed', reason: 'failed' } - } - - async checkPrecondition(precondition: TransactionPrecondition): Promise { - const decoded = decodePrecondition(precondition) - - if (!decoded) { - return false - } - - switch (decoded.type()) { - case 'native-balance': { - const native = decoded as NativeBalancePrecondition - const balance = await this.provider.getBalance(native.address) - if (native.min !== undefined && balance < native.min) { - return false - } - if (native.max !== undefined && balance > native.max) { - return false - } - return true - } - - case 'erc20-balance': { - const erc20 = decoded as Erc20BalancePrecondition - const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address]) - const result = await this.provider.call({ - to: erc20.token, - data, - }) - const balance = BigInt(result) - if (erc20.min !== undefined && balance < erc20.min) { - return false - } - if (erc20.max !== undefined && balance > erc20.max) { - return false - } - return true - } - - case 'erc20-approval': { - const erc20 = decoded as Erc20ApprovalPrecondition - const data = AbiFunction.encodeData(erc20Allowance, [erc20.address, erc20.operator]) - const result = await this.provider.call({ - to: erc20.token, - data, - }) - const allowance = BigInt(result) - return allowance >= erc20.min - } - - case 'erc721-ownership': { - const erc721 = decoded as Erc721OwnershipPrecondition - const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId]) - const result = await this.provider.call({ - to: erc721.token, - data, - }) - const owner = '0x' + result.slice(26) - const isOwner = owner.toLowerCase() === erc721.address.toString().toLowerCase() - return erc721.owned === undefined ? isOwner : erc721.owned === isOwner - } - - case 'erc721-approval': { - const erc721 = decoded as Erc721ApprovalPrecondition - const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId]) - const result = await this.provider.call({ - to: erc721.token, - data, - }) - const approved = '0x' + result.slice(26) - return approved.toLowerCase() === erc721.operator.toString().toLowerCase() - } - - case 'erc1155-balance': { - const erc1155 = decoded as Erc1155BalancePrecondition - const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address, erc1155.tokenId]) - const result = await this.provider.call({ - to: erc1155.token, - data, - }) - const balance = BigInt(result) - if (erc1155.min !== undefined && balance < erc1155.min) { - return false - } - if (erc1155.max !== undefined && balance > erc1155.max) { - return false - } - return true - } - - case 'erc1155-approval': { - const erc1155 = decoded as Erc1155ApprovalPrecondition - const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [erc1155.address, erc1155.operator]) - const result = await this.provider.call({ - to: erc1155.token, - data, - }) - return BigInt(result) === 1n - } - - default: - return false - } - } -} - -export class EIP1193ProviderAdapter implements GenericProvider { - constructor(private readonly provider: EIP1193Provider) {} - - private async trySwitchChain(chainId: number) { - try { - await this.provider.request({ - method: 'wallet_switchEthereumChain', - params: [ - { - chainId: `0x${chainId.toString(16)}`, - }, - ], - }) - } catch (error) { - // Log and continue - console.error('Error switching chain', error) - } - } - - async sendTransaction(args: { to: Address.Address; data: Hex.Hex }, chainId: number) { - const accounts: Address.Address[] = await this.provider.request({ method: 'eth_requestAccounts' }) - const from = accounts[0] - - if (!from) { - console.warn('No account selected, skipping local relayer') - return undefined - } - - await this.trySwitchChain(chainId) - - const tx = await this.provider.request({ - method: 'eth_sendTransaction', - params: [ - { - from, - to: args.to, - data: args.data, - }, - ], - }) - - return tx - } - - async getBalance(address: Address.Address) { - const balance = await this.provider.request({ - method: 'eth_getBalance', - params: [address, 'latest'], - }) - return BigInt(balance) - } - - async call(args: { to: Address.Address; data: Hex.Hex }) { - return await this.provider.request({ - method: 'eth_call', - params: [args, 'latest'], - }) - } - - async getTransactionReceipt(txHash: Hex.Hex, chainId: number) { - await this.trySwitchChain(chainId) - - const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) - - if (rpcReceipt) { - const receipt = TransactionReceipt.fromRpc(rpcReceipt as Parameters[0]) - if (receipt?.status === 'success') { - return 'success' - } else if (receipt?.status === 'reverted') { - return 'failed' - } - } - - return 'unknown' - } -} diff --git a/packages/services/relayer/src/relayer/standard/pk-relayer.ts b/packages/services/relayer/src/relayer/standard/pk-relayer.ts deleted file mode 100644 index b1d420a586..0000000000 --- a/packages/services/relayer/src/relayer/standard/pk-relayer.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Payload, Precondition } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider, Secp256k1, TransactionEnvelopeEip1559, TransactionReceipt } from 'ox' -import { LocalRelayer } from './local.js' -import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { FeeToken } from '../rpc-relayer/relayer.gen.js' - -export class PkRelayer implements Relayer { - public readonly kind = 'relayer' - public readonly type = 'pk' - public readonly id = 'pk' - private readonly relayer: LocalRelayer - - constructor( - privateKey: Hex.Hex, - private readonly provider: Provider.Provider, - ) { - const relayerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) - this.relayer = new LocalRelayer({ - sendTransaction: async (args, chainId) => { - const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' })) - if (providerChainId !== chainId) { - throw new Error('Provider chain id does not match relayer chain id') - } - - const oxArgs = { ...args, to: args.to as `0x${string}`, data: args.data as `0x${string}` } - // Estimate gas with a safety buffer - const estimatedGas = BigInt(await this.provider.request({ method: 'eth_estimateGas', params: [oxArgs] })) - const safeGasLimit = estimatedGas > 21000n ? (estimatedGas * 12n) / 10n : 50000n - - // Get base fee and priority fee - const baseFee = BigInt(await this.provider.request({ method: 'eth_gasPrice' })) - const priorityFee = 100000000n // 0.1 gwei priority fee - const maxFeePerGas = baseFee + priorityFee - - // Check sender have enough balance - const senderBalance = BigInt( - await this.provider.request({ method: 'eth_getBalance', params: [relayerAddress, 'latest'] }), - ) - if (senderBalance < maxFeePerGas * safeGasLimit) { - console.log('Sender balance:', senderBalance.toString(), 'wei') - throw new Error('Sender has insufficient balance to pay for gas') - } - const nonce = BigInt( - await this.provider.request({ - method: 'eth_getTransactionCount', - params: [relayerAddress, 'latest'], - }), - ) - - // Build the relay envelope - const relayEnvelope = TransactionEnvelopeEip1559.from({ - chainId: Number(chainId), - type: 'eip1559', - from: relayerAddress, - to: oxArgs.to, - data: oxArgs.data, - gas: safeGasLimit, - maxFeePerGas: maxFeePerGas, - maxPriorityFeePerGas: priorityFee, - nonce: nonce, - value: 0n, - }) - const relayerSignature = Secp256k1.sign({ - payload: TransactionEnvelopeEip1559.getSignPayload(relayEnvelope), - privateKey: privateKey, - }) - const signedRelayEnvelope = TransactionEnvelopeEip1559.from(relayEnvelope, { - signature: relayerSignature, - }) - const tx = await this.provider.request({ - method: 'eth_sendRawTransaction', - params: [TransactionEnvelopeEip1559.serialize(signedRelayEnvelope)], - }) - return tx - }, - getBalance: async (address: string): Promise => { - const balanceHex = await this.provider.request({ - method: 'eth_getBalance', - params: [address as Address.Address, 'latest'], - }) - return BigInt(balanceHex) - }, - call: async (args: { to: string; data: string }): Promise => { - const callArgs = { to: args.to as `0x${string}`, data: args.data as `0x${string}` } - return await this.provider.request({ method: 'eth_call', params: [callArgs, 'latest'] }) - }, - getTransactionReceipt: async (txHash: string, chainId: number) => { - Hex.assert(txHash) - - const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' })) - if (providerChainId !== chainId) { - throw new Error('Provider chain id does not match relayer chain id') - } - - const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) - if (!rpcReceipt) { - return 'unknown' - } - const receipt = TransactionReceipt.fromRpc(rpcReceipt) - return receipt.status === 'success' ? 'success' : 'failed' - }, - }) - } - - async isAvailable(_wallet: Address.Address, chainId: number): Promise { - const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' })) - return providerChainId === chainId - } - - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { - return this.relayer.feeTokens() - } - - feeOptions( - wallet: Address.Address, - chainId: number, - to: Address.Address, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return this.relayer.feeOptions(wallet, chainId, to, calls) - } - - async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> { - const providerChainId = Number(await this.provider.request({ method: 'eth_chainId' })) - if (providerChainId !== chainId) { - throw new Error('Provider chain id does not match relayer chain id') - } - return this.relayer.relay(to, data, chainId) - } - - status(opHash: Hex.Hex, chainId: number): Promise { - return this.relayer.status(opHash, chainId) - } - - async checkPrecondition(_precondition: Precondition.Precondition): Promise { - // TODO: Implement precondition check - return true - } -} diff --git a/packages/services/relayer/src/relayer/standard/sequence.ts b/packages/services/relayer/src/relayer/standard/sequence.ts deleted file mode 100644 index bb55a97262..0000000000 --- a/packages/services/relayer/src/relayer/standard/sequence.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { ETHTxnStatus, TransactionPrecondition, Relayer as Service, FeeToken } from '../rpc-relayer/relayer.gen.js' -import { Payload } from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Bytes, Hex } from 'ox' -import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -export class SequenceRelayer implements Relayer { - public readonly kind = 'relayer' - public readonly type = 'sequence' - readonly id = 'sequence' - - private readonly service: Service - - constructor(host: string) { - this.service = new Service(host, fetch) - } - - async isAvailable(_wallet: Address.Address, _chainId: number): Promise { - return true - } - - async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { - const { isFeeRequired, tokens, paymentAddress } = await this.service.feeTokens() - if (isFeeRequired) { - Address.assert(paymentAddress) - return { - isFeeRequired, - tokens, - paymentAddress, - } - } - // Not required - return { - isFeeRequired, - } - } - - async feeOptions( - wallet: Address.Address, - _chainId: number, - to: Address.Address, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - const execute = AbiFunction.from('function execute(bytes calldata _payload, bytes calldata _signature)') - const payload = Payload.encode({ type: 'call', space: 0n, nonce: 0n, calls }, to) - const signature = '0x0001' // TODO: use a stub signature - const data = AbiFunction.encodeData(execute, [Bytes.toHex(payload), signature]) - - const { options, quote } = await this.service.feeOptions({ wallet, to, data }) - - return { - options, - quote: quote ? { _tag: 'FeeQuote', _quote: quote } : undefined, - } - } - - async checkPrecondition(_precondition: TransactionPrecondition): Promise { - // TODO: implement - return false - } - - async relay(to: Address.Address, data: Hex.Hex, _chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }> { - const walletAddress = to // TODO: pass wallet address or stop requiring it - - const { txnHash } = await this.service.sendMetaTxn({ - call: { walletAddress, contract: to, input: data }, - quote: quote && (quote._quote as string), - }) - - return { opHash: `0x${txnHash}` } - } - - async status(opHash: Hex.Hex, _chainId: number): Promise { - try { - const { - receipt: { status, revertReason, txnReceipt }, - } = await this.service.getMetaTxnReceipt({ metaTxID: opHash }) - - switch (status) { - case ETHTxnStatus.UNKNOWN: - return { status: 'unknown' } - - case ETHTxnStatus.DROPPED: - return { status: 'failed', reason: revertReason ?? status } - - case ETHTxnStatus.QUEUED: - return { status: 'pending' } - - case ETHTxnStatus.SENT: - return { status: 'pending' } - - case ETHTxnStatus.SUCCEEDED: { - const receipt = JSON.parse(txnReceipt) - const transactionHash = receipt.transactionHash - Hex.assert(transactionHash) - return { status: 'confirmed', transactionHash } - } - - case ETHTxnStatus.PARTIALLY_FAILED: - return { status: 'failed', reason: revertReason ?? status } - - case ETHTxnStatus.FAILED: - return { status: 'failed', reason: revertReason ?? status } - - default: - throw new Error(`unknown transaction status '${status}'`) - } - } catch { - return { status: 'pending' } - } - } -} diff --git a/packages/services/relayer/test/preconditions/codec.test.ts b/packages/services/relayer/test/preconditions/codec.test.ts deleted file mode 100644 index a5ba279d39..0000000000 --- a/packages/services/relayer/test/preconditions/codec.test.ts +++ /dev/null @@ -1,531 +0,0 @@ -import { Address } from 'ox' -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' - -import { - decodePrecondition, - decodePreconditions, - encodePrecondition, - TransactionPrecondition, -} from '../../src/preconditions/codec.js' -import { - NativeBalancePrecondition, - Erc20BalancePrecondition, - Erc20ApprovalPrecondition, - Erc721OwnershipPrecondition, - Erc721ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc1155ApprovalPrecondition, -} from '../../src/preconditions/types.js' - -// Test addresses -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const OPERATOR_ADDRESS = Address.from('0x9876543210987654321098765432109876543210') -const ARBITRUM_CHAIN_ID = 42161 -const NATIVE_TOKEN_ADDRESS = Address.from('0x0000000000000000000000000000000000000000') - -describe('Preconditions Codec', () => { - // Mock console.warn to test error logging - const originalWarn = console.warn - beforeEach(() => { - console.warn = vi.fn() - }) - afterEach(() => { - console.warn = originalWarn - }) - - describe('decodePrecondition', () => { - it('should return undefined for null/undefined input', () => { - expect(decodePrecondition(null as unknown as TransactionPrecondition)).toBeUndefined() - expect(decodePrecondition(undefined as unknown as TransactionPrecondition)).toBeUndefined() - }) - - it('should decode native balance precondition with only min', () => { - const intent: TransactionPrecondition = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - - const precondition = result as NativeBalancePrecondition - expect(precondition.min).toBe(1000000000000000000n) - expect(precondition.max).toBeUndefined() - }) - - it('should decode ERC20 balance precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc20-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc20BalancePrecondition) - - const precondition = result as Erc20BalancePrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBeUndefined() - }) - - it('should decode ERC20 approval precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc20-approval', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc20ApprovalPrecondition) - - const precondition = result as Erc20ApprovalPrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.operator).toBe(TEST_ADDRESS) - expect(precondition.min).toBe(1000000n) - }) - - it('should decode ERC721 ownership precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc721-ownership', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc721OwnershipPrecondition) - - const precondition = result as Erc721OwnershipPrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(0n) - expect(precondition.owned).toBe(true) - }) - - it('should decode ERC721 ownership precondition without owned flag', () => { - const intent: TransactionPrecondition = { - type: 'erc721-ownership', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc721OwnershipPrecondition) - - const precondition = result as Erc721OwnershipPrecondition - expect(precondition.owned).toBe(true) - }) - - it('should decode ERC721 approval precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc721-approval', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc721ApprovalPrecondition) - - const precondition = result as Erc721ApprovalPrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(0n) - expect(precondition.operator).toBe(TEST_ADDRESS) - }) - - it('should decode ERC1155 balance precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc1155-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc1155BalancePrecondition) - - const precondition = result as Erc1155BalancePrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(0n) - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBeUndefined() - }) - - it('should decode ERC1155 approval precondition', () => { - const intent: TransactionPrecondition = { - type: 'erc1155-approval', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(Erc1155ApprovalPrecondition) - - const precondition = result as Erc1155ApprovalPrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(0n) - expect(precondition.operator).toBe(TEST_ADDRESS) - expect(precondition.min).toBe(1000000n) - }) - - it('should return undefined for unknown precondition type', () => { - const intent: TransactionPrecondition = { - type: 'unknown-type', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - } - - const result = decodePrecondition(intent) - expect(result).toBeUndefined() - }) - - it('should return undefined and log warning for invalid JSON', () => { - const intent: TransactionPrecondition = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - }) - - it('should return undefined and log warning for invalid precondition', () => { - const intent: TransactionPrecondition = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('2000000000000000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - }) - - it('should handle malformed addresses gracefully', () => { - const intent: TransactionPrecondition = { - type: 'native-balance', - ownerAddress: 'invalid-address', - tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeUndefined() - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition')) - }) - - it('should handle malformed BigInt values gracefully', () => { - const intent = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS.toString(), - tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), - chainId: ARBITRUM_CHAIN_ID, - minAmount: 'not-a-number', - } as unknown as TransactionPrecondition - - const result = decodePrecondition(intent) - expect(result).toBeUndefined() - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition')) - }) - - it('should return undefined and log warning for precondition that fails validation', () => { - // Note: NativeBalancePrecondition validation only checks min > max if both are defined - // Since TransactionPrecondition doesn't have max, this test may not trigger validation error - // But we can test with a valid precondition that should pass - const intent: TransactionPrecondition = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - }) - }) - - describe('decodePreconditions', () => { - it('should decode multiple preconditions', () => { - const intents: TransactionPrecondition[] = [ - { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - }, - { - type: 'erc20-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000'), - }, - ] - - const results = decodePreconditions(intents) - expect(results).toHaveLength(2) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition) - }) - - it('should filter out invalid preconditions', () => { - const intents: TransactionPrecondition[] = [ - { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - }, - { - type: 'invalid-type', - ownerAddress: TEST_ADDRESS, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - }, - { - type: 'native-balance', - ownerAddress: 'invalid-address', - tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('1000000000000000000'), - }, - ] - - const results = decodePreconditions(intents) - expect(results).toHaveLength(1) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - }) - - it('should return empty array for empty input', () => { - const results = decodePreconditions([]) - expect(results).toEqual([]) - }) - }) - - describe('encodePrecondition', () => { - it('should encode native balance precondition with min and max', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.min).toBe('1000000000000000000') - expect(data.max).toBe('2000000000000000000') - }) - - it('should encode native balance precondition with only min', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.min).toBe('1000000000000000000') - expect(data.max).toBeUndefined() - }) - - it('should encode native balance precondition with only max', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, undefined, 2000000000000000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.min).toBeUndefined() - expect(data.max).toBe('2000000000000000000') - }) - - it('should encode ERC20 balance precondition', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.min).toBe('1000000') - expect(data.max).toBe('2000000') - }) - - it('should encode ERC20 approval precondition', () => { - const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, OPERATOR_ADDRESS, 1000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.operator).toBe(OPERATOR_ADDRESS) - expect(data.min).toBe('1000000') - }) - - it('should encode ERC721 ownership precondition', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.tokenId).toBe('123') - expect(data.owned).toBe(true) - }) - - it('should encode ERC721 ownership precondition without owned flag', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.tokenId).toBe('123') - expect(data.owned).toBeUndefined() - }) - - it('should encode ERC721 approval precondition', () => { - const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.tokenId).toBe('123') - expect(data.operator).toBe(OPERATOR_ADDRESS) - }) - - it('should encode ERC1155 balance precondition', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n, 2000000n) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.tokenId).toBe('123') - expect(data.min).toBe('1000000') - expect(data.max).toBe('2000000') - }) - - it('should encode ERC1155 approval precondition', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - 123n, - OPERATOR_ADDRESS, - 1000000n, - ) - - const encoded = encodePrecondition(precondition) - const data = JSON.parse(encoded) - - expect(data.address).toBe(TEST_ADDRESS) - expect(data.token).toBe(TOKEN_ADDRESS) - expect(data.tokenId).toBe('123') - expect(data.operator).toBe(OPERATOR_ADDRESS) - expect(data.min).toBe('1000000') - }) - }) - - describe('roundtrip encoding/decoding', () => { - it('should roundtrip native balance precondition', () => { - const original = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n) - - const encoded = encodePrecondition(original) - const data = JSON.parse(encoded) - const intent: TransactionPrecondition = { - type: original.type(), - ownerAddress: data.address, - tokenAddress: NATIVE_TOKEN_ADDRESS, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt(data.min), - } - const decoded = decodePrecondition(intent) as NativeBalancePrecondition - - expect(decoded.address).toBe(original.address) - expect(decoded.min).toBe(original.min) - // Note: max is not preserved in TransactionPrecondition format - expect(decoded.max).toBeUndefined() - expect(decoded.type()).toBe(original.type()) - }) - - it('should roundtrip ERC20 balance precondition', () => { - const original = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n) - - const encoded = encodePrecondition(original) - const data = JSON.parse(encoded) - const intent: TransactionPrecondition = { - type: original.type(), - ownerAddress: data.address, - tokenAddress: data.token, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt(data.min), - } - const decoded = decodePrecondition(intent) as Erc20BalancePrecondition - - expect(decoded.address).toBe(original.address) - expect(decoded.token).toBe(original.token) - expect(decoded.min).toBe(original.min) - // Note: max is not preserved in TransactionPrecondition format - expect(decoded.max).toBeUndefined() - expect(decoded.type()).toBe(original.type()) - }) - - it('should roundtrip ERC721 ownership precondition', () => { - const original = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true) - - const encoded = encodePrecondition(original) - const data = JSON.parse(encoded) - const intent: TransactionPrecondition = { - type: original.type(), - ownerAddress: data.address, - tokenAddress: data.token, - chainId: ARBITRUM_CHAIN_ID, - minAmount: BigInt('0'), - } - const decoded = decodePrecondition(intent) as Erc721OwnershipPrecondition - - expect(decoded.address).toBe(original.address) - expect(decoded.token).toBe(original.token) - // Note: tokenId is not preserved in TransactionPrecondition format (defaults to 0) - expect(decoded.tokenId).toBe(0n) - // Note: owned is hardcoded to true in decoder - expect(decoded.owned).toBe(true) - expect(decoded.type()).toBe(original.type()) - }) - }) -}) diff --git a/packages/services/relayer/test/preconditions/preconditions.test.ts b/packages/services/relayer/test/preconditions/preconditions.test.ts deleted file mode 100644 index 2eee47cbad..0000000000 --- a/packages/services/relayer/test/preconditions/preconditions.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { Address, Hex, Secp256k1 } from 'ox' -import { describe, expect, it, vi } from 'vitest' -import { - Erc1155ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc20ApprovalPrecondition, - Erc20BalancePrecondition, - Erc721ApprovalPrecondition, - Erc721OwnershipPrecondition, - NativeBalancePrecondition, -} from '../../src/preconditions/types.js' -import { LocalRelayer, type GenericProvider } from '../../src/relayer/standard/local.js' -import { Network } from '@0xsequence/wallet-primitives' - -const CAN_RUN_LIVE = false -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -const ERC20_IMPLICIT_MINT_CONTRACT = '0x041E0CDC028050519C8e6485B2d9840caf63773F' - -function randomAddress(): Address.Address { - return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) -} - -function createMockProvider(): GenericProvider { - return { - sendTransaction: vi.fn(), - getBalance: vi.fn(), - call: vi.fn(), - getTransactionReceipt: vi.fn(), - } -} - -describe('Preconditions', () => { - const getProvider = async (): Promise<{ provider: GenericProvider; chainId: number }> => { - const chainId = Network.ChainId.MAINNET - if (CAN_RUN_LIVE) { - throw new Error('Live tests not configured: set up RPC and GenericProvider adapter') - } - const provider = createMockProvider() - return { provider, chainId } - } - - const testWalletAddress = randomAddress() - - const requireContractDeployed = async (_provider: GenericProvider, _contract: Address.Address) => { - if (CAN_RUN_LIVE) { - throw new Error('Live contract check not implemented') - } - } - - it('should create and check native balance precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - - const precondition = new NativeBalancePrecondition( - testWalletAddress, - 1000000000000000000n, // 1 ETH min - 2000000000000000000n, // 2 ETH max - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: ZERO_ADDRESS, - minAmount: precondition.min ?? 0n, - } - - vi.mocked(provider.getBalance).mockResolvedValue(1500000000000000000n) // 1.5 ETH - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC20 balance precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const precondition = new Erc20BalancePrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - 1000000n, // 1 token min - 2000000n, // 2 tokens max - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: precondition.min ?? 0n, - } - - vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC20 approval precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const operator = randomAddress() - const precondition = new Erc20ApprovalPrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - operator, - 1000000n, // 1 token min approval - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: precondition.min, - } - - vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC721 ownership precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const precondition = new Erc721OwnershipPrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - 1n, // tokenId - true, // must own - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: 0n, - } - - vi.mocked(provider.call).mockResolvedValue( - ('0x000000000000000000000000' + testWalletAddress.toString().slice(2).toLowerCase()) as Hex.Hex, - ) - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC721 approval precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const operator = randomAddress() - const precondition = new Erc721ApprovalPrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - 1n, // tokenId - operator, - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: 0n, - } - - // getApproved returns 32-byte word: 12 zero bytes + 20-byte address. Codec uses ownerAddress as operator. - const approvedHex = '0x' + '0'.repeat(24) + testWalletAddress.toString().slice(2).toLowerCase() - vi.mocked(provider.call).mockResolvedValue(approvedHex as Hex.Hex) - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC1155 balance precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const precondition = new Erc1155BalancePrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - 1n, // tokenId - 1000000n, // 1 token min - 2000000n, // 2 tokens max - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: precondition.min ?? 0n, - } - - vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) - - it('should create and check ERC1155 approval precondition', async () => { - const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider) - await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) - - const operator = randomAddress() - const precondition = new Erc1155ApprovalPrecondition( - testWalletAddress, - Address.from(ERC20_IMPLICIT_MINT_CONTRACT), - 1n, // tokenId - operator, - 1000000n, // 1 token min approval - ) - - const transactionPrecondition = { - type: precondition.type(), - chainId, - ownerAddress: precondition.address.toString(), - tokenAddress: precondition.token.toString(), - minAmount: precondition.min, - } - - vi.mocked(provider.call).mockResolvedValue('0x1' as Hex.Hex) // true - - const isValid = await relayer.checkPrecondition(transactionPrecondition) - expect(isValid).toBe(true) - }) -}) diff --git a/packages/services/relayer/test/preconditions/selectors.test.ts b/packages/services/relayer/test/preconditions/selectors.test.ts deleted file mode 100644 index 706642c23e..0000000000 --- a/packages/services/relayer/test/preconditions/selectors.test.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { Address } from 'ox' -import { describe, expect, it } from 'vitest' - -import { - extractChainID, - extractSupportedPreconditions, - extractNativeBalancePreconditions, - extractERC20BalancePreconditions, -} from '../../src/preconditions/selectors.js' -import { TransactionPrecondition } from '../../src/preconditions/codec.js' -import { - NativeBalancePrecondition, - Erc20BalancePrecondition, - Erc721OwnershipPrecondition, -} from '../../src/preconditions/types.js' -import { Network } from '@0xsequence/wallet-primitives' - -// Test addresses (strings for TransactionPrecondition) -const TEST_ADDRESS = '0x1234567890123456789012345678901234567890' -const TOKEN_ADDRESS = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - -function nativePrecondition(overrides: Partial = {}): TransactionPrecondition { - return { - type: 'native-balance', - chainId: Network.ChainId.MAINNET, - ownerAddress: TEST_ADDRESS, - tokenAddress: ZERO_ADDRESS, - minAmount: 1000000000000000000n, - ...overrides, - } -} - -function erc20Precondition(overrides: Partial = {}): TransactionPrecondition { - return { - type: 'erc20-balance', - chainId: Network.ChainId.MAINNET, - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - minAmount: 1000000n, - ...overrides, - } -} - -function erc721OwnershipPrecondition(overrides: Partial = {}): TransactionPrecondition { - return { - type: 'erc721-ownership', - chainId: Network.ChainId.MAINNET, - ownerAddress: TEST_ADDRESS, - tokenAddress: TOKEN_ADDRESS, - minAmount: 0n, - ...overrides, - } -} - -describe('Preconditions Selectors', () => { - describe('extractChainID', () => { - it('should extract chainID from valid precondition data', () => { - const precondition = nativePrecondition({ chainId: Network.ChainId.MAINNET }) - const chainId = extractChainID(precondition) - expect(chainId).toBe(Network.ChainId.MAINNET) - }) - - it('should extract large chainID values', () => { - const precondition = nativePrecondition({ chainId: Network.ChainId.ARBITRUM }) - const chainId = extractChainID(precondition) - expect(chainId).toBe(Network.ChainId.ARBITRUM) - }) - - it('should return undefined when chainID is not present', () => { - const precondition = { - type: 'native-balance', - ownerAddress: TEST_ADDRESS, - tokenAddress: ZERO_ADDRESS, - minAmount: 1n, - } as TransactionPrecondition - const chainId = extractChainID(precondition) - expect(chainId).toBeUndefined() - }) - - it('should return undefined for null/undefined precondition', () => { - expect(extractChainID(null as unknown as TransactionPrecondition)).toBeUndefined() - expect(extractChainID(undefined as unknown as TransactionPrecondition)).toBeUndefined() - }) - - it('should handle chainID with value 0', () => { - const precondition = nativePrecondition({ chainId: 0 }) - const chainId = extractChainID(precondition) - expect(chainId).toBe(0) - }) - }) - - describe('extractSupportedPreconditions', () => { - it('should extract valid preconditions', () => { - const intents: TransactionPrecondition[] = [nativePrecondition(), erc20Precondition()] - - const results = extractSupportedPreconditions(intents) - expect(results).toHaveLength(2) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition) - }) - - it('should filter out invalid preconditions', () => { - const intents: TransactionPrecondition[] = [ - nativePrecondition(), - { - type: 'unknown-type', - chainId: 1, - ownerAddress: TEST_ADDRESS, - tokenAddress: ZERO_ADDRESS, - minAmount: 0n, - } as TransactionPrecondition, - nativePrecondition({ ownerAddress: '' }), - ] - - const results = extractSupportedPreconditions(intents) - expect(results).toHaveLength(1) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - }) - - it('should return empty array for null/undefined input', () => { - expect(extractSupportedPreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) - expect(extractSupportedPreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) - }) - - it('should return empty array for empty input', () => { - const results = extractSupportedPreconditions([]) - expect(results).toEqual([]) - }) - - it('should handle mixed valid and invalid preconditions', () => { - const intents: TransactionPrecondition[] = [ - nativePrecondition(), - erc721OwnershipPrecondition(), - { - type: 'invalid-type', - chainId: 1, - ownerAddress: TEST_ADDRESS, - tokenAddress: ZERO_ADDRESS, - minAmount: 0n, - } as TransactionPrecondition, - ] - - const results = extractSupportedPreconditions(intents) - expect(results).toHaveLength(2) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[1]).toBeInstanceOf(Erc721OwnershipPrecondition) - }) - }) - - describe('extractNativeBalancePreconditions', () => { - it('should extract only native balance preconditions', () => { - const intents: TransactionPrecondition[] = [ - nativePrecondition({ minAmount: 1000000000000000000n }), - erc20Precondition(), - nativePrecondition({ minAmount: 2000000000000000000n }), - ] - - const results = extractNativeBalancePreconditions(intents) - expect(results).toHaveLength(2) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[1]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[0].min).toBe(1000000000000000000n) - expect(results[1].min).toBe(2000000000000000000n) - }) - - it('should return empty array when no native balance preconditions exist', () => { - const intents: TransactionPrecondition[] = [erc20Precondition(), erc721OwnershipPrecondition()] - - const results = extractNativeBalancePreconditions(intents) - expect(results).toEqual([]) - }) - - it('should return empty array for null/undefined input', () => { - expect(extractNativeBalancePreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) - expect(extractNativeBalancePreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) - }) - - it('should return empty array for empty input', () => { - const results = extractNativeBalancePreconditions([]) - expect(results).toEqual([]) - }) - - it('should filter out invalid native balance preconditions', () => { - const intents: TransactionPrecondition[] = [ - nativePrecondition({ minAmount: 1000000000000000000n }), - nativePrecondition({ ownerAddress: '' }), - ] - - const results = extractNativeBalancePreconditions(intents) - expect(results).toHaveLength(1) - expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) - expect(results[0].min).toBe(1000000000000000000n) - }) - }) - - describe('extractERC20BalancePreconditions', () => { - it('should extract only ERC20 balance preconditions', () => { - const intents: TransactionPrecondition[] = [ - nativePrecondition(), - erc20Precondition({ minAmount: 1000000n }), - erc20Precondition({ minAmount: 2000000n }), - ] - - const results = extractERC20BalancePreconditions(intents) - expect(results).toHaveLength(2) - expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition) - expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition) - expect(results[0].min).toBe(1000000n) - expect(results[1].min).toBe(2000000n) - expect(results[0].token).toEqual(Address.from(TOKEN_ADDRESS)) - expect(results[1].token).toEqual(Address.from(TOKEN_ADDRESS)) - }) - - it('should return empty array when no ERC20 balance preconditions exist', () => { - const intents: TransactionPrecondition[] = [nativePrecondition(), erc721OwnershipPrecondition()] - - const results = extractERC20BalancePreconditions(intents) - expect(results).toEqual([]) - }) - - it('should return empty array for null/undefined input', () => { - expect(extractERC20BalancePreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) - expect(extractERC20BalancePreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) - }) - - it('should return empty array for empty input', () => { - const results = extractERC20BalancePreconditions([]) - expect(results).toEqual([]) - }) - - it('should filter out invalid ERC20 balance preconditions', () => { - const intents: TransactionPrecondition[] = [ - erc20Precondition({ minAmount: 1000000n }), - erc20Precondition({ tokenAddress: '' }), - ] - - const results = extractERC20BalancePreconditions(intents) - expect(results).toHaveLength(1) - expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition) - expect(results[0].min).toBe(1000000n) - expect(results[0].token).toEqual(Address.from(TOKEN_ADDRESS)) - }) - }) -}) diff --git a/packages/services/relayer/test/preconditions/types.test.ts b/packages/services/relayer/test/preconditions/types.test.ts deleted file mode 100644 index a479676b58..0000000000 --- a/packages/services/relayer/test/preconditions/types.test.ts +++ /dev/null @@ -1,443 +0,0 @@ -import { Address } from 'ox' -import { describe, expect, it } from 'vitest' - -import { - NativeBalancePrecondition, - Erc20BalancePrecondition, - Erc20ApprovalPrecondition, - Erc721OwnershipPrecondition, - Erc721ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc1155ApprovalPrecondition, -} from '../../src/preconditions/types.js' - -// Test addresses -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const OPERATOR_ADDRESS = Address.from('0x9876543210987654321098765432109876543210') - -describe('Preconditions Types', () => { - describe('NativeBalancePrecondition', () => { - it('should create a valid native balance precondition', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.min).toBe(1000000000000000000n) - expect(precondition.max).toBe(2000000000000000000n) - expect(precondition.type()).toBe('native-balance') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create a precondition with only min value', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n) - - expect(precondition.min).toBe(1000000000000000000n) - expect(precondition.max).toBeUndefined() - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create a precondition with only max value', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, undefined, 2000000000000000000n) - - expect(precondition.min).toBeUndefined() - expect(precondition.max).toBe(2000000000000000000n) - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create a precondition with no min/max values', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS) - - expect(precondition.min).toBeUndefined() - expect(precondition.max).toBeUndefined() - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new NativeBalancePrecondition('' as Address.Address) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate min cannot be greater than max', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 2000000000000000000n, 1000000000000000000n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('min balance cannot be greater than max balance') - }) - - it('should allow min equal to max', () => { - const precondition = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 1000000000000000000n) - - expect(precondition.isValid()).toBeUndefined() - }) - }) - - describe('Erc20BalancePrecondition', () => { - it('should create a valid ERC20 balance precondition', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBe(2000000n) - expect(precondition.type()).toBe('erc20-balance') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc20BalancePrecondition('' as Address.Address, TOKEN_ADDRESS) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, '' as Address.Address) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate min cannot be greater than max', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 2000000n, 1000000n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('min balance cannot be greater than max balance') - }) - - it('should create precondition with only min value', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n) - - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBeUndefined() - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create precondition with only max value', () => { - const precondition = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined, 2000000n) - - expect(precondition.min).toBeUndefined() - expect(precondition.max).toBe(2000000n) - expect(precondition.isValid()).toBeUndefined() - }) - }) - - describe('Erc20ApprovalPrecondition', () => { - it('should create a valid ERC20 approval precondition', () => { - const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, OPERATOR_ADDRESS, 1000000n) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) - expect(precondition.min).toBe(1000000n) - expect(precondition.type()).toBe('erc20-approval') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc20ApprovalPrecondition( - '' as Address.Address, - TOKEN_ADDRESS, - OPERATOR_ADDRESS, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc20ApprovalPrecondition( - TEST_ADDRESS, - '' as Address.Address, - OPERATOR_ADDRESS, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate operator address is required', () => { - const precondition = new Erc20ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, '' as Address.Address, 1000000n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('operator address is required') - }) - - it('should validate min approval amount is required', () => { - const precondition = new Erc20ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - OPERATOR_ADDRESS, - undefined as unknown as bigint, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('min approval amount is required') - }) - }) - - describe('Erc721OwnershipPrecondition', () => { - it('should create a valid ERC721 ownership precondition', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.owned).toBe(true) - expect(precondition.type()).toBe('erc721-ownership') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create precondition with default owned value', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n) - - expect(precondition.owned).toBeUndefined() - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc721OwnershipPrecondition('' as Address.Address, TOKEN_ADDRESS, 123n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, '' as Address.Address, 123n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate tokenId is required', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as unknown as bigint) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('tokenId is required') - }) - - it('should handle tokenId of 0', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 0n) - - expect(precondition.tokenId).toBe(0n) - expect(precondition.isValid()).toBeUndefined() - }) - }) - - describe('Erc721ApprovalPrecondition', () => { - it('should create a valid ERC721 approval precondition', () => { - const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) - expect(precondition.type()).toBe('erc721-approval') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc721ApprovalPrecondition('' as Address.Address, TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, '' as Address.Address, 123n, OPERATOR_ADDRESS) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate tokenId is required', () => { - const precondition = new Erc721ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - undefined as unknown as bigint, - OPERATOR_ADDRESS, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('tokenId is required') - }) - - it('should validate operator address is required', () => { - const precondition = new Erc721ApprovalPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, '' as Address.Address) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('operator address is required') - }) - }) - - describe('Erc1155BalancePrecondition', () => { - it('should create a valid ERC1155 balance precondition', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n, 2000000n) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBe(2000000n) - expect(precondition.type()).toBe('erc1155-balance') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc1155BalancePrecondition('' as Address.Address, TOKEN_ADDRESS, 123n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, '' as Address.Address, 123n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate tokenId is required', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as unknown as bigint) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('tokenId is required') - }) - - it('should validate min cannot be greater than max', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 2000000n, 1000000n) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('min balance cannot be greater than max balance') - }) - - it('should create precondition with only min value', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, 1000000n) - - expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBeUndefined() - expect(precondition.isValid()).toBeUndefined() - }) - - it('should create precondition with only max value', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, undefined, 2000000n) - - expect(precondition.min).toBeUndefined() - expect(precondition.max).toBe(2000000n) - expect(precondition.isValid()).toBeUndefined() - }) - }) - - describe('Erc1155ApprovalPrecondition', () => { - it('should create a valid ERC1155 approval precondition', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - 123n, - OPERATOR_ADDRESS, - 1000000n, - ) - - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) - expect(precondition.min).toBe(1000000n) - expect(precondition.type()).toBe('erc1155-approval') - expect(precondition.isValid()).toBeUndefined() - }) - - it('should validate address is required', () => { - const precondition = new Erc1155ApprovalPrecondition( - '' as Address.Address, - TOKEN_ADDRESS, - 123n, - OPERATOR_ADDRESS, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('address is required') - }) - - it('should validate token address is required', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - '' as Address.Address, - 123n, - OPERATOR_ADDRESS, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('token address is required') - }) - - it('should validate tokenId is required', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - undefined as unknown as bigint, - OPERATOR_ADDRESS, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('tokenId is required') - }) - - it('should validate operator address is required', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - 123n, - '' as Address.Address, - 1000000n, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('operator address is required') - }) - - it('should validate min approval amount is required', () => { - const precondition = new Erc1155ApprovalPrecondition( - TEST_ADDRESS, - TOKEN_ADDRESS, - 123n, - OPERATOR_ADDRESS, - undefined as unknown as bigint, - ) - - const error = precondition.isValid() - expect(error).toBeInstanceOf(Error) - expect(error?.message).toBe('min approval amount is required') - }) - }) -}) diff --git a/packages/services/relayer/test/relayer/relayer.test.ts b/packages/services/relayer/test/relayer/relayer.test.ts deleted file mode 100644 index 716cd11d65..0000000000 --- a/packages/services/relayer/test/relayer/relayer.test.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { Relayer, RpcRelayerGen } from '@0xsequence/relayer' - -// Test addresses and data -const TEST_WALLET_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_TO_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const TEST_DATA = Hex.from('0x12345678') -const TEST_CHAIN_ID = Network.ChainId.MAINNET -const TEST_OP_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') - -describe('Relayer', () => { - describe('Relayer.isRelayer type guard', () => { - it('should return true for valid relayer objects', () => { - const mockRelayer: Relayer.Relayer = { - kind: 'relayer', - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - feeTokens: vi.fn(), - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - - expect(Relayer.isRelayer(mockRelayer)).toBe(true) - }) - - it('should return false for objects missing required methods', () => { - // Missing isAvailable - const missing1 = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - expect(Relayer.isRelayer(missing1)).toBe(false) - - // Missing feeOptions - const missing2 = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - expect(Relayer.isRelayer(missing2)).toBe(false) - - // Missing relay - const missing3 = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - feeOptions: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - expect(Relayer.isRelayer(missing3)).toBe(false) - - // Missing status - const missing4 = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - feeOptions: vi.fn(), - relay: vi.fn(), - checkPrecondition: vi.fn(), - } - expect(Relayer.isRelayer(missing4)).toBe(false) - - // Missing checkPrecondition - const missing5 = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - } - expect(Relayer.isRelayer(missing5)).toBe(false) - }) - - it('should return false for non-objects', () => { - // These will throw due to the 'in' operator, so we need to test the actual behavior - expect(() => Relayer.isRelayer(null)).toThrow() - expect(() => Relayer.isRelayer(undefined)).toThrow() - expect(() => Relayer.isRelayer('string')).toThrow() - expect(() => Relayer.isRelayer(123)).toThrow() - expect(() => Relayer.isRelayer(true)).toThrow() - // Arrays and objects should not throw, but should return false - expect(Relayer.isRelayer([])).toBe(false) - }) - - it('should return false for objects with properties but wrong types', () => { - const wrongTypes = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: 'not a function', - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - // The current implementation only checks if properties exist, not their types - // So this will actually return true since all required properties exist - expect(Relayer.isRelayer(wrongTypes)).toBe(true) - }) - }) - - describe('FeeOption interface', () => { - it('should accept valid fee option objects', () => { - const feeOption: Relayer.FeeOption = { - token: { - chainId: Network.ChainId.MAINNET, - name: 'Ethereum', - symbol: 'ETH', - decimals: 18, - logoURL: 'https://example.com/eth.png', - type: 'NATIVE' as RpcRelayerGen.FeeTokenType, - contractAddress: undefined, - }, - to: TEST_TO_ADDRESS, - value: '1000000000000000000', - gasLimit: 21000, - } - - expect(feeOption.token).toBeDefined() - expect(feeOption.to).toBe(TEST_TO_ADDRESS) - expect(feeOption.value).toBe('1000000000000000000') - expect(feeOption.gasLimit).toBe(21000) - }) - }) - - describe('FeeQuote interface', () => { - it('should accept valid fee quote objects', () => { - const feeQuote: Relayer.FeeQuote = { - _tag: 'FeeQuote', - _quote: { someQuoteData: 'value' }, - } - - expect(feeQuote._tag).toBe('FeeQuote') - expect(feeQuote._quote).toBeDefined() - }) - }) - - describe('OperationStatus types', () => { - it('should accept OperationUnknownStatus', () => { - const status: Relayer.OperationUnknownStatus = { - status: 'unknown', - reason: 'Transaction not found', - } - - expect(status.status).toBe('unknown') - expect(status.reason).toBe('Transaction not found') - }) - - it('should accept OperationQueuedStatus', () => { - const status: Relayer.OperationQueuedStatus = { - status: 'queued', - reason: 'Transaction queued for processing', - } - - expect(status.status).toBe('queued') - expect(status.reason).toBeDefined() - }) - - it('should accept OperationPendingStatus', () => { - const status: Relayer.OperationPendingStatus = { - status: 'pending', - reason: 'Transaction pending confirmation', - } - - expect(status.status).toBe('pending') - expect(status.reason).toBeDefined() - }) - - it('should accept OperationPendingPreconditionStatus', () => { - const status: Relayer.OperationPendingPreconditionStatus = { - status: 'pending-precondition', - reason: 'Waiting for preconditions to be met', - } - - expect(status.status).toBe('pending-precondition') - expect(status.reason).toBeDefined() - }) - - it('should accept OperationConfirmedStatus', () => { - const status: Relayer.OperationConfirmedStatus = { - status: 'confirmed', - transactionHash: TEST_OP_HASH, - data: { - receipt: { - id: 'receipt123', - status: 'success', - index: 0, - logs: [], - receipts: [], - blockNumber: '12345', - txnHash: 'hash123', - txnReceipt: 'receipt_data', - }, - }, - } - - expect(status.status).toBe('confirmed') - expect(status.transactionHash).toBe(TEST_OP_HASH) - expect(status.data).toBeDefined() - }) - - it('should accept OperationFailedStatus', () => { - const status: Relayer.OperationFailedStatus = { - status: 'failed', - transactionHash: TEST_OP_HASH, - reason: 'Transaction reverted', - data: { - receipt: { - id: 'receipt456', - status: 'failed', - index: 0, - logs: [], - receipts: [], - blockNumber: '12345', - txnHash: 'hash123', - txnReceipt: 'receipt_data', - }, - }, - } - - expect(status.status).toBe('failed') - expect(status.transactionHash).toBe(TEST_OP_HASH) - expect(status.reason).toBe('Transaction reverted') - expect(status.data).toBeDefined() - }) - - it('should handle OperationStatus union type', () => { - const statuses: Relayer.OperationStatus[] = [ - { status: 'unknown' }, - { status: 'queued' }, - { status: 'pending' }, - { status: 'pending-precondition' }, - { status: 'confirmed', transactionHash: TEST_OP_HASH }, - { status: 'failed', reason: 'Error occurred' }, - ] - - statuses.forEach((status) => { - expect(['unknown', 'queued', 'pending', 'pending-precondition', 'confirmed', 'failed']).toContain(status.status) - }) - }) - }) - - describe('Relayer interface contract', () => { - let mockRelayer: Relayer.Relayer - - beforeEach(() => { - mockRelayer = { - kind: 'relayer', - type: 'mock', - id: 'mock-relayer', - isAvailable: vi.fn(), - feeTokens: vi.fn(), - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - }) - - it('should have required properties', () => { - expect(mockRelayer.kind).toBe('relayer') - expect(mockRelayer.type).toBe('mock') - expect(mockRelayer.id).toBe('mock-relayer') - }) - - it('should have required methods with correct signatures', () => { - expect(typeof mockRelayer.isAvailable).toBe('function') - expect(typeof mockRelayer.feeOptions).toBe('function') - expect(typeof mockRelayer.relay).toBe('function') - expect(typeof mockRelayer.status).toBe('function') - expect(typeof mockRelayer.checkPrecondition).toBe('function') - }) - - it('should support typical relayer workflow methods', async () => { - // Mock the methods to return expected types - vi.mocked(mockRelayer.isAvailable).mockResolvedValue(true) - vi.mocked(mockRelayer.feeOptions).mockResolvedValue({ - options: [], - quote: undefined, - }) - vi.mocked(mockRelayer.relay).mockResolvedValue({ - opHash: TEST_OP_HASH, - }) - vi.mocked(mockRelayer.status).mockResolvedValue({ - status: 'confirmed', - transactionHash: TEST_OP_HASH, - }) - vi.mocked(mockRelayer.checkPrecondition).mockResolvedValue(true) - - // Test method calls - const isAvailable = await mockRelayer.isAvailable(TEST_WALLET_ADDRESS, TEST_CHAIN_ID) - expect(isAvailable).toBe(true) - - const feeOptions = await mockRelayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, []) - expect(feeOptions.options).toEqual([]) - - const relayResult = await mockRelayer.relay(TEST_TO_ADDRESS, TEST_DATA, TEST_CHAIN_ID) - expect(relayResult.opHash).toBe(TEST_OP_HASH) - - const statusResult = await mockRelayer.status(TEST_OP_HASH, TEST_CHAIN_ID) - expect(statusResult.status).toBe('confirmed') - - const preconditionResult = await mockRelayer.checkPrecondition({} as { type: string }) - expect(preconditionResult).toBe(true) - }) - }) - - describe('Type compatibility', () => { - it('should work with Address and Hex types from ox', () => { - // Test that the interfaces work correctly with ox types - const address = Address.from('0x1234567890123456789012345678901234567890') - const hex = Hex.from('0xabcdef') - const chainId = 1n - - expect(Address.validate(address)).toBe(true) - expect(Hex.validate(hex)).toBe(true) - expect(typeof chainId).toBe('bigint') - }) - - it('should work with wallet-primitives types', () => { - // Test basic compatibility with imported types - const mockCall: Payload.Call = { - to: TEST_TO_ADDRESS, - value: 0n, - data: TEST_DATA, - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - expect(mockCall.to).toBe(TEST_TO_ADDRESS) - expect(mockCall.data).toBe(TEST_DATA) - }) - }) -}) diff --git a/packages/services/relayer/tsconfig.json b/packages/services/relayer/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/relayer/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/services/userdata/CHANGELOG.md b/packages/services/userdata/CHANGELOG.md deleted file mode 100644 index 51ebad4131..0000000000 --- a/packages/services/userdata/CHANGELOG.md +++ /dev/null @@ -1,142 +0,0 @@ -# @0xsequence/userdata - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 3.0.0 release -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 diff --git a/packages/services/userdata/README.md b/packages/services/userdata/README.md deleted file mode 100644 index 2387b56e2e..0000000000 --- a/packages/services/userdata/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/userdata - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/userdata/eslint.config.js b/packages/services/userdata/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/services/userdata/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/services/userdata/package.json b/packages/services/userdata/package.json deleted file mode 100644 index 38f3c9d5a8..0000000000 --- a/packages/services/userdata/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/userdata", - "version": "3.0.5", - "description": "userdata sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/userdata", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/services/userdata/src/index.ts b/packages/services/userdata/src/index.ts deleted file mode 100644 index 63b8559678..0000000000 --- a/packages/services/userdata/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export * from './userdata.gen.js' - -import { UserData as UserdataRpc } from './userdata.gen.js' - -export class SequenceUserdataClient extends UserdataRpc { - constructor( - hostname: string, - public projectAccessKey?: string, - public jwtAuth?: string, - ) { - super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) - this.fetch = this._fetch - } - - _fetch = (input: RequestInfo, init?: RequestInit): Promise => { - // automatically include jwt and access key auth header to requests - // if its been set on the api client - const headers: Record = {} - - const jwtAuth = this.jwtAuth - const projectAccessKey = this.projectAccessKey - - if (jwtAuth && jwtAuth.length > 0) { - headers['Authorization'] = `BEARER ${jwtAuth}` - } - - if (projectAccessKey && projectAccessKey.length > 0) { - headers['X-Access-Key'] = projectAccessKey - } - - // before the request is made - init!.headers = { ...init!.headers, ...headers } - - return fetch(input, init) - } -} diff --git a/packages/services/userdata/src/userdata.gen.ts b/packages/services/userdata/src/userdata.gen.ts deleted file mode 100644 index d671010bed..0000000000 --- a/packages/services/userdata/src/userdata.gen.ts +++ /dev/null @@ -1,1729 +0,0 @@ -/* eslint-disable */ -// userdata v0.1.0 88764bb5f99353e11d849a1aa8f8a998501ffedb -// -- -// Code generated by Webrpc-gen@v0.30.2 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=userdata.ridl -target=typescript -client -out=./clients/userdata.gen.ts - -// Webrpc description and code-gen version -export const WebrpcVersion = 'v1' - -// Schema version of your RIDL schema -export const WebrpcSchemaVersion = 'v0.1.0' - -// Schema hash generated from your RIDL schema -export const WebrpcSchemaHash = '88764bb5f99353e11d849a1aa8f8a998501ffedb' - -// -// Client interface -// - -export interface UserDataClient { - getCapabilities(headers?: object, signal?: AbortSignal): Promise - - getAccessToken(req: GetAccessTokenRequest, headers?: object, signal?: AbortSignal): Promise - - getIdentityToken( - req: GetIdentityTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - getWalletPreferences( - req: GetWalletPreferencesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putWalletPreferences( - req: PutWalletPreferencesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listWalletSigners( - req: ListWalletSignersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putWalletSigner(req: PutWalletSignerRequest, headers?: object, signal?: AbortSignal): Promise - - deleteWalletSigner( - req: DeleteWalletSignerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listSessions(req: ListSessionsRequest, headers?: object, signal?: AbortSignal): Promise - - putSession(req: PutSessionRequest, headers?: object, signal?: AbortSignal): Promise - - deleteSession(req: DeleteSessionRequest, headers?: object, signal?: AbortSignal): Promise - - listContacts(req: ListContactsRequest, headers?: object, signal?: AbortSignal): Promise - - putContact(req: PutContactRequest, headers?: object, signal?: AbortSignal): Promise - - deleteContact(req: DeleteContactRequest, headers?: object, signal?: AbortSignal): Promise - - listWatchedWallets( - req: ListWatchedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putWatchedWallet( - req: PutWatchedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteWatchedWallet( - req: DeleteWatchedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listDiscoverFavorites( - req: ListDiscoverFavoritesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putDiscoverFavorite( - req: PutDiscoverFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteDiscoverFavorite( - req: DeleteDiscoverFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listDiscoverHistory( - req: ListDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putDiscoverHistory( - req: PutDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteDiscoverHistory( - req: DeleteDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listTokenFavorites( - req: ListTokenFavoritesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putTokenFavorite( - req: PutTokenFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - deleteTokenFavorite( - req: DeleteTokenFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - listHiddenTokens( - req: ListHiddenTokensRequest, - headers?: object, - signal?: AbortSignal, - ): Promise - - putHiddenToken(req: PutHiddenTokenRequest, headers?: object, signal?: AbortSignal): Promise - - deleteHiddenToken( - req: DeleteHiddenTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise -} - -// -// Schema types -// - -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} - -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string -} - -export interface Wallet { - address: string - ecosystem?: number - preferences: WalletPreferences - updatedAt: string - createdAt: string -} - -export interface WalletPreferences { - manualSigning?: boolean - hideUnlistedTokens?: boolean - hideCollectibles?: boolean - includeTestnets?: boolean - currency?: string -} - -export interface WalletSigner { - walletAddress: string - signerAddress: string - kind: string - email?: string - updatedAt: string - createdAt: string -} - -export interface Session { - walletAddress: string - sessionAddress: string - ipAddress: string - userAgent: string - originUrl: string - appUrl: string - createdAt: string -} - -export interface Contact { - walletAddress: string - contactAddress: string - nickname: string - updatedAt: string - createdAt: string -} - -export interface WatchedWallet { - walletAddress: string - watchedAddress: string - nickname: string - updatedAt: string - createdAt: string -} - -export interface DiscoverFavorite { - walletAddress: string - id: number - dappId: string - createdAt: string -} - -export interface DiscoverHistory { - walletAddress: string - id: number - dappId: string - accessedAt: string -} - -export interface TokenFavorite { - walletAddress: string - id: number - chainId: string - contractAddress: string - tokenId: string - createdAt: string -} - -export interface HiddenToken { - walletAddress: string - id: number - chainId: string - contractAddress: string - tokenId?: string - createdAt: string -} - -export interface SessionProps { - address: string - appUrl: string -} - -export interface WalletSignerProps { - address: string - kind: string - email?: string -} - -export interface ContactProps { - address: string - nickname?: string -} - -export interface DiscoverProps { - dappId: string -} - -export interface TokenFavoriteProps { - chainId: string - contractAddress: string - tokenId: string -} - -export interface HiddenTokenProps { - chainId: string - contractAddress: string - tokenId?: string -} - -export interface WatchedWalletProps { - watchedAddress: string - nickname?: string -} - -export interface GetCapabilitiesRequest {} - -export interface GetCapabilitiesResponse { - supportedMethods: Array -} - -export interface GetAccessTokenRequest { - ethauthProof: string - chainId: string -} - -export interface GetAccessTokenResponse { - accessToken: string - refreshToken: string - expiresIn: number -} - -export interface GetIdentityTokenRequest { - claims: { [key: string]: any } -} - -export interface GetIdentityTokenResponse { - idToken: string -} - -export interface GetWalletPreferencesRequest { - wallet: string -} - -export interface GetWalletPreferencesResponse { - preferences: WalletPreferences -} - -export interface PutWalletPreferencesRequest { - wallet: string - preferences: WalletPreferences -} - -export interface PutWalletPreferencesResponse {} - -export interface ListWalletSignersRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListWalletSignersResponse { - signers: Array - nextCursor: string -} - -export interface PutWalletSignerRequest { - wallet: string - signer: WalletSignerProps -} - -export interface PutWalletSignerResponse { - signer: WalletSigner -} - -export interface DeleteWalletSignerRequest { - wallet: string - signer: string -} - -export interface DeleteWalletSignerResponse {} - -export interface ListSessionsRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListSessionsResponse { - sessions: Array - nextCursor: string -} - -export interface PutSessionRequest { - wallet: string - session: SessionProps -} - -export interface PutSessionResponse { - session: Session -} - -export interface DeleteSessionRequest { - wallet: string - session: string -} - -export interface DeleteSessionResponse {} - -export interface ListContactsRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListContactsResponse { - contacts: Array - nextCursor: string -} - -export interface PutContactRequest { - wallet: string - contact: ContactProps -} - -export interface PutContactResponse { - contact: Contact -} - -export interface DeleteContactRequest { - wallet: string - contact: string -} - -export interface DeleteContactResponse {} - -export interface ListWatchedWalletsRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListWatchedWalletsResponse { - watchedWallets: Array - nextCursor: string -} - -export interface PutWatchedWalletRequest { - wallet: string - watchedWallet: WatchedWalletProps -} - -export interface PutWatchedWalletResponse { - watchedWallet: WatchedWallet -} - -export interface DeleteWatchedWalletRequest { - wallet: string - watchedWallet: string -} - -export interface DeleteWatchedWalletResponse {} - -export interface ListDiscoverFavoritesRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListDiscoverFavoritesResponse { - favorites: Array - nextCursor: string -} - -export interface PutDiscoverFavoriteRequest { - wallet: string - favorite: DiscoverProps -} - -export interface PutDiscoverFavoriteResponse { - favorite: DiscoverFavorite -} - -export interface DeleteDiscoverFavoriteRequest { - wallet: string - id: number -} - -export interface DeleteDiscoverFavoriteResponse {} - -export interface ListDiscoverHistoryRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListDiscoverHistoryResponse { - history: Array - nextCursor: string -} - -export interface PutDiscoverHistoryRequest { - wallet: string - history: DiscoverProps -} - -export interface PutDiscoverHistoryResponse { - history: DiscoverHistory -} - -export interface DeleteDiscoverHistoryRequest { - wallet: string - id: number -} - -export interface DeleteDiscoverHistoryResponse {} - -export interface ListTokenFavoritesRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListTokenFavoritesResponse { - favorites: Array - nextCursor: string -} - -export interface PutTokenFavoriteRequest { - wallet: string - favorite: TokenFavoriteProps -} - -export interface PutTokenFavoriteResponse { - favorite: TokenFavorite -} - -export interface DeleteTokenFavoriteRequest { - wallet: string - id: number -} - -export interface DeleteTokenFavoriteResponse {} - -export interface ListHiddenTokensRequest { - wallet: string - pageSize: number - cursor: string -} - -export interface ListHiddenTokensResponse { - hiddenTokens: Array - nextCursor: string -} - -export interface PutHiddenTokenRequest { - wallet: string - token: HiddenTokenProps -} - -export interface PutHiddenTokenResponse { - hiddenToken: HiddenToken -} - -export interface DeleteHiddenTokenRequest { - wallet: string - id: number -} - -export interface DeleteHiddenTokenResponse {} - -// -// Client -// - -export class UserData implements UserDataClient { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/UserData/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - queryKey = { - getCapabilities: () => ['UserData', 'getCapabilities'] as const, - getAccessToken: (req: GetAccessTokenRequest) => ['UserData', 'getAccessToken', req] as const, - getIdentityToken: (req: GetIdentityTokenRequest) => ['UserData', 'getIdentityToken', req] as const, - getWalletPreferences: (req: GetWalletPreferencesRequest) => ['UserData', 'getWalletPreferences', req] as const, - putWalletPreferences: (req: PutWalletPreferencesRequest) => ['UserData', 'putWalletPreferences', req] as const, - listWalletSigners: (req: ListWalletSignersRequest) => ['UserData', 'listWalletSigners', req] as const, - putWalletSigner: (req: PutWalletSignerRequest) => ['UserData', 'putWalletSigner', req] as const, - deleteWalletSigner: (req: DeleteWalletSignerRequest) => ['UserData', 'deleteWalletSigner', req] as const, - listSessions: (req: ListSessionsRequest) => ['UserData', 'listSessions', req] as const, - putSession: (req: PutSessionRequest) => ['UserData', 'putSession', req] as const, - deleteSession: (req: DeleteSessionRequest) => ['UserData', 'deleteSession', req] as const, - listContacts: (req: ListContactsRequest) => ['UserData', 'listContacts', req] as const, - putContact: (req: PutContactRequest) => ['UserData', 'putContact', req] as const, - deleteContact: (req: DeleteContactRequest) => ['UserData', 'deleteContact', req] as const, - listWatchedWallets: (req: ListWatchedWalletsRequest) => ['UserData', 'listWatchedWallets', req] as const, - putWatchedWallet: (req: PutWatchedWalletRequest) => ['UserData', 'putWatchedWallet', req] as const, - deleteWatchedWallet: (req: DeleteWatchedWalletRequest) => ['UserData', 'deleteWatchedWallet', req] as const, - listDiscoverFavorites: (req: ListDiscoverFavoritesRequest) => ['UserData', 'listDiscoverFavorites', req] as const, - putDiscoverFavorite: (req: PutDiscoverFavoriteRequest) => ['UserData', 'putDiscoverFavorite', req] as const, - deleteDiscoverFavorite: (req: DeleteDiscoverFavoriteRequest) => - ['UserData', 'deleteDiscoverFavorite', req] as const, - listDiscoverHistory: (req: ListDiscoverHistoryRequest) => ['UserData', 'listDiscoverHistory', req] as const, - putDiscoverHistory: (req: PutDiscoverHistoryRequest) => ['UserData', 'putDiscoverHistory', req] as const, - deleteDiscoverHistory: (req: DeleteDiscoverHistoryRequest) => ['UserData', 'deleteDiscoverHistory', req] as const, - listTokenFavorites: (req: ListTokenFavoritesRequest) => ['UserData', 'listTokenFavorites', req] as const, - putTokenFavorite: (req: PutTokenFavoriteRequest) => ['UserData', 'putTokenFavorite', req] as const, - deleteTokenFavorite: (req: DeleteTokenFavoriteRequest) => ['UserData', 'deleteTokenFavorite', req] as const, - listHiddenTokens: (req: ListHiddenTokensRequest) => ['UserData', 'listHiddenTokens', req] as const, - putHiddenToken: (req: PutHiddenTokenRequest) => ['UserData', 'putHiddenToken', req] as const, - deleteHiddenToken: (req: DeleteHiddenTokenRequest) => ['UserData', 'deleteHiddenToken', req] as const, - } - - getCapabilities = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetCapabilities'), createHttpRequest('{}', headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetCapabilitiesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getAccessToken = ( - req: GetAccessTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetAccessToken'), - createHttpRequest(JsonEncode(req, 'GetAccessTokenRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetAccessTokenResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getIdentityToken = ( - req: GetIdentityTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetIdentityToken'), - createHttpRequest(JsonEncode(req, 'GetIdentityTokenRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetIdentityTokenResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - getWalletPreferences = ( - req: GetWalletPreferencesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('GetWalletPreferences'), - createHttpRequest(JsonEncode(req, 'GetWalletPreferencesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'GetWalletPreferencesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putWalletPreferences = ( - req: PutWalletPreferencesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutWalletPreferences'), - createHttpRequest(JsonEncode(req, 'PutWalletPreferencesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutWalletPreferencesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listWalletSigners = ( - req: ListWalletSignersRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListWalletSigners'), - createHttpRequest(JsonEncode(req, 'ListWalletSignersRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListWalletSignersResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putWalletSigner = ( - req: PutWalletSignerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutWalletSigner'), - createHttpRequest(JsonEncode(req, 'PutWalletSignerRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutWalletSignerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteWalletSigner = ( - req: DeleteWalletSignerRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteWalletSigner'), - createHttpRequest(JsonEncode(req, 'DeleteWalletSignerRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteWalletSignerResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listSessions = (req: ListSessionsRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('ListSessions'), - createHttpRequest(JsonEncode(req, 'ListSessionsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListSessionsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putSession = (req: PutSessionRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('PutSession'), - createHttpRequest(JsonEncode(req, 'PutSessionRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutSessionResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteSession = ( - req: DeleteSessionRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteSession'), - createHttpRequest(JsonEncode(req, 'DeleteSessionRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteSessionResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listContacts = (req: ListContactsRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('ListContacts'), - createHttpRequest(JsonEncode(req, 'ListContactsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListContactsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putContact = (req: PutContactRequest, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch( - this.url('PutContact'), - createHttpRequest(JsonEncode(req, 'PutContactRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutContactResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteContact = ( - req: DeleteContactRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteContact'), - createHttpRequest(JsonEncode(req, 'DeleteContactRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteContactResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listWatchedWallets = ( - req: ListWatchedWalletsRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListWatchedWallets'), - createHttpRequest(JsonEncode(req, 'ListWatchedWalletsRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListWatchedWalletsResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putWatchedWallet = ( - req: PutWatchedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutWatchedWallet'), - createHttpRequest(JsonEncode(req, 'PutWatchedWalletRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutWatchedWalletResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteWatchedWallet = ( - req: DeleteWatchedWalletRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteWatchedWallet'), - createHttpRequest(JsonEncode(req, 'DeleteWatchedWalletRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteWatchedWalletResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listDiscoverFavorites = ( - req: ListDiscoverFavoritesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListDiscoverFavorites'), - createHttpRequest(JsonEncode(req, 'ListDiscoverFavoritesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListDiscoverFavoritesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putDiscoverFavorite = ( - req: PutDiscoverFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutDiscoverFavorite'), - createHttpRequest(JsonEncode(req, 'PutDiscoverFavoriteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutDiscoverFavoriteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteDiscoverFavorite = ( - req: DeleteDiscoverFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteDiscoverFavorite'), - createHttpRequest(JsonEncode(req, 'DeleteDiscoverFavoriteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteDiscoverFavoriteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listDiscoverHistory = ( - req: ListDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListDiscoverHistory'), - createHttpRequest(JsonEncode(req, 'ListDiscoverHistoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListDiscoverHistoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putDiscoverHistory = ( - req: PutDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutDiscoverHistory'), - createHttpRequest(JsonEncode(req, 'PutDiscoverHistoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutDiscoverHistoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteDiscoverHistory = ( - req: DeleteDiscoverHistoryRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteDiscoverHistory'), - createHttpRequest(JsonEncode(req, 'DeleteDiscoverHistoryRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteDiscoverHistoryResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listTokenFavorites = ( - req: ListTokenFavoritesRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListTokenFavorites'), - createHttpRequest(JsonEncode(req, 'ListTokenFavoritesRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListTokenFavoritesResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putTokenFavorite = ( - req: PutTokenFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutTokenFavorite'), - createHttpRequest(JsonEncode(req, 'PutTokenFavoriteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutTokenFavoriteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteTokenFavorite = ( - req: DeleteTokenFavoriteRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteTokenFavorite'), - createHttpRequest(JsonEncode(req, 'DeleteTokenFavoriteRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteTokenFavoriteResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - listHiddenTokens = ( - req: ListHiddenTokensRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('ListHiddenTokens'), - createHttpRequest(JsonEncode(req, 'ListHiddenTokensRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'ListHiddenTokensResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - putHiddenToken = ( - req: PutHiddenTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('PutHiddenToken'), - createHttpRequest(JsonEncode(req, 'PutHiddenTokenRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'PutHiddenTokenResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } - - deleteHiddenToken = ( - req: DeleteHiddenTokenRequest, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch( - this.url('DeleteHiddenToken'), - createHttpRequest(JsonEncode(req, 'DeleteHiddenTokenRequest'), headers, signal), - ).then( - (res) => { - return buildResponse(res).then((_data) => { - return JsonDecode(_data, 'DeleteHiddenTokenResponse') - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ - cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, - }) - }, - ) - } -} - -const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - return { method: 'POST', headers: reqHeaders, body, signal } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise - -export const JsonEncode = (obj: T, _typ: string = ''): string => { - return JSON.stringify(obj) -} - -export const JsonDecode = (data: string | any, _typ: string = ''): T => { - let parsed: any = data - if (typeof data === 'string') { - try { - parsed = JSON.parse(data) - } catch (err) { - throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) - } - } - return parsed as T -} - -// -// Errors -// - -type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } - -export class WebrpcError extends Error { - code: number - status: number - - constructor(error: WebrpcErrorParams = {}) { - super(error.message) - this.name = error.name || 'WebrpcEndpointError' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) - } -} - -export class WebrpcEndpointError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcEndpoint' - this.code = typeof error.code === 'number' ? error.code : 0 - this.message = error.message || `endpoint error` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcRequestFailed' - this.code = typeof error.code === 'number' ? error.code : -1 - this.message = error.message || `request failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRoute' - this.code = typeof error.code === 'number' ? error.code : -2 - this.message = error.message || `bad route` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadMethod' - this.code = typeof error.code === 'number' ? error.code : -3 - this.message = error.message || `bad method` - this.status = typeof error.status === 'number' ? error.status : 405 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadRequest' - this.code = typeof error.code === 'number' ? error.code : -4 - this.message = error.message || `bad request` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcBadResponse' - this.code = typeof error.code === 'number' ? error.code : -5 - this.message = error.message || `bad response` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcServerPanic' - this.code = typeof error.code === 'number' ? error.code : -6 - this.message = error.message || `server panic` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcInternalError' - this.code = typeof error.code === 'number' ? error.code : -7 - this.message = error.message || `internal error` - this.status = typeof error.status === 'number' ? error.status : 500 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientAbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcClientAborted' - this.code = typeof error.code === 'number' ? error.code : -8 - this.message = error.message || `request aborted by client` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamLost' - this.code = typeof error.code === 'number' ? error.code : -9 - this.message = error.message || `stream lost` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'WebrpcStreamFinished' - this.code = typeof error.code === 'number' ? error.code : -10 - this.message = error.message || `stream finished` - this.status = typeof error.status === 'number' ? error.status : 200 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// -// Schema errors -// - -export class UnauthorizedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unauthorized' - this.code = typeof error.code === 'number' ? error.code : 1000 - this.message = error.message || `Unauthorized access` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnauthorizedError.prototype) - } -} - -export class PermissionDeniedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'PermissionDenied' - this.code = typeof error.code === 'number' ? error.code : 1001 - this.message = error.message || `Permission denied` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, PermissionDeniedError.prototype) - } -} - -export class SessionExpiredError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'SessionExpired' - this.code = typeof error.code === 'number' ? error.code : 1002 - this.message = error.message || `Session expired` - this.status = typeof error.status === 'number' ? error.status : 403 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, SessionExpiredError.prototype) - } -} - -export class MethodNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'MethodNotFound' - this.code = typeof error.code === 'number' ? error.code : 1003 - this.message = error.message || `Method not found` - this.status = typeof error.status === 'number' ? error.status : 404 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, MethodNotFoundError.prototype) - } -} - -export class RequestConflictError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RequestConflict' - this.code = typeof error.code === 'number' ? error.code : 1004 - this.message = error.message || `Conflict with target resource` - this.status = typeof error.status === 'number' ? error.status : 409 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RequestConflictError.prototype) - } -} - -export class AbortedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Aborted' - this.code = typeof error.code === 'number' ? error.code : 1005 - this.message = error.message || `Request aborted` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, AbortedError.prototype) - } -} - -export class GeoblockedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Geoblocked' - this.code = typeof error.code === 'number' ? error.code : 1006 - this.message = error.message || `Geoblocked region` - this.status = typeof error.status === 'number' ? error.status : 451 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, GeoblockedError.prototype) - } -} - -export class RateLimitedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'RateLimited' - this.code = typeof error.code === 'number' ? error.code : 1007 - this.message = error.message || `Rate-limited. Please slow down.` - this.status = typeof error.status === 'number' ? error.status : 429 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, RateLimitedError.prototype) - } -} - -export class ProjectNotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'ProjectNotFound' - this.code = typeof error.code === 'number' ? error.code : 1008 - this.message = error.message || `Project not found` - this.status = typeof error.status === 'number' ? error.status : 401 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) - } -} - -export class InvalidArgumentError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'InvalidArgument' - this.code = typeof error.code === 'number' ? error.code : 2000 - this.message = error.message || `Invalid argument` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class UnavailableError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'Unavailable' - this.code = typeof error.code === 'number' ? error.code : 2002 - this.message = error.message || `Unavailable resource` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnavailableError.prototype) - } -} - -export class QueryFailedError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'QueryFailed' - this.code = typeof error.code === 'number' ? error.code : 2003 - this.message = error.message || `Query failed` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, QueryFailedError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'NotFound' - this.code = typeof error.code === 'number' ? error.code : 3000 - this.message = error.message || `Resource not found` - this.status = typeof error.status === 'number' ? error.status : 400 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export class UnsupportedNetworkError extends WebrpcError { - constructor(error: WebrpcErrorParams = {}) { - super(error) - this.name = error.name || 'UnsupportedNetwork' - this.code = typeof error.code === 'number' ? error.code : 3008 - this.message = error.message || `Unsupported network` - this.status = typeof error.status === 'number' ? error.status : 422 - if (error.cause !== undefined) this.cause = error.cause - Object.setPrototypeOf(this, UnsupportedNetworkError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientAborted = 'WebrpcClientAborted', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', - Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - InvalidArgument = 'InvalidArgument', - Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - NotFound = 'NotFound', - UnsupportedNetwork = 'UnsupportedNetwork', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientAborted = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - Unauthorized = 1000, - PermissionDenied = 1001, - SessionExpired = 1002, - MethodNotFound = 1003, - RequestConflict = 1004, - Aborted = 1005, - Geoblocked = 1006, - RateLimited = 1007, - ProjectNotFound = 1008, - InvalidArgument = 2000, - Unavailable = 2002, - QueryFailed = 2003, - NotFound = 3000, - UnsupportedNetwork = 3008, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientAbortedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, - [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1008]: ProjectNotFoundError, - [2000]: InvalidArgumentError, - [2002]: UnavailableError, - [2003]: QueryFailedError, - [3000]: NotFoundError, - [3008]: UnsupportedNetworkError, -} - -// -// Webrpc -// - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.30.2;gen-typescript@v0.22.2;userdata@v0.1.0' - -type WebrpcGenVersions = { - WebrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - WebrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, WebrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - WebrpcGenVersion: WebrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} diff --git a/packages/services/userdata/tsconfig.json b/packages/services/userdata/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/services/userdata/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/test/package.json b/packages/test/package.json new file mode 100644 index 0000000000..d1c6fb9c19 --- /dev/null +++ b/packages/test/package.json @@ -0,0 +1,118 @@ +{ + "name": "@wagmi/test", + "description": "Test utils for wagmi", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/test" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo", + "test:build": "publint --strict" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "react/**", + "vue/**" + ], + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./react": { + "types": "./dist/types/exports/react.d.ts", + "default": "./dist/esm/exports/react.js" + }, + "./vue": { + "types": "./dist/types/exports/vue.d.ts", + "default": "./dist/esm/exports/vue.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "react": ["./dist/types/exports/react.d.ts"], + "vue": ["./dist/types/exports/vue.d.ts"] + } + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "@tanstack/vue-query": ">=5.0.0", + "@testing-library/react": ">=14.0.0", + "@types/react": ">=18", + "@types/react-dom": ">=18", + "@wagmi/core": "workspace:*", + "@wagmi/vue": "workspace:*", + "prool": "^0.0.23", + "react": ">=18", + "react-dom": ">=18", + "typescript": ">=5.0.4", + "viem": "2.x", + "vue": ">=3", + "wagmi": "workspace:*" + }, + "peerDependenciesMeta": { + "@tanstack/react-query": { + "optional": true + }, + "@tanstack/vue-query": { + "optional": true + }, + "@testing-library/react": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue": { + "optional": true + }, + "wagmi": { + "optional": true + } + }, + "devDependencies": { + "@tanstack/react-query": "catalog:", + "@tanstack/vue-query": "catalog:", + "@testing-library/dom": "catalog:", + "@testing-library/react": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@wagmi/core": "workspace:*", + "@wagmi/vue": "workspace:*", + "react": "catalog:", + "react-dom": "catalog:", + "vue": "catalog:", + "wagmi": "workspace:*" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": ["eth", "ethereum", "dapps", "wallet", "web3"] +} diff --git a/packages/test/src/chains.ts b/packages/test/src/chains.ts new file mode 100644 index 0000000000..af9bdc5d99 --- /dev/null +++ b/packages/test/src/chains.ts @@ -0,0 +1,51 @@ +import type { Compute } from '@wagmi/core/internal' +import { + type Chain as viem_Chain, + mainnet as viem_mainnet, + optimism as viem_optimism, +} from 'viem/chains' + +import { getRpcUrls } from './utils.js' + +type Fork = { blockNumber: bigint; url: string } + +export type Chain = Compute< + viem_Chain & { + fork: Fork + port: number + } +> + +const mainnetFork = { + blockNumber: 19_258_213n, + url: process.env.VITE_MAINNET_FORK_URL ?? 'https://eth.merkle.io', +} as const satisfies Fork + +export const mainnet = { + ...viem_mainnet, + ...getRpcUrls({ port: 8545 }), + fork: mainnetFork, +} as const satisfies Chain + +export const mainnet2 = { + ...viem_mainnet, + ...getRpcUrls({ port: 8546 }), + id: 456, + nativeCurrency: { decimals: 18, name: 'wagmi', symbol: 'WAG' }, + fork: mainnetFork, +} as const satisfies Chain + +export const optimism = { + ...getRpcUrls({ port: 8547 }), + ...viem_optimism, + fork: { + blockNumber: 107_317_577n, + url: process.env.VITE_OPTIMISM_FORK_URL ?? 'https://mainnet.optimism.io', + }, +} as const satisfies Chain + +export const chain = { + mainnet, + mainnet2, + optimism, +} diff --git a/packages/test/src/clients.ts b/packages/test/src/clients.ts new file mode 100644 index 0000000000..5a2d86005a --- /dev/null +++ b/packages/test/src/clients.ts @@ -0,0 +1,62 @@ +import { + http, + type Account, + type Client, + type TestActions, + type TestRpcSchema, + type Transport, + createTestClient, +} from 'viem' + +import { type Chain, mainnet, mainnet2, optimism } from './chains.js' + +export const mainnetTestClient = createTestClient({ + mode: 'anvil', + cacheTime: 0, + chain: mainnet, + transport: http(), +}).extend(wagmiTestMethods) + +export const mainnet2TestClient = createTestClient({ + mode: 'anvil', + cacheTime: 0, + chain: mainnet2, + transport: http(), +}).extend(wagmiTestMethods) + +export const optimismTestClient = createTestClient({ + mode: 'anvil', + cacheTime: 0, + chain: optimism, + transport: http(), +}).extend(wagmiTestMethods) + +export const testClient = { + mainnet: mainnetTestClient, + mainnet2: mainnet2TestClient, + optimism: optimismTestClient, +} + +function wagmiTestMethods( + client: Client< + Transport, + Chain, + Account | undefined, + TestRpcSchema<'anvil'>, + TestActions + >, +) { + return { + /** Resets instance attached to chain. */ + async restart() { + return await fetch(`${client.chain.rpcUrls.default.http[0]}/restart`) + }, + /** Resets fork attached to chain at starting block number. */ + resetFork() { + return client.reset({ + jsonRpcUrl: client.chain.fork.url, + blockNumber: client.chain.fork.blockNumber, + }) + }, + } +} diff --git a/packages/test/src/config.ts b/packages/test/src/config.ts new file mode 100644 index 0000000000..a94cdf45de --- /dev/null +++ b/packages/test/src/config.ts @@ -0,0 +1,29 @@ +import { createConfig, mock } from '@wagmi/core' +import { http } from 'viem' + +import { mainnet, mainnet2, optimism } from './chains.js' +import { accounts } from './constants.js' + +export const config = createConfig({ + chains: [mainnet, mainnet2, optimism], + connectors: [mock({ accounts }), mock({ accounts: reverse(accounts) })], + pollingInterval: 100, + storage: null, + transports: { + [mainnet.id]: http(), + [mainnet2.id]: http(), + [optimism.id]: http(), + }, +}) + +type Reverse< + list extends readonly unknown[], + /// + result extends readonly unknown[] = [], +> = list extends readonly [infer head, ...infer tail] + ? Reverse + : result + +function reverse(list: list): Reverse { + return [...list].reverse() as Reverse +} diff --git a/packages/test/src/constants.ts b/packages/test/src/constants.ts new file mode 100644 index 0000000000..96ca5a9efa --- /dev/null +++ b/packages/test/src/constants.ts @@ -0,0 +1,318 @@ +import { type Address, parseAbi } from 'viem' + +import type { chain } from './chains.js' + +/** + * The id of the current test worker. + * + * This is used by the anvil proxy to route requests to the correct anvil instance. + */ +export const pool = Number(process.env.VITEST_POOL_ID ?? 1) + +// Test accounts +export const accounts = [ + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', + '0x90F79bf6EB2c4f870365E785982E1f101E93b906', + '0x15d34aaf54267db7d7c367839aaf71a00a2c6a65', + '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', + '0x976EA74026E726554dB657fA54763abd0C3a0aa9', + '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', + '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', + '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720', +] as const + +// for `'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'` +export const privateKey = + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + +export let walletConnectProjectId: string +if (process.env.VITE_WC_PROJECT_ID) + walletConnectProjectId = process.env.VITE_WC_PROJECT_ID +else walletConnectProjectId = 'foobarbaz' + +export const typedData = { + basic: { + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000', + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + }, + complex: { + domain: { + name: 'Ether Mail 🥵', + version: '1.1.1', + chainId: 1, + verifyingContract: '0x0000000000000000000000000000000000000000', + }, + types: { + Name: [ + { name: 'first', type: 'string' }, + { name: 'last', type: 'string' }, + ], + Person: [ + { name: 'name', type: 'Name' }, + { name: 'wallet', type: 'address' }, + { name: 'favoriteColors', type: 'string[3]' }, + { name: 'foo', type: 'uint256' }, + { name: 'age', type: 'uint8' }, + { name: 'isCool', type: 'bool' }, + ], + Mail: [ + { name: 'timestamp', type: 'uint256' }, + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + { name: 'hash', type: 'bytes' }, + ], + }, + message: { + timestamp: 1234567890n, + contents: 'Hello, Bob! 🖤', + hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + from: { + name: { + first: 'Cow', + last: 'Burns', + }, + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + age: 69, + foo: 123123123123123123n, + favoriteColors: ['red', 'green', 'blue'], + isCool: false, + }, + to: { + name: { first: 'Bob', last: 'Builder' }, + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + age: 70, + foo: 123123123123123123n, + favoriteColors: ['orange', 'yellow', 'green'], + isCool: true, + }, + }, + }, +} as const + +export const abi = { + erc20: parseAbi([ + 'event Approval(address indexed owner, address indexed spender, uint256 value)', + 'event Transfer(address indexed from, address indexed to, uint256 value)', + 'function allowance(address owner, address spender) view returns (uint256)', + 'function approve(address spender, uint256 amount) returns (bool)', + 'function balanceOf(address account) view returns (uint256)', + 'function decimals() view returns (uint8)', + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function transfer(address recipient, uint256 amount) returns (bool)', + 'function transferFrom(address sender, address recipient, uint256 amount) returns (bool)', + ]), + mloot: parseAbi([ + 'constructor()', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', + 'event ApprovalForAll(address indexed owner, address indexed operator, bool approved)', + 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'function approve(address to, uint256 tokenId)', + 'function balanceOf(address owner) view returns (uint256)', + 'function claim(uint256 tokenId)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function getChest(uint256 tokenId) view returns (string)', + 'function getFoot(uint256 tokenId) view returns (string)', + 'function getHand(uint256 tokenId) view returns (string)', + 'function getHead(uint256 tokenId) view returns (string)', + 'function getNeck(uint256 tokenId) view returns (string)', + 'function getRing(uint256 tokenId) view returns (string)', + 'function getWaist(uint256 tokenId) view returns (string)', + 'function getWeapon(uint256 tokenId) view returns (string)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function name() view returns (string)', + 'function owner() view returns (address)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function renounceOwnership()', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)', + 'function setApprovalForAll(address operator, bool approved)', + 'function supportsInterface(bytes4 interfaceId) view returns (bool)', + 'function symbol() view returns (string)', + 'function tokenByIndex(uint256 index) view returns (uint256)', + 'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)', + 'function tokenURI(uint256 tokenId) view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function transferFrom(address from, address to, uint256 tokenId)', + 'function transferOwnership(address newOwner)', + ]), + shields: parseAbi([ + 'constructor(string name_, string symbol_, address _emblemWeaver, address makerBadgeRecipient, address granteeBadgeRecipient)', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', + 'event ApprovalForAll(address indexed owner, address indexed operator, bool approved)', + 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', + 'event ShieldBuilt(uint256 tokenId, uint16 field, uint16 hardware, uint16 frame, uint24[4] colors, uint8 shieldBadge)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'function approve(address to, uint256 tokenId)', + 'function balanceOf(address owner) view returns (uint256)', + 'function build(uint16 field, uint16 hardware, uint16 frame, uint24[4] colors, uint256 tokenId) payable', + 'function collectFees()', + 'function emblemWeaver() view returns (address)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function mint(address to, uint8 count) payable', + 'function mythicFee() view returns (uint256)', + 'function name() view returns (string)', + 'function owner() view returns (address)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function publicMintActive() view returns (bool)', + 'function publicMintPrice() view returns (uint256)', + 'function renounceOwnership()', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)', + 'function setApprovalForAll(address operator, bool approved)', + 'function setPublicMintActive()', + 'function setPublicMintPrice(uint256 _publicMintPrice)', + 'function shieldHashes(bytes32) view returns (bool)', + 'function shields(uint256 tokenId) view returns (uint16 field, uint16 hardware, uint16 frame, uint24 color1, uint24 color2, uint24 color3, uint24 color4, uint8 shieldBadge)', + 'function specialFee() view returns (uint256)', + 'function supportsInterface(bytes4 interfaceId) view returns (bool)', + 'function symbol() view returns (string)', + 'function tokenURI(uint256 tokenId) view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function transferFrom(address from, address to, uint256 tokenId)', + 'function transferOwnership(address newOwner)', + ]), + wagmigotchi: parseAbi([ + 'constructor()', + 'event CaretakerLoved(address indexed caretaker, uint256 indexed amount)', + 'function clean()', + 'function feed()', + 'function getAlive() view returns (bool)', + 'function getBoredom() view returns (uint256)', + 'function getHunger() view returns (uint256)', + 'function getSleepiness() view returns (uint256)', + 'function getStatus() view returns (string)', + 'function getUncleanliness() view returns (uint256)', + 'function love(address) view returns (uint256)', + 'function play()', + 'function sleep()', + ]), + wagmiMintExample: parseAbi([ + 'constructor()', + 'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)', + 'event ApprovalForAll(address indexed owner, address indexed operator, bool approved)', + 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)', + 'function approve(address to, uint256 tokenId)', + 'function balanceOf(address owner) view returns (uint256)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function mint()', + 'function name() view returns (string)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)', + 'function setApprovalForAll(address operator, bool approved)', + 'function supportsInterface(bytes4 interfaceId) view returns (bool)', + 'function symbol() view returns (string)', + 'function tokenURI(uint256 tokenId) pure returns (string)', + 'function totalSupply() view returns (uint256)', + 'function transferFrom(address from, address to, uint256 tokenId)', + ]), + viewOverloads: parseAbi([ + 'function foo() view returns (int8)', + 'function foo(address) view returns (string)', + 'function foo(address, address) view returns ((address foo, address bar))', + 'function bar() view returns (int8)', + ]), + writeOverloads: parseAbi([ + 'function foo() payable returns (int8)', + 'function foo(address) returns (string)', + 'function foo(address, address) returns ((address foo, address bar))', + 'function bar() payable returns (int8)', + ]), + bayc: parseAbi([ + 'constructor(string name, string symbol, uint256 maxNftSupply, uint256 saleStart)', + 'function mintApe(uint256 numberOfTokens) payable', + 'function reserveApes()', + 'function flipSaleState()', + 'function emergencySetStartingIndexBlock()', + 'function setStartingIndex()', + 'function setRevealTimestamp(uint256 revealTimeStamp)', + 'function setProvenanceHash(string provenanceHash)', + 'function setBaseURI(string baseURI)', + 'function startingIndex() view returns (uint256)', + 'function startingIndexBlock() view returns (uint256)', + 'function saleIsActive() view returns (bool)', + 'function maxApePurchase() view returns (uint256)', + 'function apePrice() view returns (uint256)', + 'function REVEAL_TIMESTAMP() view returns (uint256)', + 'function MAX_APES() view returns (uint256)', + 'function BAYC_PROVENANCE() view returns (string)', + 'function name() view returns (string)', + 'function symbol() view returns (string)', + 'function totalSupply() view returns (uint256)', + 'function balanceOf(address owner) view returns (uint256)', + 'function ownerOf(uint256 tokenId) view returns (address)', + 'function getApproved(uint256 tokenId) view returns (address)', + 'function isApprovedForAll(address owner, address operator) view returns (bool)', + 'function approve(address to, uint256 tokenId)', + 'function setApprovalForAll(address operator, bool approved)', + 'function safeTransferFrom(address from, address to, uint256 tokenId)', + 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)', + 'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)', + 'function tokenByIndex(uint256 index) view returns (uint256)', + 'function tokenURI(uint256 tokenId) view returns (string)', + 'function supportsInterface(bytes4 interfaceId) view returns (bool)', + 'function transferFrom(address from, address to, uint256 tokenId)', + 'function renounceOwnership()', + 'function transferOwnership(address newOwner)', + 'function withdraw()', + ]), +} as const + +const mainnetAddress = { + mloot: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + shields: '0x0747118c9f44c7a23365b2476dcd05e03114c747', + usdc: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + usdcHolder: '0x5414d89a8bf7e99d732bc52f3e6a3ef461c0c078', + wagmigotchi: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + wagmiMintExample: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', +} as const + +export const address = { + ...mainnetAddress, + mainnet: mainnetAddress, + mainnet2: mainnetAddress, + optimism: { + usdc: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', + }, +} as const satisfies Record & + Record> + +export const bytecode = { + bayc: '0x608060405260405180602001604052806000815250600b90805190602001906200002b92919062000484565b506000600f60006101000a81548160ff0219169083151502179055503480156200005457600080fd5b50604051620046d0380380620046d0833981810160405260808110156200007a57600080fd5b81019080805160405193929190846401000000008211156200009b57600080fd5b83820191506020820185811115620000b257600080fd5b8251866001820283011164010000000082111715620000d057600080fd5b8083526020830192505050908051906020019080838360005b8381101562000106578082015181840152602081019050620000e9565b50505050905090810190601f168015620001345780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200015857600080fd5b838201915060208201858111156200016f57600080fd5b82518660018202830111640100000000821117156200018d57600080fd5b8083526020830192505050908051906020019080838360005b83811015620001c3578082015181840152602081019050620001a6565b50505050905090810190601f168015620001f15780820380516001836020036101000a031916815260200191505b5060405260200180519060200190929190805190602001909291905050508383620002296301ffc9a760e01b6200037360201b60201c565b81600690805190602001906200024192919062000484565b5080600790805190602001906200025a92919062000484565b50620002736380ac58cd60e01b6200037360201b60201c565b6200028b635b5e139f60e01b6200037360201b60201c565b620002a363780e9d6360e01b6200037360201b60201c565b50506000620002b76200047c60201b60201c565b905080600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35081600e81905550620bdd808101601081905550505050506200052a565b63ffffffff60e01b817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916141562000410576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4552433136353a20696e76616c696420696e746572666163652069640000000081525060200191505060405180910390fd5b6001600080837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060006101000a81548160ff02191690831515021790555050565b600033905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10620004c757805160ff1916838001178555620004f8565b82800160010185558215620004f8579182015b82811115620004f7578251825591602001919060010190620004da565b5b5090506200050791906200050b565b5090565b5b80821115620005265760008160009055506001016200050c565b5090565b614196806200053a6000396000f3fe60806040526004361061021a5760003560e01c80636c0360eb11610123578063b0f67427116100ab578063e36d64981161006f578063e36d649814610ddf578063e985e9c514610e0a578063e986655014610e91578063eb8d244414610ea8578063f2fde38b14610ed55761021a565b8063b0f6742714610bac578063b88d4fde14610bc3578063bb8a16bd14610cd5578063c87b56dd14610d00578063cb774d4714610db45761021a565b80637d17fcbe116100f25780637d17fcbe14610a395780638da5cb5b14610a5057806395d89b4114610a91578063a22cb46514610b21578063a723533e14610b7e5761021a565b80636c0360eb1461090257806370a0823114610992578063715018a6146109f75780637a3f451e14610a0e5761021a565b80632f745c59116101a65780634f6ccce7116101755780634f6ccce7146106cb57806355f804b31461071a578063571dff3b146107e2578063607e20e31461080d5780636352211e1461089d5761021a565b80632f745c59146105b357806334918dfd146106225780633ccfd60b1461063957806342842e0e146106505761021a565b8063095ea7b3116101ed578063095ea7b3146103bf578063109695231461041a57806318160ddd146104e257806318e20a381461050d57806323b872dd146105385761021a565b8063018a2c371461021f57806301ffc9a71461025a57806306fdde03146102ca578063081812fc1461035a575b600080fd5b34801561022b57600080fd5b506102586004803603602081101561024257600080fd5b8101908080359060200190929190505050610f26565b005b34801561026657600080fd5b506102b26004803603602081101561027d57600080fd5b8101908080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610fdf565b60405180821515815260200191505060405180910390f35b3480156102d657600080fd5b506102df611046565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561031f578082015181840152602081019050610304565b50505050905090810190601f16801561034c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561036657600080fd5b506103936004803603602081101561037d57600080fd5b81019080803590602001909291905050506110e8565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156103cb57600080fd5b50610418600480360360408110156103e257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611183565b005b34801561042657600080fd5b506104e06004803603602081101561043d57600080fd5b810190808035906020019064010000000081111561045a57600080fd5b82018360208201111561046c57600080fd5b8035906020019184600183028401116401000000008311171561048e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506112c7565b005b3480156104ee57600080fd5b506104f7611390565b6040518082815260200191505060405180910390f35b34801561051957600080fd5b506105226113a1565b6040518082815260200191505060405180910390f35b34801561054457600080fd5b506105b16004803603606081101561055b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506113a7565b005b3480156105bf57600080fd5b5061060c600480360360408110156105d657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061141d565b6040518082815260200191505060405180910390f35b34801561062e57600080fd5b50610637611478565b005b34801561064557600080fd5b5061064e611553565b005b34801561065c57600080fd5b506106c96004803603606081101561067357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611651565b005b3480156106d757600080fd5b50610704600480360360208110156106ee57600080fd5b8101908080359060200190929190505050611671565b6040518082815260200191505060405180910390f35b34801561072657600080fd5b506107e06004803603602081101561073d57600080fd5b810190808035906020019064010000000081111561075a57600080fd5b82018360208201111561076c57600080fd5b8035906020019184600183028401116401000000008311171561078e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611694565b005b3480156107ee57600080fd5b506107f761174f565b6040518082815260200191505060405180910390f35b34801561081957600080fd5b50610822611754565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610862578082015181840152602081019050610847565b50505050905090810190601f16801561088f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108a957600080fd5b506108d6600480360360208110156108c057600080fd5b81019080803590602001909291905050506117f2565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561090e57600080fd5b50610917611829565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561095757808201518184015260208101905061093c565b50505050905090810190601f1680156109845780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561099e57600080fd5b506109e1600480360360208110156109b557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506118cb565b6040518082815260200191505060405180910390f35b348015610a0357600080fd5b50610a0c6119a0565b005b348015610a1a57600080fd5b50610a23611b10565b6040518082815260200191505060405180910390f35b348015610a4557600080fd5b50610a4e611b1c565b005b348015610a5c57600080fd5b50610a65611c4c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b348015610a9d57600080fd5b50610aa6611c76565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610ae6578082015181840152602081019050610acb565b50505050905090810190601f168015610b135780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610b2d57600080fd5b50610b7c60048036036040811015610b4457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803515159060200190929190505050611d18565b005b610baa60048036036020811015610b9457600080fd5b8101908080359060200190929190505050611ece565b005b348015610bb857600080fd5b50610bc1612127565b005b348015610bcf57600080fd5b50610cd360048036036080811015610be657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610c4d57600080fd5b820183602082011115610c5f57600080fd5b80359060200191846001830284011164010000000083111715610c8157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061220b565b005b348015610ce157600080fd5b50610cea612283565b6040518082815260200191505060405180910390f35b348015610d0c57600080fd5b50610d3960048036036020811015610d2357600080fd5b8101908080359060200190929190505050612289565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610d79578082015181840152602081019050610d5e565b50505050905090810190601f168015610da65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610dc057600080fd5b50610dc961255a565b6040518082815260200191505060405180910390f35b348015610deb57600080fd5b50610df4612560565b6040518082815260200191505060405180910390f35b348015610e1657600080fd5b50610e7960048036036040811015610e2d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612566565b60405180821515815260200191505060405180910390f35b348015610e9d57600080fd5b50610ea66125fa565b005b348015610eb457600080fd5b50610ebd612764565b60405180821515815260200191505060405180910390f35b348015610ee157600080fd5b50610f2460048036036020811015610ef857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612777565b005b610f2e61296c565b73ffffffffffffffffffffffffffffffffffffffff16610f4c611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614610fd5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b8060108190555050565b6000806000837bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200190815260200160002060009054906101000a900460ff169050919050565b606060068054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156110de5780601f106110b3576101008083540402835291602001916110de565b820191906000526020600020905b8154815290600101906020018083116110c157829003601f168201915b5050505050905090565b60006110f382612974565b611148576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061408b602c913960400191505060405180910390fd5b6004600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b600061118e826117f2565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611215576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061410f6021913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1661123461296c565b73ffffffffffffffffffffffffffffffffffffffff16148061126357506112628161125d61296c565b612566565b5b6112b8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526038815260200180613f956038913960400191505060405180910390fd5b6112c28383612991565b505050565b6112cf61296c565b73ffffffffffffffffffffffffffffffffffffffff166112ed611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611376576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b80600b908051906020019061138c929190613de6565b5050565b600061139c6002612a4a565b905090565b60105481565b6113b86113b261296c565b82612a5f565b61140d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260318152602001806141306031913960400191505060405180910390fd5b611418838383612b53565b505050565b600061147082600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d9690919063ffffffff16565b905092915050565b61148061296c565b73ffffffffffffffffffffffffffffffffffffffff1661149e611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611527576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600f60009054906101000a900460ff1615600f60006101000a81548160ff021916908315150217905550565b61155b61296c565b73ffffffffffffffffffffffffffffffffffffffff16611579611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611602576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b60004790503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801561164d573d6000803e3d6000fd5b5050565b61166c8383836040518060200160405280600081525061220b565b505050565b600080611688836002612db090919063ffffffff16565b50905080915050919050565b61169c61296c565b73ffffffffffffffffffffffffffffffffffffffff166116ba611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611743576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b61174c81612ddc565b50565b601481565b600b8054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156117ea5780601f106117bf576101008083540402835291602001916117ea565b820191906000526020600020905b8154815290600101906020018083116117cd57829003601f168201915b505050505081565b600061182282604051806060016040528060298152602001613ff7602991396002612df69092919063ffffffff16565b9050919050565b606060098054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156118c15780601f10611896576101008083540402835291602001916118c1565b820191906000526020600020905b8154815290600101906020018083116118a457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611952576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180613fcd602a913960400191505060405180910390fd5b611999600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612e15565b9050919050565b6119a861296c565b73ffffffffffffffffffffffffffffffffffffffff166119c6611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611a4f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a36000600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b67011c37937e08000081565b611b2461296c565b73ffffffffffffffffffffffffffffffffffffffff16611b42611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614611bcb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b6000600d5414611c43576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f5374617274696e6720696e64657820697320616c72656164792073657400000081525060200191505060405180910390fd5b43600c81905550565b6000600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b606060078054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611d0e5780601f10611ce357610100808354040283529160200191611d0e565b820191906000526020600020905b815481529060010190602001808311611cf157829003601f168201915b5050505050905090565b611d2061296c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611dc1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f4552433732313a20617070726f766520746f2063616c6c65720000000000000081525060200191505060405180910390fd5b8060056000611dce61296c565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff16611e7b61296c565b73ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b600f60009054906101000a900460ff16611f50576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f53616c65206d7573742062652061637469766520746f206d696e74204170650081525060200191505060405180910390fd5b6014811115611faa576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180613f746021913960400191505060405180910390fd5b600e54611fc782611fb9611390565b612e2a90919063ffffffff16565b111561201e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806140426028913960400191505060405180910390fd5b3461203a8267011c37937e080000612eb290919063ffffffff16565b11156120ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45746865722076616c75652073656e74206973206e6f7420636f72726563740081525060200191505060405180910390fd5b60005b818110156120ef5760006120c3611390565b9050600e546120d0611390565b10156120e1576120e03382612f38565b5b5080806001019150506120b1565b506000600c541480156121175750600e54612108611390565b148061211657506010544210155b5b156121245743600c819055505b50565b61212f61296c565b73ffffffffffffffffffffffffffffffffffffffff1661214d611c4c565b73ffffffffffffffffffffffffffffffffffffffff16146121d6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b60006121e0611390565b905060005b601e811015612207576121fa33828401612f38565b80806001019150506121e5565b5050565b61221c61221661296c565b83612a5f565b612271576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260318152602001806141306031913960400191505060405180910390fd5b61227d84848484612f56565b50505050565b600e5481565b606061229482612974565b6122e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806140e0602f913960400191505060405180910390fd5b6060600860008481526020019081526020016000208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156123925780601f1061236757610100808354040283529160200191612392565b820191906000526020600020905b81548152906001019060200180831161237557829003601f168201915b5050505050905060606123a3611829565b90506000815114156123b9578192505050612555565b60008251111561248a5780826040516020018083805190602001908083835b602083106123fb57805182526020820191506020810190506020830392506123d8565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b6020831061244c5780518252602082019150602081019050602083039250612429565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050612555565b8061249485612fc8565b6040516020018083805190602001908083835b602083106124ca57805182526020820191506020810190506020830392506124a7565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b6020831061251b57805182526020820191506020810190506020830392506124f8565b6001836020036101000a03801982511681845116808217855250505050505090500192505050604051602081830303815290604052925050505b919050565b600d5481565b600c5481565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16905092915050565b6000600d5414612672576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f5374617274696e6720696e64657820697320616c72656164792073657400000081525060200191505060405180910390fd5b6000600c5414156126eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f5374617274696e6720696e64657820626c6f636b206d7573742062652073657481525060200191505060405180910390fd5b600e54600c544060001c816126fc57fe5b06600d8190555060ff61271a600c544361310f90919063ffffffff16565b111561273a57600e54600143034060001c8161273257fe5b06600d819055505b6000600d5414156127625761275b6001600d54612e2a90919063ffffffff16565b600d819055505b565b600f60009054906101000a900460ff1681565b61277f61296c565b73ffffffffffffffffffffffffffffffffffffffff1661279d611c4c565b73ffffffffffffffffffffffffffffffffffffffff1614612826576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657281525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156128ac576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180613ed86026913960400191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a380600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600033905090565b600061298a82600261319290919063ffffffff16565b9050919050565b816004600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808273ffffffffffffffffffffffffffffffffffffffff16612a04836117f2565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000612a58826000016131ac565b9050919050565b6000612a6a82612974565b612abf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180613f48602c913960400191505060405180910390fd5b6000612aca836117f2565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480612b3957508373ffffffffffffffffffffffffffffffffffffffff16612b21846110e8565b73ffffffffffffffffffffffffffffffffffffffff16145b80612b4a5750612b498185612566565b5b91505092915050565b8273ffffffffffffffffffffffffffffffffffffffff16612b73826117f2565b73ffffffffffffffffffffffffffffffffffffffff1614612bdf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806140b76029913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415612c65576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180613efe6024913960400191505060405180910390fd5b612c708383836131bd565b612c7b600082612991565b612ccc81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131c290919063ffffffff16565b50612d1e81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131dc90919063ffffffff16565b50612d35818360026131f69092919063ffffffff16565b50808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050565b6000612da5836000018361322b565b60001c905092915050565b600080600080612dc386600001866132ae565b915091508160001c8160001c9350935050509250929050565b8060099080519060200190612df2929190613de6565b5050565b6000612e09846000018460001b84613347565b60001c90509392505050565b6000612e238260000161343d565b9050919050565b600080828401905083811015612ea8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b600080831415612ec55760009050612f32565b6000828402905082848281612ed657fe5b0414612f2d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061406a6021913960400191505060405180910390fd5b809150505b92915050565b612f5282826040518060200160405280600081525061344e565b5050565b612f61848484612b53565b612f6d848484846134bf565b612fc2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180613ea66032913960400191505060405180910390fd5b50505050565b60606000821415613010576040518060400160405280600181526020017f3000000000000000000000000000000000000000000000000000000000000000815250905061310a565b600082905060005b6000821461303a578080600101915050600a828161303257fe5b049150613018565b60608167ffffffffffffffff8111801561305357600080fd5b506040519080825280601f01601f1916602001820160405280156130865781602001600182028036833780820191505090505b50905060006001830390508593505b6000841461310257600a84816130a757fe5b0660300160f81b828280600190039350815181106130c157fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600a84816130fa57fe5b049350613095565b819450505050505b919050565b600082821115613187576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525060200191505060405180910390fd5b818303905092915050565b60006131a4836000018360001b6136d8565b905092915050565b600081600001805490509050919050565b505050565b60006131d4836000018360001b6136fb565b905092915050565b60006131ee836000018360001b6137e3565b905092915050565b6000613222846000018460001b8473ffffffffffffffffffffffffffffffffffffffff1660001b613853565b90509392505050565b60008183600001805490501161328c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180613e846022913960400191505060405180910390fd5b82600001828154811061329b57fe5b9060005260206000200154905092915050565b60008082846000018054905011613310576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806140206022913960400191505060405180910390fd5b600084600001848154811061332157fe5b906000526020600020906002020190508060000154816001015492509250509250929050565b6000808460010160008581526020019081526020016000205490506000811415839061340e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156133d35780820151818401526020810190506133b8565b50505050905090810190601f1680156134005780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5084600001600182038154811061342157fe5b9060005260206000209060020201600101549150509392505050565b600081600001805490509050919050565b613458838361392f565b61346560008484846134bf565b6134ba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180613ea66032913960400191505060405180910390fd5b505050565b60006134e08473ffffffffffffffffffffffffffffffffffffffff16613b23565b6134ed57600190506136d0565b606061365763150b7a0260e01b61350261296c565b888787604051602401808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561358657808201518184015260208101905061356b565b50505050905090810190601f1680156135b35780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051806060016040528060328152602001613ea6603291398773ffffffffffffffffffffffffffffffffffffffff16613b369092919063ffffffff16565b9050600081806020019051602081101561367057600080fd5b8101908080519060200190929190505050905063150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614925050505b949350505050565b600080836001016000848152602001908152602001600020541415905092915050565b600080836001016000848152602001908152602001600020549050600081146137d7576000600182039050600060018660000180549050039050600086600001828154811061374657fe5b906000526020600020015490508087600001848154811061376357fe5b906000526020600020018190555060018301876001016000838152602001908152602001600020819055508660000180548061379b57fe5b600190038181906000526020600020016000905590558660010160008781526020019081526020016000206000905560019450505050506137dd565b60009150505b92915050565b60006137ef8383613b4e565b61384857826000018290806001815401808255809150506001900390600052602060002001600090919091909150558260000180549050836001016000848152602001908152602001600020819055506001905061384d565b600090505b92915050565b60008084600101600085815260200190815260200160002054905060008114156138fa57846000016040518060400160405280868152602001858152509080600181540180825580915050600190039060005260206000209060020201600090919091909150600082015181600001556020820151816001015550508460000180549050856001016000868152602001908152602001600020819055506001915050613928565b8285600001600183038154811061390d57fe5b90600052602060002090600202016001018190555060009150505b9392505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156139d2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260208152602001807f4552433732313a206d696e7420746f20746865207a65726f206164647265737381525060200191505060405180910390fd5b6139db81612974565b15613a4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601c8152602001807f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000081525060200191505060405180910390fd5b613a5a600083836131bd565b613aab81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206131dc90919063ffffffff16565b50613ac2818360026131f69092919063ffffffff16565b50808273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a45050565b600080823b905060008111915050919050565b6060613b458484600085613b71565b90509392505050565b600080836001016000848152602001908152602001600020541415905092915050565b606082471015613bcc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180613f226026913960400191505060405180910390fd5b613bd585613b23565b613c47576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601d8152602001807f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000081525060200191505060405180910390fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b60208310613c975780518252602082019150602081019050602083039250613c74565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114613cf9576040519150601f19603f3d011682016040523d82523d6000602084013e613cfe565b606091505b5091509150613d0e828286613d1a565b92505050949350505050565b60608315613d2a57829050613ddf565b600083511115613d3d5782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613da4578082015181840152602081019050613d89565b50505050905090810190601f168015613dd15780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613e2757805160ff1916838001178555613e55565b82800160010185558215613e55579182015b82811115613e54578251825591602001919060010190613e39565b5b509050613e629190613e66565b5090565b5b80821115613e7f576000816000905550600101613e67565b509056fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e64734552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573734552433732313a207472616e7366657220746f20746865207a65726f2061646472657373416464726573733a20696e73756666696369656e742062616c616e636520666f722063616c6c4552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e43616e206f6e6c79206d696e7420323020746f6b656e7320617420612074696d654552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e456e756d657261626c654d61703a20696e646578206f7574206f6620626f756e6473507572636861736520776f756c6420657863656564206d617820737570706c79206f662041706573536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f774552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564a2646970667358221220b0e64d1fa6c4dbeb9c6f54607d7e1996943fe27624a80652f57b53fda084621b64736f6c63430007000033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000006080e6d70000000000000000000000000000000000000000000000000000000000000011426f7265644170655961636874436c756200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044241594300000000000000000000000000000000000000000000000000000000', + wagmiMintExample: + '0x608060405260006007553480156200001657600080fd5b50604051806040016040528060058152602001647761676d6960d81b815250604051806040016040528060058152602001645741474d4960d81b81525081600090805190602001906200006b9291906200008a565b508051620000819060019060208401906200008a565b5050506200016c565b828054620000989062000130565b90600052602060002090601f016020900481019282620000bc576000855562000107565b82601f10620000d757805160ff191683800117855562000107565b8280016001018555821562000107579182015b8281111562000107578251825591602001919060010190620000ea565b506200011592915062000119565b5090565b5b808211156200011557600081556001016200011a565b600181811c908216806200014557607f821691505b6020821081036200016657634e487b7160e01b600052602260045260246000fd5b50919050565b6128c2806200017c6000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c80636352211e11610097578063a22cb46511610066578063a22cb46514610215578063b88d4fde14610228578063c87b56dd1461023b578063e985e9c51461024e57600080fd5b80636352211e146101d457806370a08231146101e757806395d89b41146101fa578063a0712d681461020257600080fd5b80631249c58b116100d35780631249c58b1461018f57806318160ddd1461019757806323b872dd146101ae57806342842e0e146101c157600080fd5b806301ffc9a71461010557806306fdde031461012d578063081812fc14610142578063095ea7b31461017a575b600080fd5b61011861011336600461178f565b610297565b60405190151581526020015b60405180910390f35b61013561037c565b6040516101249190611829565b61015561015036600461183c565b61040e565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b61018d61018836600461187e565b6104d3565b005b61018d61062b565b6101a060065481565b604051908152602001610124565b61018d6101bc3660046118a8565b61067d565b61018d6101cf3660046118a8565b610704565b6101556101e236600461183c565b61071f565b6101a06101f53660046118e4565b6107b7565b61013561086b565b61018d61021036600461183c565b61087a565b61018d6102233660046118ff565b610902565b61018d61023636600461196a565b610911565b61013561024936600461183c565b61099f565b61011861025c366004611a64565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd00000000000000000000000000000000000000000000000000000000148061032a57507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b8061037657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60606000805461038b90611a97565b80601f01602080910402602001604051908101604052809291908181526020018280546103b790611a97565b80156104045780601f106103d957610100808354040283529160200191610404565b820191906000526020600020905b8154815290600101906020018083116103e757829003601f168201915b5050505050905090565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff166104aa5760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a20617070726f76656420717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006104de8261071f565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105815760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084016104a1565b3373ffffffffffffffffffffffffffffffffffffffff821614806105aa57506105aa813361025c565b61061c5760405162461bcd60e51b815260206004820152603860248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f74206f7760448201527f6e6572206e6f7220617070726f76656420666f7220616c6c000000000000000060648201526084016104a1565b6106268383610b07565b505050565b6007545b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156106615760010161062f565b61066b3382610ba7565b60068054600190810190915501600755565b6106873382610bc1565b6106f95760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b610626838383610d17565b61062683838360405180602001604052806000815250610911565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16806103765760405162461bcd60e51b815260206004820152602960248201527f4552433732313a206f776e657220717565727920666f72206e6f6e657869737460448201527f656e7420746f6b656e000000000000000000000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff82166108425760405162461bcd60e51b815260206004820152602a60248201527f4552433732313a2062616c616e636520717565727920666f7220746865207a6560448201527f726f20616464726573730000000000000000000000000000000000000000000060648201526084016104a1565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461038b90611a97565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156108ec5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e2049442069732074616b656e00000000000000000000000000000060448201526064016104a1565b6108f63382610ba7565b50600680546001019055565b61090d338383610f4a565b5050565b61091b3383610bc1565b61098d5760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b6109998484848461105d565b50505050565b6040517f666f726567726f756e64000000000000000000000000000000000000000000006020820152602a810182905260609060009061016890604a016040516020818303038152906040528051906020012060001c6109ff9190611b19565b6040517f6261636b67726f756e64000000000000000000000000000000000000000000006020820152602a810185905290915060009061016890604a016040516020818303038152906040528051906020012060001c610a5f9190611b19565b90506000610aba610a6f866110e6565b610aa9610a7b866110e6565b610a84866110e6565b604051602001610a95929190611b2d565b60405160208183030381529060405261121b565b604051602001610a959291906125ba565b9050600081604051602001610acf919061268b565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529695505050505050565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091558190610b618261071f565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b61090d82826040518060200160405280600081525061136e565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16610c585760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a206f70657261746f7220717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084016104a1565b6000610c638361071f565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610cd1575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b80610d0f57508373ffffffffffffffffffffffffffffffffffffffff16610cf78461040e565b73ffffffffffffffffffffffffffffffffffffffff16145b949350505050565b8273ffffffffffffffffffffffffffffffffffffffff16610d378261071f565b73ffffffffffffffffffffffffffffffffffffffff1614610dc05760405162461bcd60e51b815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e657200000000000000000000000000000000000000000000000000000060648201526084016104a1565b73ffffffffffffffffffffffffffffffffffffffff8216610e485760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016104a1565b610e53600082610b07565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120805460019290610e899084906126ff565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290610ec4908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610fc55760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c65720000000000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b611068848484610d17565b611074848484846113f7565b6109995760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b60608160000361112957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115611153578061113d8161272e565b915061114c9050600a83612766565b915061112d565b60008167ffffffffffffffff81111561116e5761116e61193b565b6040519080825280601f01601f191660200182016040528015611198576020820181803683370190505b5090505b8415610d0f576111ad6001836126ff565b91506111ba600a86611b19565b6111c5906030612716565b60f81b8183815181106111da576111da61277a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350611214600a86612766565b945061119c565b6060815160000361123a57505060408051602081019091526000815290565b600060405180606001604052806040815260200161284d60409139905060006003845160026112699190612716565b6112739190612766565b61127e9060046127a9565b67ffffffffffffffff8111156112965761129661193b565b6040519080825280601f01601f1916602001820160405280156112c0576020820181803683370190505b509050600182016020820185865187015b8082101561132c576003820191508151603f8160121c168501518453600184019350603f81600c1c168501518453600184019350603f8160061c168501518453600184019350603f81168501518453506001830192506112d1565b5050600386510660018114611348576002811461135b57611363565b603d6001830353603d6002830353611363565b603d60018303535b509195945050505050565b61137883836115d0565b61138560008484846113f7565b6106265760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff84163b156115c5576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a029061146e9033908990889088906004016127e6565b6020604051808303816000875af19250505080156114c7575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526114c49181019061282f565b60015b61157a573d8080156114f5576040519150601f19603f3d011682016040523d82523d6000602084013e6114fa565b606091505b5080516000036115725760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050610d0f565b506001949350505050565b73ffffffffffffffffffffffffffffffffffffffff82166116335760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016104a1565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156116a55760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054600192906116db908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461178c57600080fd5b50565b6000602082840312156117a157600080fd5b81356117ac8161175e565b9392505050565b60005b838110156117ce5781810151838201526020016117b6565b838111156109995750506000910152565b600081518084526117f78160208601602086016117b3565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117ac60208301846117df565b60006020828403121561184e57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461187957600080fd5b919050565b6000806040838503121561189157600080fd5b61189a83611855565b946020939093013593505050565b6000806000606084860312156118bd57600080fd5b6118c684611855565b92506118d460208501611855565b9150604084013590509250925092565b6000602082840312156118f657600080fd5b6117ac82611855565b6000806040838503121561191257600080fd5b61191b83611855565b91506020830135801515811461193057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561198057600080fd5b61198985611855565b935061199760208601611855565b925060408501359150606085013567ffffffffffffffff808211156119bb57600080fd5b818701915087601f8301126119cf57600080fd5b8135818111156119e1576119e161193b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611a2757611a2761193b565b816040528281528a6020848701011115611a4057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060408385031215611a7757600080fd5b611a8083611855565b9150611a8e60208401611855565b90509250929050565b600181811c90821680611aab57607f821691505b602082108103611ae4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082611b2857611b28611aea565b500690565b7f3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323081527f30302f737667222077696474683d223130323422206865696768743d2231303260208201527f34222066696c6c3d226e6f6e65223e3c706174682066696c6c3d2268736c2800604082015260008351611bb181605f8501602088016117b3565b7f2c20313030252c20313025292220643d224d3020306831303234763130323448605f918401918201527f307a22202f3e3c672066696c6c3d2268736c2800000000000000000000000000607f8201528351611c148160928401602088016117b3565b7f2c20313030252c2039302529223e3c7061746820643d224d393033203433372e609292909101918201527f35633020392e3131332d372e3338382031362e352d31362e352031362e35732d60b28201527f31362e352d372e3338372d31362e352d31362e3520372e3338382d31362e352060d28201527f31362e352d31362e352031362e3520372e3338372031362e352031362e357a4d60f28201527f3639382e3532392035363663362e39323120302031322e35332d352e353936206101128201527f31322e35332d31322e35762d353063302d362e39303420352e3630392d31322e6101328201527f352031322e3532392d31322e356832352e30353963362e393220302031322e356101528201527f323920352e3539362031322e3532392031322e35763530633020362e393034206101728201527f352e3630392031322e352031322e35332031322e357331322e3532392d352e356101928201527f39362031322e3532392d31322e35762d353063302d362e39303420352e3630396101b28201527f2d31322e352031322e35332d31322e356832352e30353963362e3932203020316101d28201527f322e35323920352e3539362031322e3532392031322e35763530633020362e396101f28201527f303420352e3630392031322e352031322e3532392031322e356833372e3538396102128201527f63362e393220302031322e3532392d352e3539362031322e3532392d31322e356102328201527f762d373563302d362e3930342d352e3630392d31322e352d31322e3532392d316102528201527f322e35732d31322e353320352e3539362d31322e35332031322e357635362e326102728201527f3561362e32363420362e3236342030203120312d31322e3532392030563437386102928201527f2e3563302d362e3930342d352e3630392d31322e352d31322e35332d31322e356102b28201527f483639382e353239632d362e393220302d31322e35323920352e3539362d31326102d28201527f2e3532392031322e35763735633020362e39303420352e3630392031322e35206102f28201527f31322e3532392031322e357a22202f3e3c7061746820643d224d3135372e36356103128201527f3520353431632d362e39333220302d31322e3535322d352e3539362d31322e356103328201527f35322d31322e35762d353063302d362e3930342d352e3631392d31322e352d316103528201527f322e3535312d31322e3553313230203437312e35393620313230203437382e356103728201527f763735633020362e39303420352e36322031322e352031322e3535322031322e6103928201527f35683135302e363263362e39333320302031322e3535322d352e3539362031326103b28201527f2e3535322d31322e35762d353063302d362e39303420352e3631392d31322e356103d28201527f2031322e3535322d31322e35683134342e33343563332e343635203020362e326103f28201527f373620322e37393820362e32373620362e3235732d322e38313120362e32352d6104128201527f362e32373620362e3235483332302e383238632d362e39333320302d31322e356104328201527f353220352e3539362d31322e3535322031322e357633372e35633020362e39306104528201527f3420352e3631392031322e352031322e3535322031322e35683135302e3632636104728201527f362e39333320302031322e3535322d352e3539362031322e3535322d31322e356104928201527f762d373563302d362e3930342d352e3631392d31322e352d31322e3535322d316104b28201527f322e35483238332e313732632d362e39333220302d31322e35353120352e35396104d28201527f362d31322e3535312031322e35763530633020362e3930342d352e36313920316104f28201527f322e352d31322e3535322031322e35682d32352e313033632d362e39333320306105128201527f2d31322e3535322d352e3539362d31322e3535322d31322e35762d353063302d6105328201527f362e3930342d352e36322d31322e352d31322e3535322d31322e35732d31322e6105528201527f35353220352e3539362d31322e3535322031322e35763530633020362e3930346105728201527f2d352e3631392031322e352d31322e3535312031322e35682d32352e3130347a6105928201527f6d3330312e3234322d362e3235633020332e3435322d322e38313120362e32356105b28201527f2d362e32373620362e3235483333392e363535632d332e34363520302d362e326105d28201527f37362d322e3739382d362e3237362d362e323573322e3831312d362e323520366105f28201527f2e3237362d362e3235683131322e39363663332e343635203020362e323736206106128201527f322e37393820362e32373620362e32357a4d343937203535332e3831386330206106328201527f362e39323920352e3632382031322e3534362031322e3537312031322e3534366106528201527f6831333261362e323820362e323820302030203120362e32383620362e3237326106728201527f20362e323820362e32382030203020312d362e32383620362e323733682d31336106928201527f32632d362e39343320302d31322e35373120352e3631362d31322e35373120316106b28201527f322e3534364131322e35362031322e3536203020302030203530392e353731206106d28201527f363034683135302e38353863362e39343320302031322e3537312d352e3631366106f28201527f2031322e3537312d31322e353435762d3131322e393163302d362e3932382d356107128201527f2e3632382d31322e3534352d31322e3537312d31322e353435483530392e35376107328201527f31632d362e39343320302d31322e35373120352e3631372d31322e35373120316107528201527f322e3534357637352e3237337a6d33372e3731342d36322e373237632d362e396107728201527f343320302d31322e35373120352e3631372d31322e3537312031322e353435766107928201527f32352e303931633020362e39323920352e3632382031322e3534362031322e356107b28201527f37312031322e353436683130302e35373263362e39343320302031322e3537316107d28201527f2d352e3631372031322e3537312d31322e353436762d32352e30393163302d366107f28201527f2e3932382d352e3632382d31322e3534352d31322e3537312d31322e353435486108128201527f3533342e3731347a222066696c6c2d72756c653d226576656e6f646422202f3e6108328201527f3c2f673e3c2f7376673e0000000000000000000000000000000000000000000061085282015261085c01949350505050565b7f7b226e616d65223a20227761676d6920230000000000000000000000000000008152600083516125f28160118501602088016117b3565b7f222c2022696d616765223a2022646174613a696d6167652f7376672b786d6c3b6011918401918201527f6261736536342c00000000000000000000000000000000000000000000000000603182015283516126558160388401602088016117b3565b7f227d00000000000000000000000000000000000000000000000000000000000060389290910191820152603a01949350505050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c0000008152600082516126c381601d8501602087016117b3565b91909101601d0192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015612711576127116126d0565b500390565b60008219821115612729576127296126d0565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361275f5761275f6126d0565b5060010190565b60008261277557612775611aea565b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156127e1576127e16126d0565b500290565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152508360408301526080606083015261282560808301846117df565b9695505050505050565b60006020828403121561284157600080fd5b81516117ac8161175e56fe4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2fa26469706673582212201665a4f9111990d7529375848d3fd02c0121091a940da59e763eba826e7b077064736f6c634300080d0033', +} as const diff --git a/packages/test/src/exports/index.test-d.ts b/packages/test/src/exports/index.test-d.ts new file mode 100644 index 0000000000..b056d56358 --- /dev/null +++ b/packages/test/src/exports/index.test-d.ts @@ -0,0 +1,4 @@ +import { expectTypeOf } from 'vitest' + +// noop test because vitest typecheck fails unless each workspace project has type test +expectTypeOf(1).toEqualTypeOf() diff --git a/packages/test/src/exports/index.test.ts b/packages/test/src/exports/index.test.ts new file mode 100644 index 0000000000..2a44ddbf8f --- /dev/null +++ b/packages/test/src/exports/index.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest' + +import * as react from './index.js' + +test('exports', () => { + expect(Object.keys(react)).toMatchInlineSnapshot(` + [ + "chain", + "mainnet", + "mainnet2", + "optimism", + "abi", + "accounts", + "address", + "bytecode", + "privateKey", + "typedData", + "walletConnectProjectId", + "testClient", + "mainnetTestClient", + "mainnet2TestClient", + "optimismTestClient", + "config", + "addressRegex", + "transactionHashRegex", + "wait", + ] + `) +}) diff --git a/packages/test/src/exports/index.ts b/packages/test/src/exports/index.ts new file mode 100644 index 0000000000..5844aeac01 --- /dev/null +++ b/packages/test/src/exports/index.ts @@ -0,0 +1,25 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { chain, mainnet, mainnet2, optimism } from '../chains.js' + +export { + abi, + accounts, + address, + bytecode, + privateKey, + typedData, + walletConnectProjectId, +} from '../constants.js' + +export { + testClient, + mainnetTestClient, + mainnet2TestClient, + optimismTestClient, +} from '../clients.js' + +export { config } from '../config.js' + +export { addressRegex, transactionHashRegex } from '../regex.js' + +export { wait } from '../utils.js' diff --git a/packages/test/src/exports/react.ts b/packages/test/src/exports/react.ts new file mode 100644 index 0000000000..f9dfe5c4b4 --- /dev/null +++ b/packages/test/src/exports/react.ts @@ -0,0 +1,63 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { + type RenderHookOptions, + type RenderHookResult, + type RenderOptions, + type RenderResult, + render as rtl_render, + renderHook as rtl_renderHook, + waitFor as rtl_waitFor, + type waitForOptions, +} from '@testing-library/react' +import { type ReactElement, createElement } from 'react' +import { WagmiProvider } from 'wagmi' + +import { config } from '../config.js' + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { act, cleanup } from '@testing-library/react' + +export const queryClient = new QueryClient() + +export function createWrapper>( + Wrapper: TComponent, + props: Parameters[0], +) { + type Props = { children?: React.ReactNode | undefined } + return function CreatedWrapper({ children }: Props) { + return createElement( + Wrapper, + props, + createElement(QueryClientProvider, { client: queryClient }, children), + ) + } +} + +export function renderHook( + render: (props: Props) => Result, + options?: RenderHookOptions | undefined, +): RenderHookResult { + queryClient.clear() + return rtl_renderHook(render, { + wrapper: createWrapper(WagmiProvider, { config, reconnectOnMount: false }), + ...options, + }) +} + +export function render( + element: ReactElement, + options?: RenderOptions | undefined, +): RenderResult { + queryClient.clear() + return rtl_render(element, { + wrapper: createWrapper(WagmiProvider, { config, reconnectOnMount: false }), + ...options, + }) +} + +export function waitFor( + callback: () => Promise | T, + options?: waitForOptions | undefined, +): Promise { + return rtl_waitFor(callback, { timeout: 10_000, ...options }) +} diff --git a/packages/test/src/exports/vue.ts b/packages/test/src/exports/vue.ts new file mode 100644 index 0000000000..ac49b17a31 --- /dev/null +++ b/packages/test/src/exports/vue.ts @@ -0,0 +1,66 @@ +import { VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { type App, type Ref, createApp, watch } from 'vue' + +import { config } from '../config.js' + +export type RenderComposableReturnType unknown> = [ + ReturnType, + App, +] + +export function renderComposable unknown>( + composable: composable, + options: { attach?: (app: App) => void } | undefined = { + attach(app) { + app + .use(WagmiPlugin, { + config, + reconnectOnMount: false, + }) + .use(VueQueryPlugin, {}) + }, + }, +): RenderComposableReturnType { + let result = undefined + const app = createApp({ + setup() { + result = composable() + return () => {} + }, + }) + + options.attach?.(app) + app.mount(document.createElement('div')) + + return [result, app] as unknown as RenderComposableReturnType +} + +export type WaitForOptions = { + timeout?: number +} + +export function waitFor( + ref: ref, + predicate: (value: ref['value']) => boolean = (value) => value, + options: WaitForOptions = {}, +) { + const { timeout = 10_000 } = options + return new Promise((resolve, reject) => { + const timer = timeout + ? setTimeout(() => { + _unwatch() + if (predicate(ref.value)) resolve() + else reject(new Error(`\`waitFor\` timed out in ${timeout}ms.`)) + }, timeout) + : undefined + + const _unwatch = watch(ref, (value) => { + if (predicate(value)) { + if (timer) clearTimeout(timer) + _unwatch() + resolve() + } + }) + }) +} diff --git a/packages/test/src/globalSetup.ts b/packages/test/src/globalSetup.ts new file mode 100644 index 0000000000..f8248e8390 --- /dev/null +++ b/packages/test/src/globalSetup.ts @@ -0,0 +1,26 @@ +import { createServer } from 'prool' +import { anvil } from 'prool/instances' + +import { chain as chainLookup } from './chains.js' + +export default async function () { + const promises = [] + for (const chain of Object.values(chainLookup)) { + promises.push( + createServer({ + instance: anvil({ + chainId: chain.id, + forkUrl: chain.fork.url, + forkBlockNumber: chain.fork.blockNumber, + noMining: true, + }), + port: chain.port, + }).start(), + ) + } + const results = await Promise.all(promises) + + return async () => { + await Promise.all(results.map((stop) => stop())) + } +} diff --git a/packages/test/src/regex.ts b/packages/test/src/regex.ts new file mode 100644 index 0000000000..5998ea513e --- /dev/null +++ b/packages/test/src/regex.ts @@ -0,0 +1,3 @@ +export const addressRegex = /^0x([A-Fa-f0-9]{40})$/ + +export const transactionHashRegex = /^0x([A-Fa-f0-9]{64})$/ diff --git a/packages/test/src/setup.ts b/packages/test/src/setup.ts new file mode 100644 index 0000000000..1a41b7a6ed --- /dev/null +++ b/packages/test/src/setup.ts @@ -0,0 +1,8 @@ +import { afterAll } from 'vitest' + +import { testClient } from './clients.js' + +afterAll(async () => { + // If you are using a fork, you can reset your anvil instance to the initial fork block. + await Promise.all(Object.values(testClient).map((client) => client.restart())) +}) diff --git a/packages/test/src/utils.ts b/packages/test/src/utils.ts new file mode 100644 index 0000000000..85afe15c4b --- /dev/null +++ b/packages/test/src/utils.ts @@ -0,0 +1,25 @@ +const pool = + Number(process.env.VITEST_POOL_ID ?? 1) + Math.floor(Math.random() * 10000) + +export function getRpcUrls({ port }: { port: number }) { + return { + port, + rpcUrls: { + // These rpc urls are automatically used in the transports. + default: { + // Note how we append the worker id to the local rpc urls. + http: [`http://127.0.0.1:${port}/${pool}`], + webSocket: [`ws://127.0.0.1:${port}/${pool}`], + }, + public: { + // Note how we append the worker id to the local rpc urls. + http: [`http://127.0.0.1:${port}/${pool}`], + webSocket: [`ws://127.0.0.1:${port}/${pool}`], + }, + }, + } as const +} + +export async function wait(time: number) { + return new Promise((res) => setTimeout(res, time)) +} diff --git a/packages/test/tsconfig.build.json b/packages/test/tsconfig.build.json new file mode 100644 index 0000000000..fbed2b1036 --- /dev/null +++ b/packages/test/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/test/tsconfig.json b/packages/test/tsconfig.json new file mode 100644 index 0000000000..bd33919ac3 --- /dev/null +++ b/packages/test/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/utils/README.md b/packages/utils/README.md deleted file mode 100644 index 90a929712a..0000000000 --- a/packages/utils/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# packages/utils - -This folder contains utility packages. We group them under -the utils/ folder to keep the repo nice and tidy. diff --git a/packages/utils/abi/CHANGELOG.md b/packages/utils/abi/CHANGELOG.md deleted file mode 100644 index 8194c38b89..0000000000 --- a/packages/utils/abi/CHANGELOG.md +++ /dev/null @@ -1,2254 +0,0 @@ -# @0xsequence/abi - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 - -## 2.3.8 - -### Patch Changes - -- indexer: update clients - -## 2.3.7 - -### Patch Changes - -- Metadata updates - -## 2.3.6 - -### Patch Changes - -- New chains - -## 2.3.5 - -### Patch Changes - -- Add Frequency Testnet - -## 2.3.4 - -### Patch Changes - -- metadata: exclude deprecated methods on rpc client - -## 2.3.3 - -### Patch Changes - -- metadata: client update - -## 2.3.2 - -### Patch Changes - -- metadata: update rpc client - -## 2.3.1 - -### Patch Changes - -- indexer: update rpc client - -## 2.3.0 - -### Minor Changes - -- update metadata rpc client - -## 2.2.15 - -### Patch Changes - -- API updates - -## 2.2.14 - -### Patch Changes - -- Somnia Testnet and Monad Testnet - -## 2.2.13 - -### Patch Changes - -- Add XR1 to all networks - -## 2.2.12 - -### Patch Changes - -- Add XR1 - -## 2.2.11 - -### Patch Changes - -- Relayer updates - -## 2.2.10 - -### Patch Changes - -- Etherlink support - -## 2.2.9 - -### Patch Changes - -- Indexer gateway native token balances - -## 2.2.8 - -### Patch Changes - -- Add Moonbeam and Moonbase Alpha - -## 2.2.7 - -### Patch Changes - -- Update Builder package - -## 2.2.6 - -### Patch Changes - -- Update relayer package - -## 2.2.5 - -### Patch Changes - -- auth: fix sequence indexer gateway url -- account: immutable wallet proxy hook - -## 2.2.4 - -### Patch Changes - -- network: update soneium mainnet block explorer url -- waas: signTypedData intent support - -## 2.2.3 - -### Patch Changes - -- provider: updating initWallet to use connected network configs if they exist - -## 2.2.2 - -### Patch Changes - -- pass projectAccessKey to relayer at all times - -## 2.2.1 - -### Patch Changes - -- waas-ethers: sign typed data - -## 2.2.0 - -### Minor Changes - -- indexer: gateway client -- @0xsequence/builder -- upgrade puppeteer to v23.10.3 - -## 2.1.8 - -### Patch Changes - -- Add Soneium Mainnet - -## 2.1.7 - -### Patch Changes - -- guard: pass project access key to guard requests - -## 2.1.6 - -### Patch Changes - -- Add LAOS and Telos Testnet chains - -## 2.1.5 - -### Patch Changes - -- account: save presigned configuration with reference chain id 1 - -## 2.1.4 - -### Patch Changes - -- provider: pass projectAccessKey into MuxMessageProvider - -## 2.1.3 - -### Patch Changes - -- waas: time drift date fix due to strange browser quirk - -## 2.1.2 - -### Patch Changes - -- provider: export analytics correctly - -## 2.1.1 - -### Patch Changes - -- Add LAOS chain support - -## 2.1.0 - -### Minor Changes - -- account: forward project access key when estimating fees and sending transactions - -### Patch Changes - -- sessions: save signatures with reference chain id - -## 2.0.26 - -### Patch Changes - -- account: fix chain id comparison - -## 2.0.25 - -### Patch Changes - -- skale-nebula: deploy gas limit = 10m - -## 2.0.24 - -### Patch Changes - -- sessions: arweave: configurable gateway url -- waas: use /status to get time drift before sending any intents - -## 2.0.23 - -### Patch Changes - -- Add The Root Network support - -## 2.0.22 - -### Patch Changes - -- Add SKALE Nebula Mainnet support - -## 2.0.21 - -### Patch Changes - -- account: add publishWitnessFor - -## 2.0.20 - -### Patch Changes - -- upgrade deps, and improve waas session status handling - -## 2.0.19 - -### Patch Changes - -- Add Immutable zkEVM support - -## 2.0.18 - -### Patch Changes - -- waas: new contractCall transaction type -- sessions: add arweave owner - -## 2.0.17 - -### Patch Changes - -- update waas auth to clear session before signIn - -## 2.0.16 - -### Patch Changes - -- Removed Astar chains - -## 2.0.15 - -### Patch Changes - -- indexer: update bindings with token balance additions - -## 2.0.14 - -### Patch Changes - -- sessions: arweave config reader -- network: add b3 and apechain mainnet configs - -## 2.0.13 - -### Patch Changes - -- network: toy-testnet - -## 2.0.12 - -### Patch Changes - -- api: update bindings - -## 2.0.11 - -### Patch Changes - -- waas: intents test fix -- api: update bindings - -## 2.0.10 - -### Patch Changes - -- network: soneium minato testnet - -## 2.0.9 - -### Patch Changes - -- network: fix SKALE network name - -## 2.0.8 - -### Patch Changes - -- metadata: update bindings - -## 2.0.7 - -### Patch Changes - -- wallet request handler fix - -## 2.0.6 - -### Patch Changes - -- network: matic -> pol - -## 2.0.5 - -### Patch Changes - -- provider: update databeat to 0.9.2 - -## 2.0.4 - -### Patch Changes - -- network: add skale-nebula-testnet - -## 2.0.3 - -### Patch Changes - -- waas: check session status in SequenceWaaS.isSignedIn() - -## 2.0.2 - -### Patch Changes - -- sessions: property convert serialized bignumber hex value to bigint - -## 2.0.1 - -### Patch Changes - -- waas: http signature check for authenticator requests -- provider: unwrap legacy json rpc responses -- use json replacer and reviver for bigints - -## 2.0.0 - -### Major Changes - -- ethers v6 - -## 1.10.15 - -### Patch Changes - -- utils: extractProjectIdFromAccessKey - -## 1.10.14 - -### Patch Changes - -- network: add borne-testnet to allNetworks - -## 1.10.13 - -### Patch Changes - -- network: add borne testnet - -## 1.10.12 - -### Patch Changes - -- api: update bindings -- global/window -> globalThis - -## 1.10.11 - -### Patch Changes - -- waas: updated intent.gen without webrpc types, errors exported from authenticator.gen - -## 1.10.10 - -### Patch Changes - -- metadata: update bindings with new contract collections api - -## 1.10.9 - -### Patch Changes - -- waas minor update - -## 1.10.8 - -### Patch Changes - -- update metadata bindings - -## 1.10.7 - -### Patch Changes - -- minor fixes to waas client - -## 1.10.6 - -### Patch Changes - -- metadata: update bindings - -## 1.10.5 - -### Patch Changes - -- network: ape-chain-testnet -> apechain-testnet - -## 1.10.4 - -### Patch Changes - -- network: add b3-sepolia, ape-chain-testnet, blast, blast-sepolia - -## 1.10.3 - -### Patch Changes - -- typing fix - -## 1.10.2 - -### Patch Changes - -- - waas: add getIdToken method - - indexer: update api client - -## 1.10.1 - -### Patch Changes - -- metadata: update bindings - -## 1.10.0 - -### Minor Changes - -- waas release v1.3.0 - -## 1.9.37 - -### Patch Changes - -- network: adds nativeToken data to NetworkMetadata constants - -## 1.9.36 - -### Patch Changes - -- guard: export client - -## 1.9.35 - -### Patch Changes - -- guard: update bindings - -## 1.9.34 - -### Patch Changes - -- waas: always use lowercase email - -## 1.9.33 - -### Patch Changes - -- waas: umd build - -## 1.9.32 - -### Patch Changes - -- indexer: update bindings - -## 1.9.31 - -### Patch Changes - -- metadata: token directory changes - -## 1.9.30 - -### Patch Changes - -- update - -## 1.9.29 - -### Patch Changes - -- disable gnosis chain - -## 1.9.28 - -### Patch Changes - -- add utils/merkletree - -## 1.9.27 - -### Patch Changes - -- network: optimistic -> optimism -- waas: remove defaults -- api, sessions: update bindings - -## 1.9.26 - -### Patch Changes - -- - add backend interfaces for pluggable interfaces - - introduce @0xsequence/react-native - - update pnpm to lockfile v9 - -## 1.9.25 - -### Patch Changes - -- update webrpc clients with new error types - -## 1.9.24 - -### Patch Changes - -- waas: add memoryStore backend to localStore - -## 1.9.23 - -### Patch Changes - -- update api client bindings - -## 1.9.22 - -### Patch Changes - -- update metadata client bindings - -## 1.9.21 - -### Patch Changes - -- api client bindings - -## 1.9.20 - -### Patch Changes - -- api client bindings update - -## 1.9.19 - -### Patch Changes - -- waas update - -## 1.9.18 - -### Patch Changes - -- provider: prohibit dangerous functions - -## 1.9.17 - -### Patch Changes - -- network: add xr-sepolia - -## 1.9.16 - -### Patch Changes - -- waas: sequence.feeOptions - -## 1.9.15 - -### Patch Changes - -- metadata: collection external_link field name fix - -## 1.9.14 - -### Patch Changes - -- network: astar-zkatana -> astar-zkyoto -- network: deprecate polygon mumbai network -- network: add xai and polygon amoy - -## 1.9.13 - -### Patch Changes - -- waas: fix @0xsequence/network dependency - -## 1.9.12 - -### Patch Changes - -- indexer: update rpc bindings -- provider: signMessage: Serialize the BytesLike or string message into hexstring before sending -- waas: SessionAuthProof - -## 1.9.11 - -### Patch Changes - -- metdata, update rpc bindings - -## 1.9.10 - -### Patch Changes - -- update metadata rpc bindings - -## 1.9.9 - -### Patch Changes - -- metadata, add SequenceCollections rpc client - -## 1.9.8 - -### Patch Changes - -- waas client update - -## 1.9.7 - -### Patch Changes - -- update rpc client bindings for api, metadata and relayer - -## 1.9.6 - -### Patch Changes - -- waas package update - -## 1.9.5 - -### Patch Changes - -- RpcRelayer prioritize project access key - -## 1.9.4 - -### Patch Changes - -- waas: fix network dependency - -## 1.9.3 - -### Patch Changes - -- provider: don't append access key to RPC url if user has already provided it - -## 1.9.2 - -### Patch Changes - -- network: add xai-sepolia - -## 1.9.1 - -### Patch Changes - -- analytics fix - -## 1.9.0 - -### Minor Changes - -- waas release - -## 1.8.8 - -### Patch Changes - -- update metadata bindings - -## 1.8.7 - -### Patch Changes - -- provider: update databeat to 0.9.1 - -## 1.8.6 - -### Patch Changes - -- guard: SignedOwnershipProof - -## 1.8.5 - -### Patch Changes - -- guard: signOwnershipProof and isSignedOwnershipProof - -## 1.8.4 - -### Patch Changes - -- network: add homeverse to networks list - -## 1.8.3 - -### Patch Changes - -- api: introduce basic linked wallet support - -## 1.8.2 - -### Patch Changes - -- provider: don't initialize analytics unless explicitly requested - -## 1.8.1 - -### Patch Changes - -- update to analytics provider - -## 1.8.0 - -### Minor Changes - -- provider: project analytics - -## 1.7.2 - -### Patch Changes - -- 0xsequence: ChainId should not be exported as a type -- account, wallet: fix nonce selection - -## 1.7.1 - -### Patch Changes - -- network: add missing avalanche logoURI - -## 1.7.0 - -### Minor Changes - -- provider: projectAccessKey is now required - -### Patch Changes - -- network: add NetworkMetadata.logoURI property for all networks - -## 1.6.3 - -### Patch Changes - -- network list update - -## 1.6.2 - -### Patch Changes - -- auth: projectAccessKey option -- wallet: use 12 bytes for random space - -## 1.6.1 - -### Patch Changes - -- core: add simple config from subdigest support -- core: fix encode tree with subdigest -- account: implement buildOnChainSignature on Account - -## 1.6.0 - -### Minor Changes - -- account, wallet: parallel transactions by default - -### Patch Changes - -- provider: emit disconnect on sign out - -## 1.5.0 - -### Minor Changes - -- signhub: add 'signing' signer status - -### Patch Changes - -- auth: Session.open: onAccountAddress callback -- account: allow empty transaction bundles - -## 1.4.9 - -### Patch Changes - -- rename SequenceMetadataClient to SequenceMetadata - -## 1.4.8 - -### Patch Changes - -- account: Account.getSigners - -## 1.4.7 - -### Patch Changes - -- update indexer client bindings - -## 1.4.6 - -### Patch Changes - -- - add sepolia networks, mark goerli as deprecated - - update indexer client bindings - -## 1.4.5 - -### Patch Changes - -- indexer/metadata: update client bindings -- auth: selectWallet with new address - -## 1.4.4 - -### Patch Changes - -- indexer: update bindings -- auth: handle jwt expiry - -## 1.4.3 - -### Patch Changes - -- guard: return active status from GuardSigner.getAuthMethods - -## 1.4.2 - -### Patch Changes - -- guard: update bindings - -## 1.4.1 - -### Patch Changes - -- network: remove unused networks -- signhub: orchestrator interface -- guard: auth methods interface -- guard: update bindings for pin and totp -- guard: no more retry logic - -## 1.4.0 - -### Minor Changes - -- project access key support - -## 1.3.0 - -### Minor Changes - -- signhub: account children - -### Patch Changes - -- guard: do not throw when building deploy transaction -- network: snowtrace.io -> subnets.avax.network/c-chain - -## 1.2.9 - -### Patch Changes - -- account: AccountSigner.sendTransaction simulateForFeeOptions -- relayer: update bindings - -## 1.2.8 - -### Patch Changes - -- rename X-Sequence-Token-Key header to X-Access-Key - -## 1.2.7 - -### Patch Changes - -- add x-sequence-token-key to clients - -## 1.2.6 - -### Patch Changes - -- Fix bind multicall provider - -## 1.2.5 - -### Patch Changes - -- Multicall default configuration fixes - -## 1.2.4 - -### Patch Changes - -- provider: Adding missing payment provider types to PaymentProviderOption -- provider: WalletRequestHandler.notifyChainChanged - -## 1.2.3 - -### Patch Changes - -- auth, provider: connect to accept optional authorizeNonce - -## 1.2.2 - -### Patch Changes - -- provider: allow createContract calls -- core: check for explicit zero address in contract deployments - -## 1.2.1 - -### Patch Changes - -- auth: use sequence api chain id as reference chain id if available - -## 1.2.0 - -### Minor Changes - -- split services from session, better local support - -## 1.1.15 - -### Patch Changes - -- guard: remove error filtering - -## 1.1.14 - -### Patch Changes - -- guard: add GuardSigner.onError - -## 1.1.13 - -### Patch Changes - -- provider: pass client version with connect options -- provider: removing large from BannerSize - -## 1.1.12 - -### Patch Changes - -- provider: adding bannerSize to ConnectOptions - -## 1.1.11 - -### Patch Changes - -- add homeverse configs - -## 1.1.10 - -### Patch Changes - -- handle default EIP6492 on send - -## 1.1.9 - -### Patch Changes - -- Custom default EIP6492 on client - -## 1.1.8 - -### Patch Changes - -- metadata: searchMetadata: add types filter - -## 1.1.7 - -### Patch Changes - -- adding signInWith connect settings option to allow dapps to automatically login their users with a certain provider optimizing the normal authentication flow - -## 1.1.6 - -### Patch Changes - -- metadata: searchMetadata: add chainID and excludeTokenMetadata filters - -## 1.1.5 - -### Patch Changes - -- account: re-compute meta-transaction id for wallet deployment transactions - -## 1.1.4 - -### Patch Changes - -- network: rename base-mainnet to base -- provider: override isDefaultChain with ConnectOptions.networkId if provided - -## 1.1.3 - -### Patch Changes - -- provider: use network id from transport session -- provider: sign authorization using ConnectOptions.networkId if provided - -## 1.1.2 - -### Patch Changes - -- provider: jsonrpc chain id fixes - -## 1.1.1 - -### Patch Changes - -- network: add base mainnet and sepolia -- provider: reject toxic transaction requests - -## 1.1.0 - -### Minor Changes - -- Refactor dapp facing provider - -## 1.0.5 - -### Patch Changes - -- network: export network constants -- guard: use the correct global for fetch -- network: nova-explorer.arbitrum.io -> nova.arbiscan.io - -## 1.0.4 - -### Patch Changes - -- provider: accept name or number for networkId - -## 1.0.3 - -### Patch Changes - -- Simpler isValidSignature helpers - -## 1.0.2 - -### Patch Changes - -- add extra signature validation utils methods - -## 1.0.1 - -### Patch Changes - -- add homeverse testnet - -## 1.0.0 - -### Major Changes - -- https://sequence.xyz/blog/sequence-wallet-light-state-sync-full-merkle-wallets - -## 0.43.34 - -### Patch Changes - -- auth: no jwt for indexer - -## 0.43.33 - -### Patch Changes - -- Adding onConnectOptionsChange handler to WalletRequestHandler - -## 0.43.32 - -### Patch Changes - -- add Base Goerli network - -## 0.43.31 - -### Patch Changes - -- remove AuxDataProvider, add promptSignInConnect - -## 0.43.30 - -### Patch Changes - -- add arbitrum goerli testnet - -## 0.43.29 - -### Patch Changes - -- provider: check availability of window object - -## 0.43.28 - -### Patch Changes - -- update api bindings - -## 0.43.27 - -### Patch Changes - -- Add rpc is sequence method - -## 0.43.26 - -### Patch Changes - -- add zkevm url to enum - -## 0.43.25 - -### Patch Changes - -- added polygon zkevm to mainnet networks - -## 0.43.24 - -### Patch Changes - -- name change from zkevm to polygon-zkevm - -## 0.43.23 - -### Patch Changes - -- update zkEVM name to Polygon zkEVM - -## 0.43.22 - -### Patch Changes - -- add zkevm chain - -## 0.43.21 - -### Patch Changes - -- api: update client bindings - -## 0.43.20 - -### Patch Changes - -- indexer: update bindings - -## 0.43.19 - -### Patch Changes - -- session proof update - -## 0.43.18 - -### Patch Changes - -- rpc client global check, hardening - -## 0.43.17 - -### Patch Changes - -- rpc clients, check of 'global' is defined - -## 0.43.16 - -### Patch Changes - -- ethers peerDep to v5, update rpc client global use - -## 0.43.15 - -### Patch Changes - -- - provider: expand receiver type on some util methods - -## 0.43.14 - -### Patch Changes - -- bump - -## 0.43.13 - -### Patch Changes - -- update rpc bindings - -## 0.43.12 - -### Patch Changes - -- provider: single wallet init, and add new unregisterWallet() method - -## 0.43.11 - -### Patch Changes - -- fix lockfiles -- re-add mocha type deleter - -## 0.43.10 - -### Patch Changes - -- various improvements - -## 0.43.9 - -### Patch Changes - -- update deps - -## 0.43.8 - -### Patch Changes - -- network: JsonRpcProvider with caching - -## 0.43.7 - -### Patch Changes - -- provider: fix wallet network init - -## 0.43.6 - -### Patch Changes - -- metadatata: update rpc bindings - -## 0.43.5 - -### Patch Changes - -- provider: do not set default network for connect messages -- provider: forward missing error message - -## 0.43.4 - -### Patch Changes - -- no-change version bump to fix incorrectly tagged snapshot build - -## 0.43.3 - -### Patch Changes - -- metadata: update bindings - -## 0.43.2 - -### Patch Changes - -- provider: implement connectUnchecked - -## 0.43.1 - -### Patch Changes - -- update to latest ethauth dep - -## 0.43.0 - -### Minor Changes - -- move ethers to a peer dependency - -## 0.42.10 - -### Patch Changes - -- add auxDataProvider - -## 0.42.9 - -### Patch Changes - -- provider: add eip-191 exceptions - -## 0.42.8 - -### Patch Changes - -- provider: skip setting intent origin if we're unity plugin - -## 0.42.7 - -### Patch Changes - -- Add sign in options to connection settings - -## 0.42.6 - -### Patch Changes - -- api bindings update - -## 0.42.5 - -### Patch Changes - -- relayer: don't treat missing receipt as hard failure - -## 0.42.4 - -### Patch Changes - -- provider: add custom app protocol to connect options - -## 0.42.3 - -### Patch Changes - -- update api bindings - -## 0.42.2 - -### Patch Changes - -- disable rinkeby network - -## 0.42.1 - -### Patch Changes - -- wallet: optional waitForReceipt parameter - -## 0.42.0 - -### Minor Changes - -- relayer: estimateGasLimits -> simulate -- add simulator package - -### Patch Changes - -- transactions: fix flattenAuxTransactions -- provider: only filter nullish values -- provider: re-map transaction 'gas' back to 'gasLimit' - -## 0.41.3 - -### Patch Changes - -- api bindings update - -## 0.41.2 - -### Patch Changes - -- api bindings update - -## 0.41.1 - -### Patch Changes - -- update default networks - -## 0.41.0 - -### Minor Changes - -- relayer: fix Relayer.wait() interface - - The interface for calling Relayer.wait() has changed. Instead of a single optional ill-defined timeout/delay parameter, there are three optional parameters, in order: - - timeout: the maximum time to wait for the transaction receipt - - delay: the polling interval, i.e. the time to wait between requests - - maxFails: the maximum number of hard failures to tolerate before giving up - - Please update your codebase accordingly. - -- relayer: add optional waitForReceipt parameter to Relayer.relay - - The behaviour of Relayer.relay() was not well-defined with respect to whether or not it waited for a receipt. - This change allows the caller to specify whether to wait or not, with the default behaviour being to wait. - -### Patch Changes - -- relayer: wait receipt retry logic -- fix wrapped object error -- provider: forward delegateCall and revertOnError transaction fields - -## 0.40.6 - -### Patch Changes - -- add arbitrum-nova chain - -## 0.40.5 - -### Patch Changes - -- api: update bindings - -## 0.40.4 - -### Patch Changes - -- add unreal transport - -## 0.40.3 - -### Patch Changes - -- provider: fix MessageToSign message type - -## 0.40.2 - -### Patch Changes - -- Wallet provider, loadSession method - -## 0.40.1 - -### Patch Changes - -- export sequence.initWallet and sequence.getWallet - -## 0.40.0 - -### Minor Changes - -- add sequence.initWallet(network, config) and sequence.getWallet() helper methods - -## 0.39.6 - -### Patch Changes - -- indexer: update client bindings - -## 0.39.5 - -### Patch Changes - -- provider: fix networkRpcUrl config option - -## 0.39.4 - -### Patch Changes - -- api: update client bindings - -## 0.39.3 - -### Patch Changes - -- add request method on Web3Provider - -## 0.39.2 - -### Patch Changes - -- update umd name - -## 0.39.1 - -### Patch Changes - -- add Aurora network -- add origin info for accountsChanged event to handle it per dapp - -## 0.39.0 - -### Minor Changes - -- abstract window.localStorage to interface type - -## 0.38.2 - -### Patch Changes - -- provider: add Settings.defaultPurchaseAmount - -## 0.38.1 - -### Patch Changes - -- update api and metadata rpc bindings - -## 0.38.0 - -### Minor Changes - -- api: update bindings, change TokenPrice interface -- bridge: remove @0xsequence/bridge package -- api: update bindings, rename ContractCallArg to TupleComponent - -## 0.37.1 - -### Patch Changes - -- Add back sortNetworks - Removing sorting was a breaking change for dapps on older versions which directly integrate sequence. - -## 0.37.0 - -### Minor Changes - -- network related fixes and improvements -- api: bindings: exchange rate lookups - -## 0.36.13 - -### Patch Changes - -- api: update bindings with new price endpoints - -## 0.36.12 - -### Patch Changes - -- wallet: skip remote signers if not needed -- auth: check that signature meets threshold before requesting auth token - -## 0.36.11 - -### Patch Changes - -- Prefix EIP191 message on wallet-request-handler - -## 0.36.10 - -### Patch Changes - -- support bannerUrl on connect - -## 0.36.9 - -### Patch Changes - -- minor dev xp improvements - -## 0.36.8 - -### Patch Changes - -- more connect options (theme, payment providers, funding currencies) - -## 0.36.7 - -### Patch Changes - -- fix missing break - -## 0.36.6 - -### Patch Changes - -- wallet_switchEthereumChain support - -## 0.36.5 - -### Patch Changes - -- auth: bump ethauth to 0.7.0 - network, wallet: don't assume position of auth network in list - api/indexer/metadata: trim trailing slash on hostname, and add endpoint urls - relayer: Allow to specify local relayer transaction parameters like gas price or gas limit - -## 0.36.4 - -### Patch Changes - -- Updating list of chain ids to include other ethereum compatible chains - -## 0.36.3 - -### Patch Changes - -- provider: pass connect options to prompter methods - -## 0.36.2 - -### Patch Changes - -- transactions: Setting target to 0x0 when empty to during SequenceTxAbiEncode - -## 0.36.1 - -### Patch Changes - -- metadata: update client with more fields - -## 0.36.0 - -### Minor Changes - -- relayer, wallet: fee quote support - -## 0.35.12 - -### Patch Changes - -- provider: rename wallet.commands to wallet.utils - -## 0.35.11 - -### Patch Changes - -- provider/utils: smoother message validation - -## 0.35.10 - -### Patch Changes - -- upgrade deps - -## 0.35.9 - -### Patch Changes - -- provider: window-transport override event handlers with new wallet instance - -## 0.35.8 - -### Patch Changes - -- provider: async wallet sign in improvements - -## 0.35.7 - -### Patch Changes - -- config: cache wallet configs - -## 0.35.6 - -### Patch Changes - -- provider: support async signin of wallet request handler - -## 0.35.5 - -### Patch Changes - -- wallet: skip threshold check during fee estimation - -## 0.35.4 - -### Patch Changes - -- - browser extension mode, center window - -## 0.35.3 - -### Patch Changes - -- - update window position when in browser extension mode - -## 0.35.2 - -### Patch Changes - -- - provider: WindowMessageHandler accept optional windowHref - -## 0.35.1 - -### Patch Changes - -- wallet: update config on undeployed too - -## 0.35.0 - -### Minor Changes - -- - config: add buildStubSignature - - provider: add checks to signing cases for wallet deployment and config statuses - - provider: add prompt for wallet deployment - - relayer: add BaseRelayer.prependWalletDeploy - - relayer: add Relayer.feeOptions - - relayer: account for wallet deployment in fee estimation - - transactions: add fromTransactionish - - wallet: add Account.prependConfigUpdate - - wallet: add Account.getFeeOptions - -## 0.34.0 - -### Minor Changes - -- - upgrade deps - -## 0.31.0 - -### Minor Changes - -- - upgrading to ethers v5.5 - -## 0.30.0 - -### Minor Changes - -- - upgrade most deps - -## 0.29.8 - -### Patch Changes - -- update api - -## 0.29.0 - -### Minor Changes - -- major architectural changes in Sequence design - - only one API instance, API is no longer a per-chain service - - separate per-chain indexer service, API no longer handles indexing - - single contract metadata service, API no longer serves metadata - - chaind package has been removed, indexer and metadata packages have been added - - stronger typing with new explicit ChainId type - - multicall fixes and improvements - - forbid "wait" transactions in sendTransactionBatch calls - -## 0.28.0 - -### Minor Changes - -- extension provider - -## 0.27.0 - -### Minor Changes - -- Add requireFreshSigner lib to sessions - -## 0.25.1 - -### Patch Changes - -- Fix build typescrypt issue - -## 0.25.0 - -### Minor Changes - -- 10c8af8: Add estimator package - Fix multicall few calls bug - -## 0.23.0 - -### Minor Changes - -- - relayer: offer variety of gas fee options from the relayer service" - -## 0.22.2 - -### Patch Changes - -- e1c109e: Fix authProof on expired sessions - -## 0.22.1 - -### Patch Changes - -- transport session cache - -## 0.22.0 - -### Minor Changes - -- e667b65: Expose all relayer options on networks - -## 0.21.5 - -### Patch Changes - -- Give priority to metaTxnId returned by relayer - -## 0.21.4 - -### Patch Changes - -- Add has enough signers method - -## 0.21.3 - -### Patch Changes - -- add window session cache - -## 0.21.2 - -### Patch Changes - -- exception handlind in relayer - -## 0.21.0 - -### Minor Changes - -- - fix gas estimation on wallets with large number of signers - - update to session handling and wallet config construction upon auth - -## 0.19.3 - -### Patch Changes - -- jwtAuth visibility, package version sync - -## 0.19.2 - -### Patch Changes - -- - api: change jwtAuth visibility - -## 0.19.0 - -### Minor Changes - -- - provider, improve dapp / wallet transport io - -## 0.18.0 - -### Minor Changes - -- relayer improvements and pending transaction handling - -## 0.16.0 - -### Minor Changes - -- relayer as its own service separate from chaind - -## 0.15.1 - -### Patch Changes - -- update api clients - -## 0.14.3 - -### Patch Changes - -- Fix 0xSequence relayer dependencies - -## 0.14.2 - -### Patch Changes - -- Add debug logs to rpc-relayer - -## 0.14.0 - -### Minor Changes - -- update sequence utils finder which includes optimization - -## 0.13.0 - -### Minor Changes - -- Update SequenceUtils deployed contract - -## 0.12.1 - -### Patch Changes - -- npm bump - -## 0.12.0 - -### Minor Changes - -- provider: improvements to window transport - -## 0.11.4 - -### Patch Changes - -- update api client - -## 0.11.3 - -### Patch Changes - -- improve openWindow state options handling - -## 0.11.2 - -### Patch Changes - -- Fix multicall proxy scopes - -## 0.11.1 - -### Patch Changes - -- Add support for dynamic and nested signatures - -## 0.11.0 - -### Minor Changes - -- Update wallet context to 1.7 contracts - -## 0.10.9 - -### Patch Changes - -- add support for public addresses as signers in session.open - -## 0.10.8 - -### Patch Changes - -- Multicall production configuration - -## 0.10.7 - -### Patch Changes - -- allow provider transport to force disconnect - -## 0.10.6 - -### Patch Changes - -- - fix getWalletState method - -## 0.10.5 - -### Patch Changes - -- update relayer gas refund options - -## 0.10.4 - -### Patch Changes - -- Update api proto - -## 0.10.3 - -### Patch Changes - -- Fix loading config cross-chain - -## 0.10.2 - -### Patch Changes - -- - message digest fix - -## 0.10.1 - -### Patch Changes - -- upgrade deps - -## 0.10.0 - -### Minor Changes - -- Deployed new contracts with ERC1271 signer support - -## 0.9.6 - -### Patch Changes - -- Update ABIs for latest sequence contracts - -## 0.9.3 - -### Patch Changes - -- - minor improvements - -## 0.9.1 - -### Patch Changes - -- - patch bump - -## 0.9.0 - -### Minor Changes - -- - provider transport hardening - -## 0.8.5 - -### Patch Changes - -- - use latest wallet-contracts - -## 0.8.4 - -### Patch Changes - -- - minor improvements, name updates and comments - -## 0.8.3 - -### Patch Changes - -- - refinements - - - normalize signer address in config - - - provider: getWalletState() method to WalletProvider - -## 0.8.2 - -### Patch Changes - -- - field rename and ethauth dependency bump - -## 0.8.1 - -### Patch Changes - -- - variety of optimizations - -## 0.8.0 - -### Minor Changes - -- - changeset fix - -## 0.7.0 - -### Patch Changes - -- 6f11ed7: sequence.js, init release diff --git a/packages/utils/abi/README.md b/packages/utils/abi/README.md deleted file mode 100644 index 79488d496c..0000000000 --- a/packages/utils/abi/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @0xsequence/abi - -See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/utils/abi/eslint.config.js b/packages/utils/abi/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/utils/abi/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/utils/abi/package.json b/packages/utils/abi/package.json deleted file mode 100644 index d70d75cdc6..0000000000 --- a/packages/utils/abi/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@0xsequence/abi", - "version": "3.0.5", - "description": "abi sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/utils/abi", - "author": "Sequence Platforms ULC", - "license": "Apache-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo", - "typecheck": "tsc --noEmit", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } -} diff --git a/packages/utils/abi/src/index.ts b/packages/utils/abi/src/index.ts deleted file mode 100644 index 39336c543c..0000000000 --- a/packages/utils/abi/src/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { abi as erc5719Abi } from './wallet/erc5719.js' -export { abi as erc1271Abi } from './wallet/erc1271.js' -export { abi as erc6492Abi } from './wallet/erc6492.js' -export { abi as factoryAbi } from './wallet/factory.js' -export { abi as mainModuleAbi } from './wallet/mainModule.js' -export { abi as mainModuleUpgradableAbi } from './wallet/mainModuleUpgradable.js' -export { abi as moduleHooksAbi } from './wallet/moduleHooks.js' -export { abi as sequenceUtilsAbi } from './wallet/sequenceUtils.js' -export { abi as requireFreshSignerAbi } from './wallet/libs/requireFreshSigners.js' -export { abi as walletProxyHookAbi } from './wallet/walletProxyHook.js' - -export { walletContracts } from './wallet/index.js' - -export { ERC1155_ABI } from './tokens/erc1155.js' -export { ERC1155_ITEMS_ABI } from './tokens/erc1155Items.js' -export { ERC20_ABI } from './tokens/erc20.js' -export { ERC6909_ABI } from './tokens/erc6909.js' -export { ERC721_ABI } from './tokens/erc721.js' -export { ERC721_ITEMS_ABI } from './tokens/erc721Items.js' - -export { ERC1155_SALE_ABI } from './sale/erc1155Sale.js' -export { ERC721_SALE_ABI } from './sale/erc721Sale.js' diff --git a/packages/utils/abi/src/sale/erc1155Sale.ts b/packages/utils/abi/src/sale/erc1155Sale.ts deleted file mode 100644 index cbc20ff0e7..0000000000 --- a/packages/utils/abi/src/sale/erc1155Sale.ts +++ /dev/null @@ -1,352 +0,0 @@ -export const ERC1155_SALE_ABI = [ - { - type: 'function', - name: 'DEFAULT_ADMIN_ROLE', - inputs: [], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'checkMerkleProof', - inputs: [ - { name: 'root', type: 'bytes32', internalType: 'bytes32' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'salt', type: 'bytes32', internalType: 'bytes32' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleAdmin', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMember', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'index', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMemberCount', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'grantRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'hasRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'initialize', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'items', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'itemsContract', - inputs: [], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'mint', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'maxTotal', type: 'uint256', internalType: 'uint256' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'function', - name: 'renounceRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'revokeRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'saleDetails', - inputs: [], - outputs: [ - { - name: '', - type: 'tuple', - internalType: 'struct IERC721SaleFunctions.SaleDetails', - components: [ - { - name: 'supplyCap', - type: 'uint256', - internalType: 'uint256', - }, - { name: 'cost', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'startTime', type: 'uint64', internalType: 'uint64' }, - { name: 'endTime', type: 'uint64', internalType: 'uint64' }, - { - name: 'merkleRoot', - type: 'bytes32', - internalType: 'bytes32', - }, - ], - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'setSaleDetails', - inputs: [ - { name: 'supplyCap', type: 'uint256', internalType: 'uint256' }, - { name: 'cost', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'startTime', type: 'uint64', internalType: 'uint64' }, - { name: 'endTime', type: 'uint64', internalType: 'uint64' }, - { name: 'merkleRoot', type: 'bytes32', internalType: 'bytes32' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'supportsInterface', - inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'withdrawERC20', - inputs: [ - { name: 'token', type: 'address', internalType: 'address' }, - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'value', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'withdrawETH', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'value', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'event', - name: 'RoleAdminChanged', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'previousAdminRole', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'newAdminRole', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleGranted', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'account', - type: 'address', - indexed: true, - internalType: 'address', - }, - { - name: 'sender', - type: 'address', - indexed: true, - internalType: 'address', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleRevoked', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'account', - type: 'address', - indexed: true, - internalType: 'address', - }, - { - name: 'sender', - type: 'address', - indexed: true, - internalType: 'address', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'SaleDetailsUpdated', - inputs: [ - { - name: 'supplyCap', - type: 'uint256', - indexed: false, - internalType: 'uint256', - }, - { - name: 'cost', - type: 'uint256', - indexed: false, - internalType: 'uint256', - }, - { - name: 'paymentToken', - type: 'address', - indexed: false, - internalType: 'address', - }, - { - name: 'startTime', - type: 'uint64', - indexed: false, - internalType: 'uint64', - }, - { - name: 'endTime', - type: 'uint64', - indexed: false, - internalType: 'uint64', - }, - { - name: 'merkleRoot', - type: 'bytes32', - indexed: false, - internalType: 'bytes32', - }, - ], - anonymous: false, - }, - { - type: 'error', - name: 'InsufficientPayment', - inputs: [ - { name: 'currency', type: 'address', internalType: 'address' }, - { name: 'expected', type: 'uint256', internalType: 'uint256' }, - { name: 'actual', type: 'uint256', internalType: 'uint256' }, - ], - }, - { - type: 'error', - name: 'InsufficientSupply', - inputs: [ - { - name: 'currentSupply', - type: 'uint256', - internalType: 'uint256', - }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - { name: 'maxSupply', type: 'uint256', internalType: 'uint256' }, - ], - }, - { type: 'error', name: 'InvalidInitialization', inputs: [] }, - { type: 'error', name: 'InvalidSaleDetails', inputs: [] }, - { - type: 'error', - name: 'MerkleProofInvalid', - inputs: [ - { name: 'root', type: 'bytes32', internalType: 'bytes32' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'salt', type: 'bytes32', internalType: 'bytes32' }, - ], - }, - { type: 'error', name: 'SaleInactive', inputs: [] }, - { type: 'error', name: 'WithdrawFailed', inputs: [] }, -] as const diff --git a/packages/utils/abi/src/sale/erc721Sale.ts b/packages/utils/abi/src/sale/erc721Sale.ts deleted file mode 100644 index c03a0977bf..0000000000 --- a/packages/utils/abi/src/sale/erc721Sale.ts +++ /dev/null @@ -1,352 +0,0 @@ -export const ERC721_SALE_ABI = [ - { - type: 'function', - name: 'DEFAULT_ADMIN_ROLE', - inputs: [], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'checkMerkleProof', - inputs: [ - { name: 'root', type: 'bytes32', internalType: 'bytes32' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'salt', type: 'bytes32', internalType: 'bytes32' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleAdmin', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMember', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'index', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMemberCount', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'grantRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'hasRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'initialize', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'items', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'itemsContract', - inputs: [], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'mint', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'maxTotal', type: 'uint256', internalType: 'uint256' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'function', - name: 'renounceRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'revokeRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'saleDetails', - inputs: [], - outputs: [ - { - name: '', - type: 'tuple', - internalType: 'struct IERC721SaleFunctions.SaleDetails', - components: [ - { - name: 'supplyCap', - type: 'uint256', - internalType: 'uint256', - }, - { name: 'cost', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'startTime', type: 'uint64', internalType: 'uint64' }, - { name: 'endTime', type: 'uint64', internalType: 'uint64' }, - { - name: 'merkleRoot', - type: 'bytes32', - internalType: 'bytes32', - }, - ], - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'setSaleDetails', - inputs: [ - { name: 'supplyCap', type: 'uint256', internalType: 'uint256' }, - { name: 'cost', type: 'uint256', internalType: 'uint256' }, - { - name: 'paymentToken', - type: 'address', - internalType: 'address', - }, - { name: 'startTime', type: 'uint64', internalType: 'uint64' }, - { name: 'endTime', type: 'uint64', internalType: 'uint64' }, - { name: 'merkleRoot', type: 'bytes32', internalType: 'bytes32' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'supportsInterface', - inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'withdrawERC20', - inputs: [ - { name: 'token', type: 'address', internalType: 'address' }, - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'value', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'withdrawETH', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'value', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'event', - name: 'RoleAdminChanged', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'previousAdminRole', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'newAdminRole', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleGranted', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'account', - type: 'address', - indexed: true, - internalType: 'address', - }, - { - name: 'sender', - type: 'address', - indexed: true, - internalType: 'address', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleRevoked', - inputs: [ - { - name: 'role', - type: 'bytes32', - indexed: true, - internalType: 'bytes32', - }, - { - name: 'account', - type: 'address', - indexed: true, - internalType: 'address', - }, - { - name: 'sender', - type: 'address', - indexed: true, - internalType: 'address', - }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'SaleDetailsUpdated', - inputs: [ - { - name: 'supplyCap', - type: 'uint256', - indexed: false, - internalType: 'uint256', - }, - { - name: 'cost', - type: 'uint256', - indexed: false, - internalType: 'uint256', - }, - { - name: 'paymentToken', - type: 'address', - indexed: false, - internalType: 'address', - }, - { - name: 'startTime', - type: 'uint64', - indexed: false, - internalType: 'uint64', - }, - { - name: 'endTime', - type: 'uint64', - indexed: false, - internalType: 'uint64', - }, - { - name: 'merkleRoot', - type: 'bytes32', - indexed: false, - internalType: 'bytes32', - }, - ], - anonymous: false, - }, - { - type: 'error', - name: 'InsufficientPayment', - inputs: [ - { name: 'currency', type: 'address', internalType: 'address' }, - { name: 'expected', type: 'uint256', internalType: 'uint256' }, - { name: 'actual', type: 'uint256', internalType: 'uint256' }, - ], - }, - { - type: 'error', - name: 'InsufficientSupply', - inputs: [ - { - name: 'currentSupply', - type: 'uint256', - internalType: 'uint256', - }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - { name: 'maxSupply', type: 'uint256', internalType: 'uint256' }, - ], - }, - { type: 'error', name: 'InvalidInitialization', inputs: [] }, - { type: 'error', name: 'InvalidSaleDetails', inputs: [] }, - { - type: 'error', - name: 'MerkleProofInvalid', - inputs: [ - { name: 'root', type: 'bytes32', internalType: 'bytes32' }, - { name: 'proof', type: 'bytes32[]', internalType: 'bytes32[]' }, - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'salt', type: 'bytes32', internalType: 'bytes32' }, - ], - }, - { type: 'error', name: 'SaleInactive', inputs: [] }, - { type: 'error', name: 'WithdrawFailed', inputs: [] }, -] as const diff --git a/packages/utils/abi/src/tokens/erc1155.ts b/packages/utils/abi/src/tokens/erc1155.ts deleted file mode 100644 index 3776277b09..0000000000 --- a/packages/utils/abi/src/tokens/erc1155.ts +++ /dev/null @@ -1,422 +0,0 @@ -// @openzeppelin/contracts@5.0.0/token/ERC1155/ERC1155.sol -export const ERC1155_ABI = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'balance', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'needed', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ERC1155InsufficientBalance', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'approver', - type: 'address', - }, - ], - name: 'ERC1155InvalidApprover', - type: 'error', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'idsLength', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'valuesLength', - type: 'uint256', - }, - ], - name: 'ERC1155InvalidArrayLength', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'ERC1155InvalidOperator', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - ], - name: 'ERC1155InvalidReceiver', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - ], - name: 'ERC1155InvalidSender', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'ERC1155MissingApprovalForAll', - type: 'error', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'account', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: false, - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256[]', - name: 'ids', - type: 'uint256[]', - }, - { - indexed: false, - internalType: 'uint256[]', - name: 'values', - type: 'uint256[]', - }, - ], - name: 'TransferBatch', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'TransferSingle', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'string', - name: 'value', - type: 'string', - }, - { - indexed: true, - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'URI', - type: 'event', - }, - { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'accounts', - type: 'address[]', - }, - { - internalType: 'uint256[]', - name: 'ids', - type: 'uint256[]', - }, - ], - name: 'balanceOfBatch', - outputs: [ - { - internalType: 'uint256[]', - name: '', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'isApprovedForAll', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256[]', - name: 'ids', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'values', - type: 'uint256[]', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'safeBatchTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: 'interfaceId', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - name: 'uri', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/tokens/erc1155Items.ts b/packages/utils/abi/src/tokens/erc1155Items.ts deleted file mode 100644 index dc8f99706b..0000000000 --- a/packages/utils/abi/src/tokens/erc1155Items.ts +++ /dev/null @@ -1,378 +0,0 @@ -//An ERC 1155 token contract with batchMint support, to make it compatible with Sequence Sales contracts (../sale/erc1155Sale.ts) -export const ERC1155_ITEMS_ABI = [ - { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, - { - type: 'function', - name: 'DEFAULT_ADMIN_ROLE', - inputs: [], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'balanceOf', - inputs: [ - { name: '_owner', type: 'address', internalType: 'address' }, - { name: '_id', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'balanceOfBatch', - inputs: [ - { name: '_owners', type: 'address[]', internalType: 'address[]' }, - { name: '_ids', type: 'uint256[]', internalType: 'uint256[]' }, - ], - outputs: [{ name: '', type: 'uint256[]', internalType: 'uint256[]' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'baseURI', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'batchBurn', - inputs: [ - { name: 'tokenIds', type: 'uint256[]', internalType: 'uint256[]' }, - { name: 'amounts', type: 'uint256[]', internalType: 'uint256[]' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'batchMint', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenIds', type: 'uint256[]', internalType: 'uint256[]' }, - { name: 'amounts', type: 'uint256[]', internalType: 'uint256[]' }, - { name: 'data', type: 'bytes', internalType: 'bytes' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'burn', - inputs: [ - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'contractURI', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleAdmin', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMember', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'index', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMemberCount', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'grantRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'hasRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'initialize', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'tokenName', type: 'string', internalType: 'string' }, - { name: 'tokenBaseURI', type: 'string', internalType: 'string' }, - { name: 'tokenContractURI', type: 'string', internalType: 'string' }, - { name: 'royaltyReceiver', type: 'address', internalType: 'address' }, - { name: 'royaltyFeeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'isApprovedForAll', - inputs: [ - { name: '_owner', type: 'address', internalType: 'address' }, - { name: '_operator', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: 'isOperator', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'mint', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - { name: 'data', type: 'bytes', internalType: 'bytes' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'name', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'renounceRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'revokeRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'royaltyInfo', - inputs: [ - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'salePrice', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [ - { name: '', type: 'address', internalType: 'address' }, - { name: '', type: 'uint256', internalType: 'uint256' }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'safeBatchTransferFrom', - inputs: [ - { name: '_from', type: 'address', internalType: 'address' }, - { name: '_to', type: 'address', internalType: 'address' }, - { name: '_ids', type: 'uint256[]', internalType: 'uint256[]' }, - { name: '_amounts', type: 'uint256[]', internalType: 'uint256[]' }, - { name: '_data', type: 'bytes', internalType: 'bytes' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'safeTransferFrom', - inputs: [ - { name: '_from', type: 'address', internalType: 'address' }, - { name: '_to', type: 'address', internalType: 'address' }, - { name: '_id', type: 'uint256', internalType: 'uint256' }, - { name: '_amount', type: 'uint256', internalType: 'uint256' }, - { name: '_data', type: 'bytes', internalType: 'bytes' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setApprovalForAll', - inputs: [ - { name: '_operator', type: 'address', internalType: 'address' }, - { name: '_approved', type: 'bool', internalType: 'bool' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setBaseMetadataURI', - inputs: [{ name: 'tokenBaseURI', type: 'string', internalType: 'string' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setContractName', - inputs: [{ name: 'tokenName', type: 'string', internalType: 'string' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setContractURI', - inputs: [{ name: 'tokenContractURI', type: 'string', internalType: 'string' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setDefaultRoyalty', - inputs: [ - { name: 'receiver', type: 'address', internalType: 'address' }, - { name: 'feeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setTokenRoyalty', - inputs: [ - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'receiver', type: 'address', internalType: 'address' }, - { name: 'feeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'supportsInterface', - inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'tokenSupply', - inputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'totalSupply', - inputs: [], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'uri', - inputs: [{ name: '_id', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'event', - name: 'ApprovalForAll', - inputs: [ - { name: '_owner', type: 'address', indexed: true, internalType: 'address' }, - { name: '_operator', type: 'address', indexed: true, internalType: 'address' }, - { name: '_approved', type: 'bool', indexed: false, internalType: 'bool' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleAdminChanged', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'previousAdminRole', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'newAdminRole', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleGranted', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'account', type: 'address', indexed: true, internalType: 'address' }, - { name: 'sender', type: 'address', indexed: true, internalType: 'address' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleRevoked', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'account', type: 'address', indexed: true, internalType: 'address' }, - { name: 'sender', type: 'address', indexed: true, internalType: 'address' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'TransferBatch', - inputs: [ - { name: '_operator', type: 'address', indexed: true, internalType: 'address' }, - { name: '_from', type: 'address', indexed: true, internalType: 'address' }, - { name: '_to', type: 'address', indexed: true, internalType: 'address' }, - { name: '_ids', type: 'uint256[]', indexed: false, internalType: 'uint256[]' }, - { name: '_amounts', type: 'uint256[]', indexed: false, internalType: 'uint256[]' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'TransferSingle', - inputs: [ - { name: '_operator', type: 'address', indexed: true, internalType: 'address' }, - { name: '_from', type: 'address', indexed: true, internalType: 'address' }, - { name: '_to', type: 'address', indexed: true, internalType: 'address' }, - { name: '_id', type: 'uint256', indexed: false, internalType: 'uint256' }, - { name: '_amount', type: 'uint256', indexed: false, internalType: 'uint256' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'URI', - inputs: [ - { name: '_uri', type: 'string', indexed: false, internalType: 'string' }, - { name: '_id', type: 'uint256', indexed: true, internalType: 'uint256' }, - ], - anonymous: false, - }, - { type: 'error', name: 'InvalidArrayLength', inputs: [] }, - { type: 'error', name: 'InvalidInitialization', inputs: [] }, -] as const diff --git a/packages/utils/abi/src/tokens/erc20.ts b/packages/utils/abi/src/tokens/erc20.ts deleted file mode 100644 index f8136eecd8..0000000000 --- a/packages/utils/abi/src/tokens/erc20.ts +++ /dev/null @@ -1,316 +0,0 @@ -// @openzeppelin/contracts@5.0.0/token/ERC20/ERC20.sol -export const ERC20_ABI = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'allowance', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'needed', - type: 'uint256', - }, - ], - name: 'ERC20InsufficientAllowance', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'balance', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'needed', - type: 'uint256', - }, - ], - name: 'ERC20InsufficientBalance', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'approver', - type: 'address', - }, - ], - name: 'ERC20InvalidApprover', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - ], - name: 'ERC20InvalidReceiver', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - ], - name: 'ERC20InvalidSender', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - ], - name: 'ERC20InvalidSpender', - type: 'error', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - ], - name: 'allowance', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'approve', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'decimals', - outputs: [ - { - internalType: 'uint8', - name: '', - type: 'uint8', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'transfer', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'transferFrom', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/tokens/erc6909.ts b/packages/utils/abi/src/tokens/erc6909.ts deleted file mode 100644 index c15bfd90e0..0000000000 --- a/packages/utils/abi/src/tokens/erc6909.ts +++ /dev/null @@ -1,404 +0,0 @@ -// @openzeppelin/contracts@5.0.0/token/ERC6909/ERC6909.sol -export const ERC6909_ABI = [ - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'allowance', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'needed', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'ERC6909InsufficientAllowance', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'balance', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'needed', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'ERC6909InsufficientBalance', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'approver', - type: 'address', - }, - ], - name: 'ERC6909InvalidApprover', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - ], - name: 'ERC6909InvalidReceiver', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - ], - name: 'ERC6909InvalidSender', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - ], - name: 'ERC6909InvalidSpender', - type: 'error', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - indexed: false, - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'OperatorSet', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'address', - name: 'caller', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'receiver', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - indexed: false, - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'allowance', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'approve', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - ], - name: 'isOperator', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'spender', - type: 'address', - }, - { - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'setOperator', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: 'interfaceId', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'transfer', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256', - }, - ], - name: 'transferFrom', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/tokens/erc721.ts b/packages/utils/abi/src/tokens/erc721.ts deleted file mode 100644 index fde46fef0a..0000000000 --- a/packages/utils/abi/src/tokens/erc721.ts +++ /dev/null @@ -1,441 +0,0 @@ -// @openzeppelin/contracts@5.0.0/token/ERC721/ERC721.sol -export const ERC721_ABI = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'ERC721IncorrectOwner', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ERC721InsufficientApproval', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'approver', - type: 'address', - }, - ], - name: 'ERC721InvalidApprover', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'ERC721InvalidOperator', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'ERC721InvalidOwner', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'receiver', - type: 'address', - }, - ], - name: 'ERC721InvalidReceiver', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: 'sender', - type: 'address', - }, - ], - name: 'ERC721InvalidSender', - type: 'error', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ERC721NonexistentToken', - type: 'error', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'approved', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - indexed: false, - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address', - }, - { - indexed: true, - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - ], - name: 'balanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'getApproved', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - ], - name: 'isApprovedForAll', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'ownerOf', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'operator', - type: 'address', - }, - { - internalType: 'bool', - name: 'approved', - type: 'bool', - }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: 'interfaceId', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'tokenURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/tokens/erc721Items.ts b/packages/utils/abi/src/tokens/erc721Items.ts deleted file mode 100644 index 9409db9caf..0000000000 --- a/packages/utils/abi/src/tokens/erc721Items.ts +++ /dev/null @@ -1,441 +0,0 @@ -//An ERC 721 token contract with batchMint support, to make it compatible with Sequence Sales contracts (../sale/erc721Sale.ts) -export const ERC721_ITEMS_ABI = [ - { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, - { - type: 'function', - name: 'DEFAULT_ADMIN_ROLE', - inputs: [], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'approve', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'function', - name: 'balanceOf', - inputs: [{ name: 'owner', type: 'address', internalType: 'address' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'batchBurn', - inputs: [{ name: 'tokenIds', type: 'uint256[]', internalType: 'uint256[]' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'burn', - inputs: [{ name: 'tokenId', type: 'uint256', internalType: 'uint256' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'contractURI', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'explicitOwnershipOf', - inputs: [{ name: 'tokenId', type: 'uint256', internalType: 'uint256' }], - outputs: [ - { - name: '', - type: 'tuple', - internalType: 'struct IERC721A.TokenOwnership', - components: [ - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'startTimestamp', type: 'uint64', internalType: 'uint64' }, - { name: 'burned', type: 'bool', internalType: 'bool' }, - { name: 'extraData', type: 'uint24', internalType: 'uint24' }, - ], - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'explicitOwnershipsOf', - inputs: [{ name: 'tokenIds', type: 'uint256[]', internalType: 'uint256[]' }], - outputs: [ - { - name: '', - type: 'tuple[]', - internalType: 'struct IERC721A.TokenOwnership[]', - components: [ - { name: 'addr', type: 'address', internalType: 'address' }, - { name: 'startTimestamp', type: 'uint64', internalType: 'uint64' }, - { name: 'burned', type: 'bool', internalType: 'bool' }, - { name: 'extraData', type: 'uint24', internalType: 'uint24' }, - ], - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getApproved', - inputs: [{ name: 'tokenId', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleAdmin', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMember', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'index', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'getRoleMemberCount', - inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'grantRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'hasRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'initialize', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'tokenName', type: 'string', internalType: 'string' }, - { name: 'tokenSymbol', type: 'string', internalType: 'string' }, - { name: 'tokenBaseURI', type: 'string', internalType: 'string' }, - { name: 'tokenContractURI', type: 'string', internalType: 'string' }, - { name: 'royaltyReceiver', type: 'address', internalType: 'address' }, - { name: 'royaltyFeeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'isApprovedForAll', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'operator', type: 'address', internalType: 'address' }, - ], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'mint', - inputs: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'amount', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'name', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'ownerOf', - inputs: [{ name: 'tokenId', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'renounceRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'revokeRole', - inputs: [ - { name: 'role', type: 'bytes32', internalType: 'bytes32' }, - { name: 'account', type: 'address', internalType: 'address' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'royaltyInfo', - inputs: [ - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'salePrice', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [ - { name: '', type: 'address', internalType: 'address' }, - { name: '', type: 'uint256', internalType: 'uint256' }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'safeTransferFrom', - inputs: [ - { name: 'from', type: 'address', internalType: 'address' }, - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'function', - name: 'safeTransferFrom', - inputs: [ - { name: 'from', type: 'address', internalType: 'address' }, - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: '_data', type: 'bytes', internalType: 'bytes' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'function', - name: 'setApprovalForAll', - inputs: [ - { name: 'operator', type: 'address', internalType: 'address' }, - { name: 'approved', type: 'bool', internalType: 'bool' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setBaseMetadataURI', - inputs: [{ name: 'tokenBaseURI', type: 'string', internalType: 'string' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setContractURI', - inputs: [{ name: 'tokenContractURI', type: 'string', internalType: 'string' }], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setDefaultRoyalty', - inputs: [ - { name: 'receiver', type: 'address', internalType: 'address' }, - { name: 'feeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setNameAndSymbol', - inputs: [ - { name: 'tokenName', type: 'string', internalType: 'string' }, - { name: 'tokenSymbol', type: 'string', internalType: 'string' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setTokenRoyalty', - inputs: [ - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - { name: 'receiver', type: 'address', internalType: 'address' }, - { name: 'feeNumerator', type: 'uint96', internalType: 'uint96' }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'supportsInterface', - inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], - outputs: [{ name: '', type: 'bool', internalType: 'bool' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'symbol', - inputs: [], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'tokenURI', - inputs: [{ name: 'tokenId', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'string', internalType: 'string' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'tokensOfOwner', - inputs: [{ name: 'owner', type: 'address', internalType: 'address' }], - outputs: [{ name: '', type: 'uint256[]', internalType: 'uint256[]' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'tokensOfOwnerIn', - inputs: [ - { name: 'owner', type: 'address', internalType: 'address' }, - { name: 'start', type: 'uint256', internalType: 'uint256' }, - { name: 'stop', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [{ name: '', type: 'uint256[]', internalType: 'uint256[]' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'totalSupply', - inputs: [], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'transferFrom', - inputs: [ - { name: 'from', type: 'address', internalType: 'address' }, - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'tokenId', type: 'uint256', internalType: 'uint256' }, - ], - outputs: [], - stateMutability: 'payable', - }, - { - type: 'event', - name: 'Approval', - inputs: [ - { name: 'owner', type: 'address', indexed: true, internalType: 'address' }, - { name: 'approved', type: 'address', indexed: true, internalType: 'address' }, - { name: 'tokenId', type: 'uint256', indexed: true, internalType: 'uint256' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'ApprovalForAll', - inputs: [ - { name: 'owner', type: 'address', indexed: true, internalType: 'address' }, - { name: 'operator', type: 'address', indexed: true, internalType: 'address' }, - { name: 'approved', type: 'bool', indexed: false, internalType: 'bool' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'ConsecutiveTransfer', - inputs: [ - { name: 'fromTokenId', type: 'uint256', indexed: true, internalType: 'uint256' }, - { name: 'toTokenId', type: 'uint256', indexed: false, internalType: 'uint256' }, - { name: 'from', type: 'address', indexed: true, internalType: 'address' }, - { name: 'to', type: 'address', indexed: true, internalType: 'address' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleAdminChanged', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'previousAdminRole', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'newAdminRole', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleGranted', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'account', type: 'address', indexed: true, internalType: 'address' }, - { name: 'sender', type: 'address', indexed: true, internalType: 'address' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'RoleRevoked', - inputs: [ - { name: 'role', type: 'bytes32', indexed: true, internalType: 'bytes32' }, - { name: 'account', type: 'address', indexed: true, internalType: 'address' }, - { name: 'sender', type: 'address', indexed: true, internalType: 'address' }, - ], - anonymous: false, - }, - { - type: 'event', - name: 'Transfer', - inputs: [ - { name: 'from', type: 'address', indexed: true, internalType: 'address' }, - { name: 'to', type: 'address', indexed: true, internalType: 'address' }, - { name: 'tokenId', type: 'uint256', indexed: true, internalType: 'uint256' }, - ], - anonymous: false, - }, - { type: 'error', name: 'ApprovalCallerNotOwnerNorApproved', inputs: [] }, - { type: 'error', name: 'ApprovalQueryForNonexistentToken', inputs: [] }, - { type: 'error', name: 'BalanceQueryForZeroAddress', inputs: [] }, - { type: 'error', name: 'InvalidInitialization', inputs: [] }, - { type: 'error', name: 'InvalidQueryRange', inputs: [] }, - { type: 'error', name: 'MintERC2309QuantityExceedsLimit', inputs: [] }, - { type: 'error', name: 'MintToZeroAddress', inputs: [] }, - { type: 'error', name: 'MintZeroQuantity', inputs: [] }, - { type: 'error', name: 'OwnerQueryForNonexistentToken', inputs: [] }, - { type: 'error', name: 'OwnershipNotInitializedForExtraData', inputs: [] }, - { type: 'error', name: 'TransferCallerNotOwnerNorApproved', inputs: [] }, - { type: 'error', name: 'TransferFromIncorrectOwner', inputs: [] }, - { type: 'error', name: 'TransferToNonERC721ReceiverImplementer', inputs: [] }, - { type: 'error', name: 'TransferToZeroAddress', inputs: [] }, - { type: 'error', name: 'URIQueryForNonexistentToken', inputs: [] }, -] as const diff --git a/packages/utils/abi/src/wallet/erc1271.ts b/packages/utils/abi/src/wallet/erc1271.ts deleted file mode 100644 index 747aaa33dd..0000000000 --- a/packages/utils/abi/src/wallet/erc1271.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const abi = [ - { - type: 'function', - name: 'isValidSignature', - constant: true, - inputs: [ - { - type: 'bytes32', - }, - { - type: 'bytes', - }, - ], - outputs: [ - { - type: 'bytes4', - }, - ], - payable: false, - stateMutability: 'view', - }, -] as const - -export const returns = { - isValidSignatureBytes32: '0x1626ba7e', -} diff --git a/packages/utils/abi/src/wallet/erc5719.ts b/packages/utils/abi/src/wallet/erc5719.ts deleted file mode 100644 index 52c9e7e488..0000000000 --- a/packages/utils/abi/src/wallet/erc5719.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const abi = [ - { - inputs: [ - { - internalType: 'bytes32', - type: 'bytes32', - }, - ], - name: 'getAlternativeSignature', - outputs: [ - { - internalType: 'string', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/wallet/erc6492.ts b/packages/utils/abi/src/wallet/erc6492.ts deleted file mode 100644 index 158bf4a8ed..0000000000 --- a/packages/utils/abi/src/wallet/erc6492.ts +++ /dev/null @@ -1,61 +0,0 @@ -export const abi = [ - { inputs: [{ internalType: 'bytes', name: 'error', type: 'bytes' }], name: 'ERC1271Revert', type: 'error' }, - { inputs: [{ internalType: 'bytes', name: 'error', type: 'bytes' }], name: 'ERC6492DeployFailed', type: 'error' }, - { - inputs: [ - { internalType: 'address', name: '_signer', type: 'address' }, - { internalType: 'bytes32', name: '_hash', type: 'bytes32' }, - { internalType: 'bytes', name: '_signature', type: 'bytes' }, - ], - name: 'isValidSig', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: '_signer', type: 'address' }, - { internalType: 'bytes32', name: '_hash', type: 'bytes32' }, - { internalType: 'bytes', name: '_signature', type: 'bytes' }, - { internalType: 'bool', name: 'allowSideEffects', type: 'bool' }, - { internalType: 'bool', name: 'deployAlreadyDeployed', type: 'bool' }, - ], - name: 'isValidSigImpl', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: '_signer', type: 'address' }, - { internalType: 'bytes32', name: '_hash', type: 'bytes32' }, - { internalType: 'bytes', name: '_signature', type: 'bytes' }, - ], - name: 'isValidSigNoThrow', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: '_signer', type: 'address' }, - { internalType: 'bytes32', name: '_hash', type: 'bytes32' }, - { internalType: 'bytes', name: '_signature', type: 'bytes' }, - ], - name: 'isValidSigWithSideEffects', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: '_signer', type: 'address' }, - { internalType: 'bytes32', name: '_hash', type: 'bytes32' }, - { internalType: 'bytes', name: '_signature', type: 'bytes' }, - ], - name: 'isValidSigWithSideEffectsNoThrow', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/wallet/factory.ts b/packages/utils/abi/src/wallet/factory.ts deleted file mode 100644 index 72d8ef02fd..0000000000 --- a/packages/utils/abi/src/wallet/factory.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const abi = [ - { - type: 'function', - name: 'deploy', - constant: false, - inputs: [ - { - type: 'address', - }, - { - type: 'bytes32', - }, - ], - outputs: [], - payable: true, - stateMutability: 'payable', - }, -] as const diff --git a/packages/utils/abi/src/wallet/index.ts b/packages/utils/abi/src/wallet/index.ts deleted file mode 100644 index 50b567e71c..0000000000 --- a/packages/utils/abi/src/wallet/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as erc5719 from './erc5719.js' -import * as erc1271 from './erc1271.js' -import * as erc6492 from './erc6492.js' -import * as factory from './factory.js' -import * as mainModule from './mainModule.js' -import * as mainModuleUpgradable from './mainModuleUpgradable.js' -import * as moduleHooks from './moduleHooks.js' -import * as sequenceUtils from './sequenceUtils.js' -import * as requireFreshSigner from './libs/requireFreshSigners.js' -import * as walletProxyHook from './walletProxyHook.js' - -/** - * @deprecated import directly from @0xsequence/abi/* instead, omitting "walletContracts" - */ -export const walletContracts = { - erc6492, - erc5719, - erc1271, - factory, - mainModule, - mainModuleUpgradable, - moduleHooks, - sequenceUtils, - requireFreshSigner, - walletProxyHook, -} diff --git a/packages/utils/abi/src/wallet/libs/requireFreshSigners.ts b/packages/utils/abi/src/wallet/libs/requireFreshSigners.ts deleted file mode 100644 index ac0ce50c5c..0000000000 --- a/packages/utils/abi/src/wallet/libs/requireFreshSigners.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const abi = [ - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - name: 'requireFreshSigner', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/wallet/mainModule.ts b/packages/utils/abi/src/wallet/mainModule.ts deleted file mode 100644 index 8d069db058..0000000000 --- a/packages/utils/abi/src/wallet/mainModule.ts +++ /dev/null @@ -1,158 +0,0 @@ -export const abi = [ - { - type: 'function', - name: 'nonce', - constant: true, - inputs: [], - outputs: [ - { - type: 'uint256', - }, - ], - payable: false, - stateMutability: 'view', - }, - { - type: 'function', - name: 'readNonce', - constant: true, - inputs: [ - { - type: 'uint256', - }, - ], - outputs: [ - { - type: 'uint256', - }, - ], - payable: false, - stateMutability: 'view', - }, - { - type: 'function', - name: 'updateImplementation', - constant: false, - inputs: [ - { - type: 'address', - }, - ], - outputs: [], - payable: false, - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'selfExecute', - constant: false, - inputs: [ - { - components: [ - { - type: 'bool', - name: 'delegateCall', - }, - { - type: 'bool', - name: 'revertOnError', - }, - { - type: 'uint256', - name: 'gasLimit', - }, - { - type: 'address', - name: 'target', - }, - { - type: 'uint256', - name: 'value', - }, - { - type: 'bytes', - name: 'data', - }, - ], - type: 'tuple[]', - }, - ], - outputs: [], - payable: false, - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'execute', - constant: false, - inputs: [ - { - components: [ - { - type: 'bool', - name: 'delegateCall', - }, - { - type: 'bool', - name: 'revertOnError', - }, - { - type: 'uint256', - name: 'gasLimit', - }, - { - type: 'address', - name: 'target', - }, - { - type: 'uint256', - name: 'value', - }, - { - type: 'bytes', - name: 'data', - }, - ], - type: 'tuple[]', - }, - { - type: 'uint256', - }, - { - type: 'bytes', - }, - ], - outputs: [], - payable: false, - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'createContract', - inputs: [ - { - type: 'bytes', - }, - ], - payable: true, - stateMutability: 'payable', - }, - { - type: 'function', - name: 'setExtraImageHash', - constant: false, - inputs: [ - { - type: 'bytes32', - name: 'imageHash', - }, - { - type: 'uint256', - name: 'expiration', - }, - ], - outputs: [], - payable: false, - stateMutability: 'nonpayable', - }, -] as const diff --git a/packages/utils/abi/src/wallet/mainModuleUpgradable.ts b/packages/utils/abi/src/wallet/mainModuleUpgradable.ts deleted file mode 100644 index 6a3be59ccf..0000000000 --- a/packages/utils/abi/src/wallet/mainModuleUpgradable.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const abi = [ - { - type: 'function', - name: 'updateImageHash', - constant: true, - inputs: [ - { - type: 'bytes32', - }, - ], - outputs: [], - payable: false, - stateMutability: 'view', - }, - { - type: 'function', - name: 'imageHash', - constant: true, - inputs: [], - outputs: [ - { - type: 'bytes32', - }, - ], - payable: false, - stateMutability: 'view', - }, -] as const diff --git a/packages/utils/abi/src/wallet/moduleHooks.ts b/packages/utils/abi/src/wallet/moduleHooks.ts deleted file mode 100644 index 93a1dbfddc..0000000000 --- a/packages/utils/abi/src/wallet/moduleHooks.ts +++ /dev/null @@ -1,248 +0,0 @@ -export const abi = [ - { - inputs: [ - { - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - ], - name: 'HookAlreadyExists', - type: 'error', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - ], - name: 'HookDoesNotExist', - type: 'error', - }, - { - inputs: [ - { - internalType: 'address', - name: '_sender', - type: 'address', - }, - { - internalType: 'address', - name: '_self', - type: 'address', - }, - ], - name: 'OnlySelfAuth', - type: 'error', - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - { - indexed: false, - internalType: 'address', - name: '_implementation', - type: 'address', - }, - ], - name: 'DefinedHook', - type: 'event', - }, - { - stateMutability: 'payable', - type: 'fallback', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - { - internalType: 'address', - name: '_implementation', - type: 'address', - }, - ], - name: 'addHook', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'uint256[]', - name: '', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: '', - type: 'uint256[]', - }, - { - internalType: 'bytes', - name: '', - type: 'bytes', - }, - ], - name: 'onERC1155BatchReceived', - outputs: [ - { - internalType: 'bytes4', - name: '', - type: 'bytes4', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - { - internalType: 'bytes', - name: '', - type: 'bytes', - }, - ], - name: 'onERC1155Received', - outputs: [ - { - internalType: 'bytes4', - name: '', - type: 'bytes4', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'address', - name: '', - type: 'address', - }, - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - { - internalType: 'bytes', - name: '', - type: 'bytes', - }, - ], - name: 'onERC721Received', - outputs: [ - { - internalType: 'bytes4', - name: '', - type: 'bytes4', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - ], - name: 'readHook', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: '_signature', - type: 'bytes4', - }, - ], - name: 'removeHook', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes4', - name: '_interfaceID', - type: 'bytes4', - }, - ], - name: 'supportsInterface', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - stateMutability: 'payable', - type: 'receive', - }, -] as const diff --git a/packages/utils/abi/src/wallet/sequenceUtils.ts b/packages/utils/abi/src/wallet/sequenceUtils.ts deleted file mode 100644 index 2480e830f9..0000000000 --- a/packages/utils/abi/src/wallet/sequenceUtils.ts +++ /dev/null @@ -1,516 +0,0 @@ -export const abi = [ - { - inputs: [ - { - internalType: 'address', - name: '_factory', - type: 'address', - }, - { - internalType: 'address', - name: '_mainModule', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: '_wallet', - type: 'address', - }, - { - indexed: true, - internalType: 'bytes32', - name: '_imageHash', - type: 'bytes32', - }, - { - indexed: false, - internalType: 'uint256', - name: '_threshold', - type: 'uint256', - }, - { - indexed: false, - internalType: 'bytes', - name: '_signers', - type: 'bytes', - }, - ], - name: 'RequiredConfig', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: '_wallet', - type: 'address', - }, - { - indexed: true, - internalType: 'address', - name: '_signer', - type: 'address', - }, - ], - name: 'RequiredSigner', - type: 'event', - }, - { - inputs: [ - { - internalType: 'address', - name: '_addr', - type: 'address', - }, - ], - name: 'callBalanceOf', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callBlockNumber', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: '_i', - type: 'uint256', - }, - ], - name: 'callBlockhash', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callChainId', - outputs: [ - { - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_addr', - type: 'address', - }, - ], - name: 'callCode', - outputs: [ - { - internalType: 'bytes', - name: 'code', - type: 'bytes', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_addr', - type: 'address', - }, - ], - name: 'callCodeHash', - outputs: [ - { - internalType: 'bytes32', - name: 'codeHash', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_addr', - type: 'address', - }, - ], - name: 'callCodeSize', - outputs: [ - { - internalType: 'uint256', - name: 'size', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callCoinbase', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callDifficulty', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callGasLeft', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callGasLimit', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callGasPrice', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callOrigin', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'callTimestamp', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - name: 'knownImageHashes', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32', - }, - ], - name: 'lastImageHashUpdate', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - name: 'lastSignerUpdate', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - name: 'lastWalletUpdate', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - components: [ - { - internalType: 'bool', - name: 'delegateCall', - type: 'bool', - }, - { - internalType: 'bool', - name: 'revertOnError', - type: 'bool', - }, - { - internalType: 'uint256', - name: 'gasLimit', - type: 'uint256', - }, - { - internalType: 'address', - name: 'target', - type: 'address', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - internalType: 'bytes', - name: 'data', - type: 'bytes', - }, - ], - internalType: 'struct IModuleCalls.Transaction[]', - name: '_txs', - type: 'tuple[]', - }, - ], - name: 'multiCall', - outputs: [ - { - internalType: 'bool[]', - name: '_successes', - type: 'bool[]', - }, - { - internalType: 'bytes[]', - name: '_results', - type: 'bytes[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_wallet', - type: 'address', - }, - { - internalType: 'uint256', - name: '_threshold', - type: 'uint256', - }, - { - components: [ - { - internalType: 'uint256', - name: 'weight', - type: 'uint256', - }, - { - internalType: 'address', - name: 'signer', - type: 'address', - }, - ], - internalType: 'struct RequireUtils.Member[]', - name: '_members', - type: 'tuple[]', - }, - { - internalType: 'bool', - name: '_index', - type: 'bool', - }, - ], - name: 'publishConfig', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_wallet', - type: 'address', - }, - { - internalType: 'bytes32', - name: '_hash', - type: 'bytes32', - }, - { - internalType: 'uint256', - name: '_sizeMembers', - type: 'uint256', - }, - { - internalType: 'bytes', - name: '_signature', - type: 'bytes', - }, - { - internalType: 'bool', - name: '_index', - type: 'bool', - }, - ], - name: 'publishInitialSigners', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: '_wallet', - type: 'address', - }, - { - internalType: 'uint256', - name: '_nonce', - type: 'uint256', - }, - ], - name: 'requireMinNonce', - outputs: [], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: '_expiration', - type: 'uint256', - }, - ], - name: 'requireNonExpired', - outputs: [], - stateMutability: 'view', - type: 'function', - }, -] as const diff --git a/packages/utils/abi/src/wallet/walletProxyHook.ts b/packages/utils/abi/src/wallet/walletProxyHook.ts deleted file mode 100644 index dfa00c6a77..0000000000 --- a/packages/utils/abi/src/wallet/walletProxyHook.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const abi = [ - { - type: 'function', - name: 'PROXY_getImplementation', - inputs: [], - outputs: [{ name: '', type: 'address', internalType: 'address' }], - stateMutability: 'view', - }, -] as const diff --git a/packages/utils/abi/tsconfig.json b/packages/utils/abi/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/utils/abi/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md new file mode 100644 index 0000000000..324b962b29 --- /dev/null +++ b/packages/vue/CHANGELOG.md @@ -0,0 +1,706 @@ +# @wagmi/vue + +## 0.1.20 + +### Patch Changes + +- Updated dependencies [[`42b1fed58e9ac09da0f8ebf3e9271f98a707aaac`](https://github.com/wevm/wagmi/commit/42b1fed58e9ac09da0f8ebf3e9271f98a707aaac)]: + - @wagmi/connectors@5.8.3 + +## 0.1.19 + +### Patch Changes + +- Updated dependencies [[`29297a48af72b537173d948ccd2fe37d39914c66`](https://github.com/wevm/wagmi/commit/29297a48af72b537173d948ccd2fe37d39914c66), [`07370106d5fb6b8fe300992d93abf25b3d0eaf57`](https://github.com/wevm/wagmi/commit/07370106d5fb6b8fe300992d93abf25b3d0eaf57)]: + - @wagmi/core@2.17.2 + - @wagmi/connectors@5.8.2 + +## 0.1.18 + +### Patch Changes + +- Updated dependencies [[`01f64e64fa4f85cdd30023903f972f4f9023681f`](https://github.com/wevm/wagmi/commit/01f64e64fa4f85cdd30023903f972f4f9023681f)]: + - @wagmi/core@2.17.1 + - @wagmi/connectors@5.8.1 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies [[`cc5517ff6880bb630f1b201930acc20dd1a0b451`](https://github.com/wevm/wagmi/commit/cc5517ff6880bb630f1b201930acc20dd1a0b451)]: + - @wagmi/connectors@5.8.0 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies [[`88427b2bcd13ec375ef519e9ad1ccffef9f02a7b`](https://github.com/wevm/wagmi/commit/88427b2bcd13ec375ef519e9ad1ccffef9f02a7b), [`799ee4d4b23c2ecd64e3f3668e67634e81939719`](https://github.com/wevm/wagmi/commit/799ee4d4b23c2ecd64e3f3668e67634e81939719), [`3f8b2edc4f237cccff1009bcef03d51ca27a7324`](https://github.com/wevm/wagmi/commit/3f8b2edc4f237cccff1009bcef03d51ca27a7324)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.17.0 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies [[`b59c024b23c69f5459b17390531207cfdf126ce4`](https://github.com/wevm/wagmi/commit/b59c024b23c69f5459b17390531207cfdf126ce4)]: + - @wagmi/connectors@5.7.12 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies [[`a4bd0623eed28e3761a27295831a60ad835f0ee0`](https://github.com/wevm/wagmi/commit/a4bd0623eed28e3761a27295831a60ad835f0ee0)]: + - @wagmi/core@2.16.7 + - @wagmi/connectors@5.7.11 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies [[`edf47477b2f6385a1c3ae01d36a8498c47f30a0b`](https://github.com/wevm/wagmi/commit/edf47477b2f6385a1c3ae01d36a8498c47f30a0b), [`e944812ebc234a72c1417b77cff341166f5e0fef`](https://github.com/wevm/wagmi/commit/e944812ebc234a72c1417b77cff341166f5e0fef)]: + - @wagmi/core@2.16.6 + - @wagmi/connectors@5.7.10 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies [[`5b7101fddb61df56e34b2e02b46bc409e496eaf9`](https://github.com/wevm/wagmi/commit/5b7101fddb61df56e34b2e02b46bc409e496eaf9)]: + - @wagmi/connectors@5.7.9 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies [[`d0c9a86921a4e939373cc6e763284e53f2a2e93c`](https://github.com/wevm/wagmi/commit/d0c9a86921a4e939373cc6e763284e53f2a2e93c)]: + - @wagmi/core@2.16.5 + - @wagmi/connectors@5.7.8 + +## 0.1.10 + +### Patch Changes + +- [`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec) Thanks [@jxom](https://github.com/jxom)! - Updated `@coinbase/wallet-sdk`. + +- Updated dependencies [[`507f864d91238bfd423d0e36d3619eb9f6e52eec`](https://github.com/wevm/wagmi/commit/507f864d91238bfd423d0e36d3619eb9f6e52eec)]: + - @wagmi/connectors@5.7.7 + - @wagmi/core@2.16.4 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies [[`639952c97f0fe3927106f42d3c9f7f366cdf7f7a`](https://github.com/wevm/wagmi/commit/639952c97f0fe3927106f42d3c9f7f366cdf7f7a), [`5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766`](https://github.com/wevm/wagmi/commit/5aa2c095f7bfb6dfcf91c6945c3e1f9c9dd05766)]: + - @wagmi/connectors@5.7.6 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies [[`a257e8d4f97431a4af872cda1817b4ae17c7bbed`](https://github.com/wevm/wagmi/commit/a257e8d4f97431a4af872cda1817b4ae17c7bbed)]: + - @wagmi/connectors@5.7.5 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies [[`c8a257e0f6d2ece013b873895c35769a8a804fdc`](https://github.com/wevm/wagmi/commit/c8a257e0f6d2ece013b873895c35769a8a804fdc)]: + - @wagmi/connectors@5.7.4 + +## 0.1.6 + +### Patch Changes + +- [#4480](https://github.com/wevm/wagmi/pull/4480) [`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8) Thanks [@RodeRickIsWatching](https://github.com/RodeRickIsWatching)! - Fixed invocation of default storage. + +- Updated dependencies [[`384a1d91597622eb59e1c05dc13ce25017c5b6d8`](https://github.com/wevm/wagmi/commit/384a1d91597622eb59e1c05dc13ce25017c5b6d8)]: + - @wagmi/core@2.16.3 + - @wagmi/connectors@5.7.3 + +## 0.1.5 + +### Patch Changes + +- [`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6) Thanks [@jxom](https://github.com/jxom)! - Fixed assignment in `getDefaultStorage`. + +- Updated dependencies [[`012907032b532a438fce48f407470250cbc8f0c6`](https://github.com/wevm/wagmi/commit/012907032b532a438fce48f407470250cbc8f0c6)]: + - @wagmi/core@2.16.2 + - @wagmi/connectors@5.7.2 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies [[`9c8c35a3b829f2c58edcd3a29e2dcd99974d7470`](https://github.com/wevm/wagmi/commit/9c8c35a3b829f2c58edcd3a29e2dcd99974d7470), [`3892ebd21c06beef4b28ece4e70d2a38807bce6f`](https://github.com/wevm/wagmi/commit/3892ebd21c06beef4b28ece4e70d2a38807bce6f)]: + - @wagmi/connectors@5.7.1 + - @wagmi/core@2.16.1 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies [[`e3f63a02c1f7d80481804584f262bc98dab0400d`](https://github.com/wevm/wagmi/commit/e3f63a02c1f7d80481804584f262bc98dab0400d)]: + - @wagmi/connectors@5.7.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85`](https://github.com/wevm/wagmi/commit/adf2253b10c6d4fc583e4bc9f01a8ef5ca267c85)]: + - @wagmi/connectors@5.6.2 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies [[`987404f590c1d29ebb3cb68928f5e54aa032793d`](https://github.com/wevm/wagmi/commit/987404f590c1d29ebb3cb68928f5e54aa032793d)]: + - @wagmi/connectors@5.6.1 + +## 0.1.0 + +### Minor Changes + +- [#4453](https://github.com/wevm/wagmi/pull/4453) [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227) Thanks [@tmm](https://github.com/tmm)! - Added support to `useConnect` for custom `connector.connect` parameters. + +### Patch Changes + +- Updated dependencies [[`afea6b67822a7a2b96901ec851441d27ee0f7a52`](https://github.com/wevm/wagmi/commit/afea6b67822a7a2b96901ec851441d27ee0f7a52), [`070e48480194c8d7f45bda1d7dd1346e6f5d7227`](https://github.com/wevm/wagmi/commit/070e48480194c8d7f45bda1d7dd1346e6f5d7227), [`8b0726c1106fce88b782e676498eabf0718b2619`](https://github.com/wevm/wagmi/commit/8b0726c1106fce88b782e676498eabf0718b2619)]: + - @wagmi/core@2.16.0 + - @wagmi/connectors@5.6.0 + +## 0.0.69 + +### Patch Changes + +- [`2f79a3da4872d6158569017b1927a07a1ff5e7ba`](https://github.com/wevm/wagmi/commit/2f79a3da4872d6158569017b1927a07a1ff5e7ba) Thanks [@tmm](https://github.com/tmm)! - Exported `injected` and `mock`. + +## 0.0.68 + +### Patch Changes + +- [#4433](https://github.com/wevm/wagmi/pull/4433) [`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc) Thanks [@Aerilym](https://github.com/Aerilym)! - Bumped Metamask SDK version to `0.31.1`. + +- Updated dependencies [[`06e186cd679b27fe195309110e766fcf46d4efbc`](https://github.com/wevm/wagmi/commit/06e186cd679b27fe195309110e766fcf46d4efbc)]: + - @wagmi/connectors@5.5.3 + - @wagmi/core@2.15.2 + +## 0.0.67 + +### Patch Changes + +- Updated dependencies [[`e563ef69130a511fd6f3f72ed4cd4fbe1390541f`](https://github.com/wevm/wagmi/commit/e563ef69130a511fd6f3f72ed4cd4fbe1390541f)]: + - @wagmi/connectors@5.5.2 + +## 0.0.66 + +### Patch Changes + +- [`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693) Thanks [@jxom](https://github.com/jxom)! - Fixed issue where `null` gas would accidentally pass through. + +- Updated dependencies [[`b8bbb409f4934538e3dd6cac5aaf7346292d0693`](https://github.com/wevm/wagmi/commit/b8bbb409f4934538e3dd6cac5aaf7346292d0693)]: + - @wagmi/core@2.15.1 + - @wagmi/connectors@5.5.1 + +## 0.0.65 + +### Minor Changes + +- [#4417](https://github.com/wevm/wagmi/pull/4417) [`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141) Thanks [@jxom](https://github.com/jxom)! - Removed simulation in `writeContract` & `sendTransaction`. + +### Patch Changes + +- Updated dependencies [[`42e65ea4fea99c639817088bba915e0933d17141`](https://github.com/wevm/wagmi/commit/42e65ea4fea99c639817088bba915e0933d17141)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.15.0 + +## 0.0.64 + +### Patch Changes + +- Updated dependencies [[`7ca62b44cd997d48f92c2b81343726a5908aa00b`](https://github.com/wevm/wagmi/commit/7ca62b44cd997d48f92c2b81343726a5908aa00b)]: + - @wagmi/connectors@5.4.0 + +## 0.0.63 + +### Patch Changes + +- Updated dependencies [[`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3), [`a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3`](https://github.com/wevm/wagmi/commit/a13aa8d7c38eb3cc8171a02d6302e6d12cf6bcb3)]: + - @wagmi/core@2.14.6 + - @wagmi/connectors@5.3.10 + +## 0.0.62 + +### Patch Changes + +- Updated dependencies [[`b12a04eeec985c48d2feac94b011d41fb29ca23e`](https://github.com/wevm/wagmi/commit/b12a04eeec985c48d2feac94b011d41fb29ca23e)]: + - @wagmi/connectors@5.3.9 + +## 0.0.61 + +### Patch Changes + +- Updated dependencies [[`6b9bbacdc7bffd44fc2165362a5e65fd434e7646`](https://github.com/wevm/wagmi/commit/6b9bbacdc7bffd44fc2165362a5e65fd434e7646), [`dac62dc99a0679fa632a0fae49873d6053d06b35`](https://github.com/wevm/wagmi/commit/dac62dc99a0679fa632a0fae49873d6053d06b35)]: + - @wagmi/core@2.14.5 + - @wagmi/connectors@5.3.8 + +## 0.0.60 + +### Patch Changes + +- Updated dependencies [[`e08681c81fbdf475213e2d0f4c5517d0abf4e743`](https://github.com/wevm/wagmi/commit/e08681c81fbdf475213e2d0f4c5517d0abf4e743)]: + - @wagmi/core@2.14.4 + - @wagmi/connectors@5.3.7 + +## 0.0.59 + +### Patch Changes + +- Updated dependencies [[`7558ff3133c11bc4c49473d08ee9a47eaa12df5b`](https://github.com/wevm/wagmi/commit/7558ff3133c11bc4c49473d08ee9a47eaa12df5b)]: + - @wagmi/connectors@5.3.6 + +## 0.0.58 + +### Patch Changes + +- Updated dependencies [[`cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7`](https://github.com/wevm/wagmi/commit/cb7dd2ebb871d0be8f1a11a8cd8ce592cd74b7c7), [`7fe78f2d09778fc01fd0cffe85ba198e64999275`](https://github.com/wevm/wagmi/commit/7fe78f2d09778fc01fd0cffe85ba198e64999275)]: + - @wagmi/core@2.14.3 + - @wagmi/connectors@5.3.5 + +## 0.0.57 + +### Patch Changes + +- Updated dependencies [[`b6861a4c378dab78d8751ae0ac2aa425f3c24b8f`](https://github.com/wevm/wagmi/commit/b6861a4c378dab78d8751ae0ac2aa425f3c24b8f), [`d0d0963bb5904a15cf0355862d62dd141ce0c31c`](https://github.com/wevm/wagmi/commit/d0d0963bb5904a15cf0355862d62dd141ce0c31c), [`ecac0ba36243d94c9199d0bd21937104c835d9a0`](https://github.com/wevm/wagmi/commit/ecac0ba36243d94c9199d0bd21937104c835d9a0)]: + - @wagmi/connectors@5.3.4 + - @wagmi/core@2.14.2 + +## 0.0.56 + +### Patch Changes + +- Updated dependencies [[`83c6d16b7d6dddfa6bda036e04f00ec313c6248c`](https://github.com/wevm/wagmi/commit/83c6d16b7d6dddfa6bda036e04f00ec313c6248c)]: + - @wagmi/connectors@5.3.3 + +## 0.0.55 + +### Patch Changes + +- Updated dependencies [[`8970cc51398e1ac713435533096215c6d31ffdf9`](https://github.com/wevm/wagmi/commit/8970cc51398e1ac713435533096215c6d31ffdf9)]: + - @wagmi/connectors@5.3.2 + +## 0.0.54 + +### Patch Changes + +- Updated dependencies [[`052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702`](https://github.com/wevm/wagmi/commit/052e72e1f8c1c14fcbdce04a9f8fa7ec28d83702), [`b250fc21ee577b2a75c5a34ff684f62fb4ad771a`](https://github.com/wevm/wagmi/commit/b250fc21ee577b2a75c5a34ff684f62fb4ad771a)]: + - @wagmi/core@2.14.1 + - @wagmi/connectors@5.3.1 + +## 0.0.53 + +### Patch Changes + +- Updated dependencies [[`f43e074f473820b208a6295d7c97f847332f1a1d`](https://github.com/wevm/wagmi/commit/f43e074f473820b208a6295d7c97f847332f1a1d)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.14.0 + +## 0.0.52 + +### Patch Changes + +- Updated dependencies [[`c05caabc20c3ced9682cfc7ba1f3f7dcfece0703`](https://github.com/wevm/wagmi/commit/c05caabc20c3ced9682cfc7ba1f3f7dcfece0703), [`5ae49af590ff168426c9c283d54c34ae5148fcd9`](https://github.com/wevm/wagmi/commit/5ae49af590ff168426c9c283d54c34ae5148fcd9), [`f3182b22e6e454d9bd74f1b940ef34431fd9555d`](https://github.com/wevm/wagmi/commit/f3182b22e6e454d9bd74f1b940ef34431fd9555d)]: + - @wagmi/core@2.13.9 + - @wagmi/connectors@5.2.2 + +## 0.0.51 + +### Patch Changes + +- Updated dependencies [[`91a40f2db08e3a91db421b8732a5511a1e6c88fd`](https://github.com/wevm/wagmi/commit/91a40f2db08e3a91db421b8732a5511a1e6c88fd)]: + - @wagmi/connectors@5.2.1 + +## 0.0.50 + +### Patch Changes + +- Updated dependencies [[`34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4`](https://github.com/wevm/wagmi/commit/34a0c3b7eea778aee7c27f7ace5e4b2be4e8a0a4)]: + - @wagmi/connectors@5.2.0 + +## 0.0.49 + +### Patch Changes + +- Updated dependencies [[`3b2123664b7ac66848390739e855c3b9702ab60c`](https://github.com/wevm/wagmi/commit/3b2123664b7ac66848390739e855c3b9702ab60c)]: + - @wagmi/connectors@5.1.15 + +## 0.0.48 + +### Patch Changes + +- Updated dependencies [[`56f2482508f2ba71bd6b0295c70c6abca7101e57`](https://github.com/wevm/wagmi/commit/56f2482508f2ba71bd6b0295c70c6abca7101e57)]: + - @wagmi/connectors@5.1.14 + - @wagmi/core@2.13.8 + +## 0.0.47 + +### Patch Changes + +- Updated dependencies [[`be75c2d4ef636d7362420ab0a106bfdf63f5d1e6`](https://github.com/wevm/wagmi/commit/be75c2d4ef636d7362420ab0a106bfdf63f5d1e6)]: + - @wagmi/core@2.13.7 + - @wagmi/connectors@5.1.13 + +## 0.0.46 + +### Patch Changes + +- Updated dependencies [[`edcbf5d6fbe92f639bead800502edda9e0aa39f1`](https://github.com/wevm/wagmi/commit/edcbf5d6fbe92f639bead800502edda9e0aa39f1)]: + - @wagmi/core@2.13.6 + - @wagmi/connectors@5.1.12 + +## 0.0.45 + +### Patch Changes + +- Updated dependencies [[`82404c960e04c83e0bae6e1e12459ef9debf9554`](https://github.com/wevm/wagmi/commit/82404c960e04c83e0bae6e1e12459ef9debf9554), [`d07ad7f63a018256908a673d078aaf79e47ac703`](https://github.com/wevm/wagmi/commit/d07ad7f63a018256908a673d078aaf79e47ac703)]: + - @wagmi/connectors@5.1.11 + +## 0.0.44 + +### Patch Changes + +- [#4262](https://github.com/wevm/wagmi/pull/4262) [`8531f83db3a1fbb8202c3e426b7f85679f587a52`](https://github.com/wevm/wagmi/commit/8531f83db3a1fbb8202c3e426b7f85679f587a52) Thanks [@nezouse](https://github.com/nezouse)! - Added experimental actions entrypoint. + +## 0.0.43 + +### Patch Changes + +- [#4260](https://github.com/wevm/wagmi/pull/4260) [`969a208a110b760a13fd7263360320f52440a9b6`](https://github.com/wevm/wagmi/commit/969a208a110b760a13fd7263360320f52440a9b6) Thanks [@tmm](https://github.com/tmm)! - Fixed `useReadContract` deployless reads support. + +- [#4259](https://github.com/wevm/wagmi/pull/4259) [`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb) Thanks [@tmm](https://github.com/tmm)! - Disabled `useConnectorClient` during reconnection if connector is not fully restored. + +- Updated dependencies [[`81de006e66121a18c61945c1f9b8426c83a5713c`](https://github.com/wevm/wagmi/commit/81de006e66121a18c61945c1f9b8426c83a5713c), [`f47ce8f6d263e49fdff90b8edb3190142d2657bb`](https://github.com/wevm/wagmi/commit/f47ce8f6d263e49fdff90b8edb3190142d2657bb)]: + - @wagmi/connectors@5.1.10 + - @wagmi/core@2.13.5 + +## 0.0.42 + +### Patch Changes + +- [#4252](https://github.com/wevm/wagmi/pull/4252) [`67defb516bbd9b2c7b03e376ecd3aca8a001d065`](https://github.com/wevm/wagmi/commit/67defb516bbd9b2c7b03e376ecd3aca8a001d065) Thanks [@tmm](https://github.com/tmm)! - Added `useWatchContractEvent`. + +## 0.0.41 + +### Patch Changes + +- Updated dependencies [[`21bd0e473d374cbbd7a01bececa6022d529026ba`](https://github.com/wevm/wagmi/commit/21bd0e473d374cbbd7a01bececa6022d529026ba), [`5c89c6853e616437a3be2b019db895451fecfb3c`](https://github.com/wevm/wagmi/commit/5c89c6853e616437a3be2b019db895451fecfb3c)]: + - @wagmi/connectors@5.1.9 + +## 0.0.40 + +### Patch Changes + +- Updated dependencies [[`b580ad4edff1721e0b9d138cf5ae2ec74d2374c7`](https://github.com/wevm/wagmi/commit/b580ad4edff1721e0b9d138cf5ae2ec74d2374c7)]: + - @wagmi/connectors@5.1.8 + +## 0.0.39 + +### Patch Changes + +- Updated dependencies [[`91fd81a068789c5020e891f539bcad8f54a7a52f`](https://github.com/wevm/wagmi/commit/91fd81a068789c5020e891f539bcad8f54a7a52f)]: + - @wagmi/connectors@5.1.7 + +## 0.0.38 + +### Patch Changes + +- Updated dependencies [[`3168616298cbb6135d0ffda771cba4126e83eba8`](https://github.com/wevm/wagmi/commit/3168616298cbb6135d0ffda771cba4126e83eba8), [`d7608ef9a79459465dc8c06a2ab740465c881907`](https://github.com/wevm/wagmi/commit/d7608ef9a79459465dc8c06a2ab740465c881907)]: + - @wagmi/connectors@5.1.6 + +## 0.0.37 + +### Patch Changes + +- Updated dependencies [[`b4c8971788c70b09479946ecfa998cff2f1b3953`](https://github.com/wevm/wagmi/commit/b4c8971788c70b09479946ecfa998cff2f1b3953)]: + - @wagmi/core@2.13.4 + - @wagmi/connectors@5.1.5 + +## 0.0.36 + +### Patch Changes + +- Updated dependencies [[`871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4`](https://github.com/wevm/wagmi/commit/871dbdbfe59ac8ad01d1ec6150ea7b091b7b7de4)]: + - @wagmi/core@2.13.3 + - @wagmi/connectors@5.1.4 + +## 0.0.35 + +### Patch Changes + +- Updated dependencies [[`1b9b523fa9b9dfe839aecdf4b40caa9547d7e594`](https://github.com/wevm/wagmi/commit/1b9b523fa9b9dfe839aecdf4b40caa9547d7e594)]: + - @wagmi/core@2.13.2 + - @wagmi/connectors@5.1.3 + +## 0.0.34 + +### Patch Changes + +- Updated dependencies [[`abb490dac4f0f02f46cb0878e7ca9a0db6aada56`](https://github.com/wevm/wagmi/commit/abb490dac4f0f02f46cb0878e7ca9a0db6aada56), [`28e0e5c9a4f856583f9d36a807502bd51a0c6ec2`](https://github.com/wevm/wagmi/commit/28e0e5c9a4f856583f9d36a807502bd51a0c6ec2)]: + - @wagmi/connectors@5.1.2 + +## 0.0.33 + +### Patch Changes + +- Updated dependencies [[`07c1227f306d0efb9421d4bb77a774f92f5fcf45`](https://github.com/wevm/wagmi/commit/07c1227f306d0efb9421d4bb77a774f92f5fcf45)]: + - @wagmi/core@2.13.1 + - @wagmi/connectors@5.1.1 + +## 0.0.32 + +### Patch Changes + +- [#4162](https://github.com/wevm/wagmi/pull/4162) [`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0) Thanks [@jxom](https://github.com/jxom)! - Added functionality for consumer-defined RPC URLs (`config.transports`) to be propagated to the WalletConnect & MetaMask Connectors. + +- Updated dependencies [[`a73a7737b756886b388f120ae423e72cca53e8a0`](https://github.com/wevm/wagmi/commit/a73a7737b756886b388f120ae423e72cca53e8a0)]: + - @wagmi/connectors@6.0.0 + - @wagmi/core@2.13.0 + +## 0.0.31 + +### Patch Changes + +- [#4124](https://github.com/wevm/wagmi/pull/4124) [`26616462db2e0140025f22c505c4541cfecb9308`](https://github.com/wevm/wagmi/commit/26616462db2e0140025f22c505c4541cfecb9308) Thanks [@t0rbik](https://github.com/t0rbik)! - Updated `useConnectorClient` to be enabled when status is `'reconnecting'` or `'connected'` (previously was also enabled when status was `'connecting'`). + +## 0.0.30 + +### Patch Changes + +- Updated dependencies [[`5bc8c8877810b2eec24a829df87dce40a51e6f20`](https://github.com/wevm/wagmi/commit/5bc8c8877810b2eec24a829df87dce40a51e6f20), [`8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5`](https://github.com/wevm/wagmi/commit/8d81df5cc884d0a210dedd3c1ea0e2e9e52b83c5)]: + - @wagmi/core@2.12.2 + - @wagmi/connectors@5.0.26 + +## 0.0.29 + +### Patch Changes + +- [#4146](https://github.com/wevm/wagmi/pull/4146) [`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b) Thanks [@jxom](https://github.com/jxom)! - Updated `@safe-global/safe-apps-sdk` + `@safe-global/safe-apps-provider` dependencies. + +- Updated dependencies [[`cc996e08e930c9e88cf753a1e874652059e81a3b`](https://github.com/wevm/wagmi/commit/cc996e08e930c9e88cf753a1e874652059e81a3b)]: + - @wagmi/connectors@5.0.25 + - @wagmi/core@2.12.1 + +## 0.0.28 + +### Patch Changes + +- [`b6cb1477e3e87984917b172a909f1968e0d77dc9`](https://github.com/wevm/wagmi/commit/b6cb1477e3e87984917b172a909f1968e0d77dc9) Thanks [@tmm](https://github.com/tmm)! - Added `useBytecode` composable. + +- Updated dependencies [[`5581a810ef70308e99c6f8b630cd4bca59f64afc`](https://github.com/wevm/wagmi/commit/5581a810ef70308e99c6f8b630cd4bca59f64afc)]: + - @wagmi/core@2.12.0 + - @wagmi/connectors@6.0.0 + +## 0.0.27 + +### Patch Changes + +- [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d) Thanks [@jxom](https://github.com/jxom)! - Updated `mipd` dependency. + +- Updated dependencies [[`b08013eaa9ce97c02f8a7128ea400e3da7ef74bb`](https://github.com/wevm/wagmi/commit/b08013eaa9ce97c02f8a7128ea400e3da7ef74bb), [`d3814ab4b88f9f0e052b53bc3d458df87b43f01d`](https://github.com/wevm/wagmi/commit/d3814ab4b88f9f0e052b53bc3d458df87b43f01d)]: + - @wagmi/core@2.11.8 + - @wagmi/connectors@5.0.23 + +## 0.0.26 + +### Patch Changes + +- Updated dependencies [[`0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e`](https://github.com/wevm/wagmi/commit/0bb8b562ae04ecfeb2d6b2f1b980ebae31dc127e)]: + - @wagmi/connectors@5.0.22 + - @wagmi/core@2.11.7 + +## 0.0.25 + +### Patch Changes + +- [#4060](https://github.com/wevm/wagmi/pull/4060) [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad) Thanks [@dalechyn](https://github.com/dalechyn)! - Bumped Tanstack Query dependencies to fix typing issues between exported Wagmi query options and TanStack Query suspense query methods (due to [`direction` property in `QueryFunctionContext` being deprecated](https://github.com/TanStack/query/pull/7410)). + +- Updated dependencies [[`ff0760b5900114bcfdf420a9fba3cc278ac95afe`](https://github.com/wevm/wagmi/commit/ff0760b5900114bcfdf420a9fba3cc278ac95afe), [`95965c1f19d480b97f2b297a077a9e607dee32ad`](https://github.com/wevm/wagmi/commit/95965c1f19d480b97f2b297a077a9e607dee32ad)]: + - @wagmi/connectors@5.0.21 + - @wagmi/core@2.11.6 + +## 0.0.24 + +### Patch Changes + +- Updated dependencies [[`43fa971d34cac57fa5a2898ad4d839b95d7af37c`](https://github.com/wevm/wagmi/commit/43fa971d34cac57fa5a2898ad4d839b95d7af37c)]: + - @wagmi/connectors@5.0.20 + +## 0.0.23 + +### Patch Changes + +- Updated dependencies [[`b7ad208030d9f2e3f89912ff76b16cdbd848feda`](https://github.com/wevm/wagmi/commit/b7ad208030d9f2e3f89912ff76b16cdbd848feda)]: + - @wagmi/connectors@5.0.19 + +## 0.0.22 + +### Patch Changes + +- Updated dependencies [[`44d24620c9e3957f3245d14d6a042736371df70b`](https://github.com/wevm/wagmi/commit/44d24620c9e3957f3245d14d6a042736371df70b)]: + - @wagmi/connectors@5.0.18 + +## 0.0.21 + +### Patch Changes + +- Updated dependencies [[`04f2b846b113f3d300d82c9fa75212f1805817c5`](https://github.com/wevm/wagmi/commit/04f2b846b113f3d300d82c9fa75212f1805817c5)]: + - @wagmi/core@2.11.5 + - @wagmi/connectors@5.0.17 + +## 0.0.20 + +### Patch Changes + +- Updated dependencies [[`9e8345cd56186b997b5e56deaa2cfc69b30d15f6`](https://github.com/wevm/wagmi/commit/9e8345cd56186b997b5e56deaa2cfc69b30d15f6), [`02c38c28d1aa0ad7a61c33775de603ed974c5c1b`](https://github.com/wevm/wagmi/commit/02c38c28d1aa0ad7a61c33775de603ed974c5c1b)]: + - @wagmi/core@2.11.4 + - @wagmi/connectors@5.0.16 + +## 0.0.19 + +### Patch Changes + +- Updated dependencies [[`8974e6269bb5d7bfaa90db0246bc7d13e8bff798`](https://github.com/wevm/wagmi/commit/8974e6269bb5d7bfaa90db0246bc7d13e8bff798)]: + - @wagmi/core@2.11.3 + - @wagmi/connectors@5.0.15 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies [[`b4d9ef79deb554ee20fed6666a474be5e7cdd522`](https://github.com/wevm/wagmi/commit/b4d9ef79deb554ee20fed6666a474be5e7cdd522)]: + - @wagmi/core@2.11.2 + - @wagmi/connectors@5.0.14 + +## 0.0.17 + +### Patch Changes + +- Updated dependencies [[`9c862d8d63e3d692a22cef2a90782b74a9103f17`](https://github.com/wevm/wagmi/commit/9c862d8d63e3d692a22cef2a90782b74a9103f17)]: + - @wagmi/connectors@5.0.13 + - @wagmi/core@2.11.1 + +## 0.0.16 + +### Patch Changes + +- Updated dependencies [[`06bb598a7f04c7b167f5b7ff6d46bd15886a6a14`](https://github.com/wevm/wagmi/commit/06bb598a7f04c7b167f5b7ff6d46bd15886a6a14), [`24a45b269bd0214a29d6f82a84ac66ef8c3f3822`](https://github.com/wevm/wagmi/commit/24a45b269bd0214a29d6f82a84ac66ef8c3f3822)]: + - @wagmi/core@2.11.0 + - @wagmi/connectors@6.0.0 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies [[`f2a7cefab96691ebed8b8e45ffde071c47b58dbe`](https://github.com/wevm/wagmi/commit/f2a7cefab96691ebed8b8e45ffde071c47b58dbe), [`f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5`](https://github.com/wevm/wagmi/commit/f0ea0b2a7fe193dadfeb49a4c8031ee451c638b5), [`e3b124ce414b8fd1b2214e2c5a28dc72158a13d1`](https://github.com/wevm/wagmi/commit/e3b124ce414b8fd1b2214e2c5a28dc72158a13d1)]: + - @wagmi/core@2.10.6 + - @wagmi/connectors@5.0.11 + +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`560952acd4bfe33db6c7c07b35c613cef278677c`](https://github.com/wevm/wagmi/commit/560952acd4bfe33db6c7c07b35c613cef278677c)]: + - @wagmi/connectors@5.0.10 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`32cdd7b7dc5aff916c040628519562c3a99d418d`](https://github.com/wevm/wagmi/commit/32cdd7b7dc5aff916c040628519562c3a99d418d)]: + - @wagmi/connectors@5.0.9 + +## 0.0.12 + +### Patch Changes + +- Updated dependencies [[`c1952d1ff7f0a491dc88595a49159451b07b5621`](https://github.com/wevm/wagmi/commit/c1952d1ff7f0a491dc88595a49159451b07b5621)]: + - @wagmi/connectors@5.0.8 + +## 0.0.11 + +### Patch Changes + +- Updated dependencies [[`030c7c2cb380dfd67a2182f62e2aa7a6e1601898`](https://github.com/wevm/wagmi/commit/030c7c2cb380dfd67a2182f62e2aa7a6e1601898)]: + - @wagmi/core@2.10.5 + - @wagmi/connectors@5.0.7 + +## 0.0.10 + +### Patch Changes + +- Updated dependencies [[`51fde8a0433b4fff357c1a8d7e08b41b4c86c968`](https://github.com/wevm/wagmi/commit/51fde8a0433b4fff357c1a8d7e08b41b4c86c968)]: + - @wagmi/core@2.10.4 + - @wagmi/connectors@5.0.6 + +## 0.0.9 + +### Patch Changes + +- Updated dependencies [[`70dd28669dd8d2ce08217cd02e29a8fbba7a08d4`](https://github.com/wevm/wagmi/commit/70dd28669dd8d2ce08217cd02e29a8fbba7a08d4)]: + - @wagmi/connectors@5.0.5 + +## 0.0.8 + +### Patch Changes + +- Updated dependencies [[`be9e1b8a9818b92eb0654a20d9471e9e39329e7e`](https://github.com/wevm/wagmi/commit/be9e1b8a9818b92eb0654a20d9471e9e39329e7e)]: + - @wagmi/connectors@5.0.4 + +## 0.0.7 + +### Patch Changes + +- Updated dependencies [[`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c), [`2804a8a583b1874271154898b4bae38756ef581c`](https://github.com/wevm/wagmi/commit/2804a8a583b1874271154898b4bae38756ef581c)]: + - @wagmi/connectors@5.0.3 + - @wagmi/core@2.10.3 + +## 0.0.6 + +### Patch Changes + +- [`ec2f63f106fd468f28b43d3b88ab3e89aaf5e81a`](https://github.com/wevm/wagmi/commit/ec2f63f106fd468f28b43d3b88ab3e89aaf5e81a) Thanks [@tmm](https://github.com/tmm)! - Fixed `useSwitchChain` `chains` typing. + +## 0.0.5 + +### Patch Changes + +- [#3940](https://github.com/wevm/wagmi/pull/3940) [`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a) Thanks [@jxom](https://github.com/jxom)! - Fixed usage of `metaMask` connector in Vite environments. + +- Updated dependencies [[`a5071f581dfdfb961718873643a2fc629101c72a`](https://github.com/wevm/wagmi/commit/a5071f581dfdfb961718873643a2fc629101c72a)]: + - @wagmi/connectors@5.0.2 + - @wagmi/core@2.10.2 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies []: + - @wagmi/connectors@5.0.1 + - @wagmi/core@2.10.1 + +## 0.0.3 + +### Patch Changes + +- Updated dependencies [[`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1), [`3117e71825f9c58a0d718f3d1686f1a191fa9cb1`](https://github.com/wevm/wagmi/commit/3117e71825f9c58a0d718f3d1686f1a191fa9cb1)]: + - @wagmi/connectors@5.0.0 + - @wagmi/core@2.10.0 + +## 0.0.2 + +### Patch Changes + +- [#3906](https://github.com/wevm/wagmi/pull/3906) [`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67) Thanks [@tmm](https://github.com/tmm)! - Added support for Vue. + +- Updated dependencies [[`32fcb4a31dde6b0206961d8ffe9c651f8a459c67`](https://github.com/wevm/wagmi/commit/32fcb4a31dde6b0206961d8ffe9c651f8a459c67)]: + - @wagmi/connectors@4.3.10 + - @wagmi/core@2.9.8 diff --git a/packages/vue/README.md b/packages/vue/README.md new file mode 100644 index 0000000000..10a4b805d5 --- /dev/null +++ b/packages/vue/README.md @@ -0,0 +1,14 @@ +# @wagmi/vue + +Vue Composables for Ethereum + +## Installation + +```bash +pnpm add @wagmi/vue viem @tanstack/vue-query +``` + +## Documentation + +For documentation and guides, visit [wagmi.sh](https://wagmi.sh). + diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 0000000000..8f0a821301 --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,113 @@ +{ + "name": "@wagmi/vue", + "description": "Vue Composables for Ethereum", + "version": "0.1.20", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wevm/wagmi.git", + "directory": "packages/vue" + }, + "scripts": { + "build": "pnpm run clean && pnpm run build:esm+types", + "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", + "check:types": "tsc --noEmit", + "clean": "rm -rf dist tsconfig.tsbuildinfo actions chains connectors nuxt query", + "test:build": "publint --strict && attw --pack --ignore-rules cjs-resolves-to-esm" + }, + "files": [ + "dist/**", + "!dist/**/*.tsbuildinfo", + "src/**/*.ts", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "/actions", + "/chains", + "/connectors", + "/nuxt", + "/query" + ], + "sideEffects": false, + "type": "module", + "main": "./dist/esm/exports/index.js", + "types": "./dist/types/exports/index.d.ts", + "typings": "./dist/types/exports/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/exports/index.d.ts", + "default": "./dist/esm/exports/index.js" + }, + "./actions": { + "types": "./dist/types/exports/actions.d.ts", + "default": "./dist/esm/exports/actions.js" + }, + "./actions/experimental": { + "types": "./dist/types/exports/actions/experimental.d.ts", + "default": "./dist/esm/exports/actions/experimental.js" + }, + "./chains": { + "types": "./dist/types/exports/chains.d.ts", + "default": "./dist/esm/exports/chains.js" + }, + "./connectors": { + "types": "./dist/types/exports/connectors.d.ts", + "default": "./dist/esm/exports/connectors.js" + }, + "./nuxt": { + "types": "./dist/types/exports/nuxt.d.ts", + "default": "./dist/esm/exports/nuxt.js" + }, + "./query": { + "types": "./dist/types/exports/query.d.ts", + "default": "./dist/esm/exports/query.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "actions": ["./dist/types/exports/actions.d.ts"], + "chains": ["./dist/types/exports/chains.d.ts"], + "connectors": ["./dist/types/exports/connectors.d.ts"], + "nuxt": ["./dist/types/exports/nuxt.d.ts"], + "query": ["./dist/types/exports/query.d.ts"] + } + }, + "peerDependencies": { + "@tanstack/vue-query": ">=5.0.0", + "nuxt": ">=3.0.0", + "typescript": ">=5.0.4", + "viem": "2.x", + "vue": ">=3" + }, + "peerDependenciesMeta": { + "nuxt": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "dependencies": { + "@wagmi/connectors": "workspace:*", + "@wagmi/core": "workspace:*" + }, + "devDependencies": { + "@nuxt/schema": "^3.11.2", + "@tanstack/vue-query": "catalog:", + "@vue/test-utils": "^2.4.6", + "nuxt": "^3.19.0", + "vue": "catalog:" + }, + "contributors": ["awkweb.eth ", "jxom.eth "], + "funding": "https://github.com/sponsors/wevm", + "keywords": [ + "wagmi", + "vue", + "composables", + "eth", + "ethereum", + "dapps", + "wallet", + "web3" + ] +} diff --git a/packages/vue/src/composables/useAccount.test-d.ts b/packages/vue/src/composables/useAccount.test-d.ts new file mode 100644 index 0000000000..cc0ef07dcc --- /dev/null +++ b/packages/vue/src/composables/useAccount.test-d.ts @@ -0,0 +1,69 @@ +import type { Connector } from '@wagmi/core' +import type { Address, Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useAccount } from './useAccount.js' + +test('states', () => { + const result = deepUnref(useAccount()) + + switch (result.status) { + case 'reconnecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: Chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: boolean + isConnecting: false + isDisconnected: false + isReconnecting: true + status: 'reconnecting' + }>() + break + } + case 'connecting': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address | undefined + chain: Chain | undefined + chainId: number | undefined + connector: Connector | undefined + isConnected: false + isReconnecting: false + isConnecting: true + isDisconnected: false + status: 'connecting' + }>() + break + } + case 'connected': { + expectTypeOf(result).toMatchTypeOf<{ + address: Address + chain: Chain | undefined + chainId: number + connector: Connector + isConnected: true + isConnecting: false + isDisconnected: false + isReconnecting: false + status: 'connected' + }>() + break + } + case 'disconnected': { + expectTypeOf(result).toMatchTypeOf<{ + address: undefined + chain: undefined + chainId: undefined + connector: undefined + isConnected: false + isReconnecting: false + isConnecting: false + isDisconnected: true + status: 'disconnected' + }>() + break + } + } +}) diff --git a/packages/vue/src/composables/useAccount.test.ts b/packages/vue/src/composables/useAccount.test.ts new file mode 100644 index 0000000000..773532857d --- /dev/null +++ b/packages/vue/src/composables/useAccount.test.ts @@ -0,0 +1,20 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' + +test('default', async () => { + const [account] = renderComposable(() => useAccount()) + + expect(account.address.value).not.toBeDefined() + expect(account.status.value).toEqual('disconnected') + + await connect(config, { connector: config.connectors[0]! }) + + expect(account.address.value).toBeDefined() + expect(account.status.value).toEqual('connected') + + await disconnect(config) +}) diff --git a/packages/vue/src/composables/useAccount.ts b/packages/vue/src/composables/useAccount.ts new file mode 100644 index 0000000000..136595fb20 --- /dev/null +++ b/packages/vue/src/composables/useAccount.ts @@ -0,0 +1,37 @@ +import { + type Config, + type GetAccountReturnType, + type ResolvedRegister, + getAccount, + watchAccount, +} from '@wagmi/core' +import { type ToRefs, onScopeDispose, reactive, readonly, toRefs } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import { updateState } from '../utils/updateState.js' +import { useConfig } from './useConfig.js' + +export type UseAccountParameters = + ConfigParameter + +export type UseAccountReturnType = ToRefs< + GetAccountReturnType +> + +/** https://wagmi.sh/vue/api/composables/useAccount */ +export function useAccount( + parameters: UseAccountParameters = {}, +): UseAccountReturnType { + const config = useConfig(parameters) + + const account = reactive(getAccount(config)) + + const unsubscribe = watchAccount(config, { + onChange(data) { + updateState(account, data) + }, + }) + onScopeDispose(() => unsubscribe()) + + return toRefs(readonly(account)) as UseAccountReturnType +} diff --git a/packages/vue/src/composables/useAccountEffect.test.ts b/packages/vue/src/composables/useAccountEffect.test.ts new file mode 100644 index 0000000000..10ed7a3e37 --- /dev/null +++ b/packages/vue/src/composables/useAccountEffect.test.ts @@ -0,0 +1,75 @@ +import { VueQueryPlugin } from '@tanstack/vue-query' +import { mock } from '@wagmi/connectors' +import { http, connect, createConfig, disconnect } from '@wagmi/core' +import { accounts, chain } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test, vi } from 'vitest' +import type { App } from 'vue' + +import { WagmiPlugin } from '../plugin.js' +import { useAccount } from './useAccount.js' +import { useAccountEffect } from './useAccountEffect.js' +import { useConnect } from './useConnect.js' +import { useDisconnect } from './useDisconnect.js' + +test('behavior: connect and disconnect called once', async () => { + const onConnect = vi.fn() + const onDisconnect = vi.fn() + + renderComposable(() => useAccountEffect({ onConnect, onDisconnect })) + const [connect] = renderComposable(() => useConnect()) + const [disconnect] = renderComposable(() => useDisconnect()) + + connect.connect({ + connector: connect.connectors[0]!, + }) + await waitFor(connect.isSuccess) + connect.connect({ + connector: connect.connectors[0]!, + }) + + disconnect.disconnect() + await waitFor(disconnect.isSuccess) + disconnect.disconnect() + + expect(onConnect).toBeCalledTimes(1) + expect(onDisconnect).toBeCalledTimes(1) +}) + +test('behavior: connect called on reconnect', async () => { + const config = createConfig({ + chains: [chain.mainnet], + connectors: [ + mock({ + accounts, + features: { reconnect: true }, + }), + ], + transports: { [chain.mainnet.id]: http() }, + }) + + function attach(app: App) { + app + .use(WagmiPlugin, { + config, + reconnectOnMount: true, + }) + .use(VueQueryPlugin, {}) + } + + await connect(config, { connector: config.connectors[0]! }) + const onConnect = vi.fn((data) => { + expect(data.isReconnected).toBeTruthy() + }) + + renderComposable(() => useAccountEffect({ onConnect }), { + attach, + }) + const [account] = renderComposable(() => useAccount(), { attach }) + + await waitFor(account.status, (status) => status === 'connected') + + expect(onConnect).toBeCalledTimes(1) + + await disconnect(config) +}) diff --git a/packages/vue/src/composables/useAccountEffect.ts b/packages/vue/src/composables/useAccountEffect.ts new file mode 100644 index 0000000000..021624cea8 --- /dev/null +++ b/packages/vue/src/composables/useAccountEffect.ts @@ -0,0 +1,66 @@ +import { type GetAccountReturnType, watchAccount } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { watchEffect } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { useConfig } from './useConfig.js' + +export type UseAccountEffectParameters = Compute< + DeepMaybeRef< + { + onConnect?( + data: Compute< + Pick< + Extract, + 'address' | 'addresses' | 'chain' | 'chainId' | 'connector' + > & { + isReconnected: boolean + } + >, + ): void + onDisconnect?(): void + } & ConfigParameter + > +> + +/** https://wagmi.sh/vue/api/composables/useAccountEffect */ +export function useAccountEffect(parameters: UseAccountEffectParameters = {}) { + const config = useConfig(parameters) + + watchEffect((onCleanup) => { + const { onConnect, onDisconnect } = deepUnref(parameters) + + const unwatch = watchAccount(config, { + onChange(data, prevData) { + if ( + (prevData.status === 'reconnecting' || + (prevData.status === 'connecting' && + prevData.address === undefined)) && + data.status === 'connected' + ) { + const { address, addresses, chain, chainId, connector } = data + const isReconnected = + prevData.status === 'reconnecting' || + // if `previousAccount.status` is `undefined`, the connector connected immediately. + prevData.status === undefined + onConnect?.({ + address, + addresses, + chain, + chainId, + connector, + isReconnected, + }) + } else if ( + prevData.status === 'connected' && + data.status === 'disconnected' + ) + onDisconnect?.() + }, + }) + + onCleanup(() => unwatch()) + }) +} diff --git a/packages/vue/src/composables/useBalance.test-d.ts b/packages/vue/src/composables/useBalance.test-d.ts new file mode 100644 index 0000000000..44ba34ea48 --- /dev/null +++ b/packages/vue/src/composables/useBalance.test-d.ts @@ -0,0 +1,15 @@ +import { expectTypeOf, test } from 'vitest' +import type { Ref } from 'vue' + +import { useBalance } from './useBalance.js' + +test('select data', () => { + const result = useBalance({ + query: { + select(data) { + return data?.value + }, + }, + }) + expectTypeOf(result.data).toEqualTypeOf | Ref>() +}) diff --git a/packages/vue/src/composables/useBalance.test.ts b/packages/vue/src/composables/useBalance.test.ts new file mode 100644 index 0000000000..cf2662d302 --- /dev/null +++ b/packages/vue/src/composables/useBalance.test.ts @@ -0,0 +1,119 @@ +import { accounts, chain, testClient, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { parseEther } from 'viem' +import { beforeEach, expect, test } from 'vitest' + +import { ref } from 'vue' +import { useBalance } from './useBalance.js' + +const address = accounts[0] + +beforeEach(async () => { + await testClient.mainnet.setBalance({ address, value: parseEther('10000') }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet2.setBalance({ address, value: parseEther('69') }) + await testClient.mainnet2.mine({ blocks: 1 }) +}) + +test('default', async () => { + const [query] = renderComposable(() => useBalance({ address })) + + await waitFor(query.isSuccess) + + expect(query.data.value).toMatchObject( + expect.objectContaining({ + decimals: expect.any(Number), + formatted: expect.any(String), + symbol: expect.any(String), + value: expect.any(BigInt), + }), + ) +}) + +test('parameters: chainId', async () => { + const [query] = renderComposable(() => + useBalance({ address, chainId: chain.mainnet2.id }), + ) + + await waitFor(query.isSuccess) + + expect(query.data.value).toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "69", + "symbol": "WAG", + "value": 69000000000000000000n, + } + `) +}) + +test('parameters: token', async () => { + const [query] = renderComposable(() => + useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + }), + ) + + await waitFor(query.isSuccess) + + expect(query.data.value).toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "0.559062564299199392", + "symbol": "DAI", + "value": 559062564299199392n, + } + `) +}) + +test('parameters: unit', async () => { + const [query] = renderComposable(() => + useBalance({ + address, + chainId: chain.mainnet2.id, + unit: 'wei', + }), + ) + + await waitFor(query.isSuccess) + + expect(query.data.value).toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "69000000000000000000", + "symbol": "WAG", + "value": 69000000000000000000n, + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + const address = ref() + + const [query] = renderComposable(() => useBalance({ address })) + + await wait(100) + expect(query.fetchStatus.value).toMatchInlineSnapshot(`"idle"`) + + address.value = accounts[0] + + await waitFor(query.isSuccess) + + expect(query.data.value).toMatchInlineSnapshot(` + { + "decimals": 18, + "formatted": "10000", + "symbol": "ETH", + "value": 10000000000000000000000n, + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const [query] = renderComposable(() => useBalance({ address })) + + await wait(100) + + expect(query.fetchStatus.value).toMatchInlineSnapshot(`"idle"`) +}) diff --git a/packages/vue/src/composables/useBalance.ts b/packages/vue/src/composables/useBalance.ts new file mode 100644 index 0000000000..1fdcdc9450 --- /dev/null +++ b/packages/vue/src/composables/useBalance.ts @@ -0,0 +1,65 @@ +import type { Config, GetBalanceErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetBalanceData, + type GetBalanceOptions, + type GetBalanceQueryKey, + getBalanceQueryOptions, +} from '@wagmi/core/query' +import type { GetBalanceQueryFnData } from '@wagmi/core/query' + +import { computed } from 'vue' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseBalanceParameters< + config extends Config = Config, + selectData = GetBalanceData, +> = Compute< + DeepMaybeRef< + GetBalanceOptions & + ConfigParameter & + QueryParameter< + GetBalanceQueryFnData, + GetBalanceErrorType, + selectData, + GetBalanceQueryKey + > + > +> + +export type UseBalanceReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useBalance */ +export function useBalance< + config extends Config = ResolvedRegister['config'], + selectData = GetBalanceData, +>( + parameters_: UseBalanceParameters = {}, +): UseBalanceReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + address, + chainId = configChainId.value, + query = {}, + } = parameters.value + const options = getBalanceQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions as any) as UseBalanceReturnType +} diff --git a/packages/vue/src/composables/useBlockNumber.test-d.ts b/packages/vue/src/composables/useBlockNumber.test-d.ts new file mode 100644 index 0000000000..255a1e02e8 --- /dev/null +++ b/packages/vue/src/composables/useBlockNumber.test-d.ts @@ -0,0 +1,65 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { mainnet, optimism } from '@wagmi/core/chains' +import { expectTypeOf, test } from 'vitest' + +import { useBlockNumber } from './useBlockNumber.js' + +test('select data', () => { + const result = useBlockNumber({ + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + useBlockNumber({ + config, + watch: { + poll: false, + }, + }) + + useBlockNumber({ + config, + chainId: mainnet.id, + watch: { + poll: true, + }, + }) + useBlockNumber({ + config, + chainId: mainnet.id, + watch: { + // @ts-expect-error + poll: false, + }, + }) + + useBlockNumber({ + config, + chainId: optimism.id, + watch: { + poll: true, + }, + }) + useBlockNumber({ + config, + chainId: optimism.id, + watch: { + poll: false, + }, + }) +}) diff --git a/packages/vue/src/composables/useBlockNumber.test.ts b/packages/vue/src/composables/useBlockNumber.test.ts new file mode 100644 index 0000000000..68e7e6241c --- /dev/null +++ b/packages/vue/src/composables/useBlockNumber.test.ts @@ -0,0 +1,65 @@ +import { config, testClient } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { ref } from 'vue' +import { useBlockNumber } from './useBlockNumber.js' + +test('default', async () => { + await testClient.mainnet.resetFork() + + const [blockNumberQuery] = renderComposable(() => useBlockNumber()) + + await waitFor(blockNumberQuery.status, (status) => status === 'success') + + expect(blockNumberQuery.data.value).toMatchInlineSnapshot('19258213n') +}) + +test('parameters: chainId', async () => { + config.setState((state) => ({ ...state, chainId: config.chains[0].id })) + + const [blockNumberQuery] = renderComposable(() => useBlockNumber()) + + await waitFor(blockNumberQuery.status, (status) => status === 'success') + expect(blockNumberQuery.data.value).toBeTypeOf('bigint') + + config.setState((state) => ({ ...state, chainId: config.chains[2].id })) + + await waitFor(blockNumberQuery.status, (status) => status === 'success') + expect(blockNumberQuery.data.value).toBeTypeOf('bigint') +}) + +test('parameters: watch', async () => { + const [blockNumberQuery] = renderComposable(() => + useBlockNumber({ watch: true }), + ) + + await waitFor(blockNumberQuery.status, (status) => status === 'success') + + const blockNumber = blockNumberQuery.data.value! + await testClient.mainnet.mine({ blocks: 1 }) + + await waitFor(blockNumberQuery.data, (data) => data === blockNumber + 1n) +}) + +test('parameters: watch (reactive)', async () => { + const watch = ref(true) + + const [blockNumberQuery] = renderComposable(() => useBlockNumber({ watch })) + + await waitFor(blockNumberQuery.status, (status) => status === 'success') + + const blockNumber = blockNumberQuery.data.value! + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(blockNumberQuery.data, (data) => data === blockNumber + 1n) + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(blockNumberQuery.data, (data) => data === blockNumber + 2n) + + watch.value = false + + await testClient.mainnet.mine({ blocks: 1 }) + await waitFor(blockNumberQuery.data, (data) => data === blockNumber + 2n, { + timeout: 1_000, + }) +}) diff --git a/packages/vue/src/composables/useBlockNumber.ts b/packages/vue/src/composables/useBlockNumber.ts new file mode 100644 index 0000000000..aadf04fb25 --- /dev/null +++ b/packages/vue/src/composables/useBlockNumber.ts @@ -0,0 +1,120 @@ +import { useQueryClient } from '@tanstack/vue-query' +import type { + Config, + GetBlockNumberErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { + Compute, + UnionCompute, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { + type GetBlockNumberData, + type GetBlockNumberOptions, + type GetBlockNumberQueryFnData, + type GetBlockNumberQueryKey, + getBlockNumberQueryOptions, +} from '@wagmi/core/query' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef, DeepUnwrapRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { + type UseWatchBlockNumberParameters, + useWatchBlockNumber, +} from './useWatchBlockNumber.js' + +export type UseBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +> = Compute< + DeepMaybeRef< + GetBlockNumberOptions & + ConfigParameter & + QueryParameter< + GetBlockNumberQueryFnData, + GetBlockNumberErrorType, + selectData, + GetBlockNumberQueryKey + > & { + watch?: + | boolean + | UnionCompute< + UnionStrictOmit< + DeepUnwrapRef>, + 'chainId' | 'config' | 'onBlockNumber' | 'onError' + > + > + | undefined + } + > +> + +export type UseBlockNumberReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useBlockNumber */ +export function useBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetBlockNumberData, +>( + parameters_: UseBlockNumberParameters = {}, +): UseBlockNumberReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const queryClient = useQueryClient() + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + chainId = configChainId.value, + query = {}, + watch: _, + ...rest + } = parameters.value + const options = getBlockNumberQueryOptions(config, { + ...deepUnref(rest), + chainId, + }) + return { + ...query, + ...options, + } + }) + + const watchBlockNumberArgs = computed(() => { + const { + config, + chainId = configChainId.value, + query, + watch, + } = parameters.value + return { + ...({ + config, + chainId, + ...(typeof watch === 'object' ? watch : {}), + } as UseWatchBlockNumberParameters), + enabled: + (query?.enabled ?? true) && + (typeof watch === 'object' ? watch.enabled : watch), + onBlockNumber(blockNumber) { + queryClient.setQueryData(queryOptions.value.queryKey, blockNumber) + }, + } satisfies UseWatchBlockNumberParameters + }) + + useWatchBlockNumber(watchBlockNumberArgs) + + return useQuery(queryOptions as any) as UseBlockNumberReturnType +} diff --git a/packages/vue/src/composables/useBytecode.test-d.ts b/packages/vue/src/composables/useBytecode.test-d.ts new file mode 100644 index 0000000000..16fb3a36ff --- /dev/null +++ b/packages/vue/src/composables/useBytecode.test-d.ts @@ -0,0 +1,16 @@ +import type { Hex } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useBytecode } from './useBytecode.js' + +test('select data', () => { + const result = useBytecode({ + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useBytecode.test.ts b/packages/vue/src/composables/useBytecode.test.ts new file mode 100644 index 0000000000..f98eccc8db --- /dev/null +++ b/packages/vue/src/composables/useBytecode.test.ts @@ -0,0 +1,296 @@ +import { address, chain, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { ref } from 'vue' +import { deepUnref } from '../utils/cloneDeep.js' +import { useBytecode } from './useBytecode.js' + +test('default', async () => { + const [result] = renderComposable(() => + useBytecode({ + address: address.wagmiMintExample, + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": "0x608060405234801561001057600080fd5b50600436106101005760003560e01c80636352211e11610097578063a22cb46511610066578063a22cb46514610215578063b88d4fde14610228578063c87b56dd1461023b578063e985e9c51461024e57600080fd5b80636352211e146101d457806370a08231146101e757806395d89b41146101fa578063a0712d681461020257600080fd5b80631249c58b116100d35780631249c58b1461018f57806318160ddd1461019757806323b872dd146101ae57806342842e0e146101c157600080fd5b806301ffc9a71461010557806306fdde031461012d578063081812fc14610142578063095ea7b31461017a575b600080fd5b61011861011336600461178f565b610297565b60405190151581526020015b60405180910390f35b61013561037c565b6040516101249190611829565b61015561015036600461183c565b61040e565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b61018d61018836600461187e565b6104d3565b005b61018d61062b565b6101a060065481565b604051908152602001610124565b61018d6101bc3660046118a8565b61067d565b61018d6101cf3660046118a8565b610704565b6101556101e236600461183c565b61071f565b6101a06101f53660046118e4565b6107b7565b61013561086b565b61018d61021036600461183c565b61087a565b61018d6102233660046118ff565b610902565b61018d61023636600461196a565b610911565b61013561024936600461183c565b61099f565b61011861025c366004611a64565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd00000000000000000000000000000000000000000000000000000000148061032a57507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b8061037657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60606000805461038b90611a97565b80601f01602080910402602001604051908101604052809291908181526020018280546103b790611a97565b80156104045780601f106103d957610100808354040283529160200191610404565b820191906000526020600020905b8154815290600101906020018083116103e757829003601f168201915b5050505050905090565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff166104aa5760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a20617070726f76656420717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006104de8261071f565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105815760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084016104a1565b3373ffffffffffffffffffffffffffffffffffffffff821614806105aa57506105aa813361025c565b61061c5760405162461bcd60e51b815260206004820152603860248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f74206f7760448201527f6e6572206e6f7220617070726f76656420666f7220616c6c000000000000000060648201526084016104a1565b6106268383610b07565b505050565b6007545b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156106615760010161062f565b61066b3382610ba7565b60068054600190810190915501600755565b6106873382610bc1565b6106f95760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b610626838383610d17565b61062683838360405180602001604052806000815250610911565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16806103765760405162461bcd60e51b815260206004820152602960248201527f4552433732313a206f776e657220717565727920666f72206e6f6e657869737460448201527f656e7420746f6b656e000000000000000000000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff82166108425760405162461bcd60e51b815260206004820152602a60248201527f4552433732313a2062616c616e636520717565727920666f7220746865207a6560448201527f726f20616464726573730000000000000000000000000000000000000000000060648201526084016104a1565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461038b90611a97565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156108ec5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e2049442069732074616b656e00000000000000000000000000000060448201526064016104a1565b6108f63382610ba7565b50600680546001019055565b61090d338383610f4a565b5050565b61091b3383610bc1565b61098d5760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b6109998484848461105d565b50505050565b6040517f666f726567726f756e64000000000000000000000000000000000000000000006020820152602a810182905260609060009061016890604a016040516020818303038152906040528051906020012060001c6109ff9190611b19565b6040517f6261636b67726f756e64000000000000000000000000000000000000000000006020820152602a810185905290915060009061016890604a016040516020818303038152906040528051906020012060001c610a5f9190611b19565b90506000610aba610a6f866110e6565b610aa9610a7b866110e6565b610a84866110e6565b604051602001610a95929190611b2d565b60405160208183030381529060405261121b565b604051602001610a959291906125ba565b9050600081604051602001610acf919061268b565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529695505050505050565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091558190610b618261071f565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b61090d82826040518060200160405280600081525061136e565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16610c585760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a206f70657261746f7220717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084016104a1565b6000610c638361071f565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610cd1575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b80610d0f57508373ffffffffffffffffffffffffffffffffffffffff16610cf78461040e565b73ffffffffffffffffffffffffffffffffffffffff16145b949350505050565b8273ffffffffffffffffffffffffffffffffffffffff16610d378261071f565b73ffffffffffffffffffffffffffffffffffffffff1614610dc05760405162461bcd60e51b815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e657200000000000000000000000000000000000000000000000000000060648201526084016104a1565b73ffffffffffffffffffffffffffffffffffffffff8216610e485760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016104a1565b610e53600082610b07565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120805460019290610e899084906126ff565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290610ec4908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610fc55760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c65720000000000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b611068848484610d17565b611074848484846113f7565b6109995760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b60608160000361112957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115611153578061113d8161272e565b915061114c9050600a83612766565b915061112d565b60008167ffffffffffffffff81111561116e5761116e61193b565b6040519080825280601f01601f191660200182016040528015611198576020820181803683370190505b5090505b8415610d0f576111ad6001836126ff565b91506111ba600a86611b19565b6111c5906030612716565b60f81b8183815181106111da576111da61277a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350611214600a86612766565b945061119c565b6060815160000361123a57505060408051602081019091526000815290565b600060405180606001604052806040815260200161284d60409139905060006003845160026112699190612716565b6112739190612766565b61127e9060046127a9565b67ffffffffffffffff8111156112965761129661193b565b6040519080825280601f01601f1916602001820160405280156112c0576020820181803683370190505b509050600182016020820185865187015b8082101561132c576003820191508151603f8160121c168501518453600184019350603f81600c1c168501518453600184019350603f8160061c168501518453600184019350603f81168501518453506001830192506112d1565b5050600386510660018114611348576002811461135b57611363565b603d6001830353603d6002830353611363565b603d60018303535b509195945050505050565b61137883836115d0565b61138560008484846113f7565b6106265760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff84163b156115c5576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a029061146e9033908990889088906004016127e6565b6020604051808303816000875af19250505080156114c7575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526114c49181019061282f565b60015b61157a573d8080156114f5576040519150601f19603f3d011682016040523d82523d6000602084013e6114fa565b606091505b5080516000036115725760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050610d0f565b506001949350505050565b73ffffffffffffffffffffffffffffffffffffffff82166116335760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016104a1565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156116a55760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054600192906116db908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461178c57600080fd5b50565b6000602082840312156117a157600080fd5b81356117ac8161175e565b9392505050565b60005b838110156117ce5781810151838201526020016117b6565b838111156109995750506000910152565b600081518084526117f78160208601602086016117b3565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117ac60208301846117df565b60006020828403121561184e57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461187957600080fd5b919050565b6000806040838503121561189157600080fd5b61189a83611855565b946020939093013593505050565b6000806000606084860312156118bd57600080fd5b6118c684611855565b92506118d460208501611855565b9150604084013590509250925092565b6000602082840312156118f657600080fd5b6117ac82611855565b6000806040838503121561191257600080fd5b61191b83611855565b91506020830135801515811461193057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561198057600080fd5b61198985611855565b935061199760208601611855565b925060408501359150606085013567ffffffffffffffff808211156119bb57600080fd5b818701915087601f8301126119cf57600080fd5b8135818111156119e1576119e161193b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611a2757611a2761193b565b816040528281528a6020848701011115611a4057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060408385031215611a7757600080fd5b611a8083611855565b9150611a8e60208401611855565b90509250929050565b600181811c90821680611aab57607f821691505b602082108103611ae4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082611b2857611b28611aea565b500690565b7f3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323081527f30302f737667222077696474683d223130323422206865696768743d2231303260208201527f34222066696c6c3d226e6f6e65223e3c706174682066696c6c3d2268736c2800604082015260008351611bb181605f8501602088016117b3565b7f2c20313030252c20313025292220643d224d3020306831303234763130323448605f918401918201527f307a22202f3e3c672066696c6c3d2268736c2800000000000000000000000000607f8201528351611c148160928401602088016117b3565b7f2c20313030252c2039302529223e3c7061746820643d224d393033203433372e609292909101918201527f35633020392e3131332d372e3338382031362e352d31362e352031362e35732d60b28201527f31362e352d372e3338372d31362e352d31362e3520372e3338382d31362e352060d28201527f31362e352d31362e352031362e3520372e3338372031362e352031362e357a4d60f28201527f3639382e3532392035363663362e39323120302031322e35332d352e353936206101128201527f31322e35332d31322e35762d353063302d362e39303420352e3630392d31322e6101328201527f352031322e3532392d31322e356832352e30353963362e393220302031322e356101528201527f323920352e3539362031322e3532392031322e35763530633020362e393034206101728201527f352e3630392031322e352031322e35332031322e357331322e3532392d352e356101928201527f39362031322e3532392d31322e35762d353063302d362e39303420352e3630396101b28201527f2d31322e352031322e35332d31322e356832352e30353963362e3932203020316101d28201527f322e35323920352e3539362031322e3532392031322e35763530633020362e396101f28201527f303420352e3630392031322e352031322e3532392031322e356833372e3538396102128201527f63362e393220302031322e3532392d352e3539362031322e3532392d31322e356102328201527f762d373563302d362e3930342d352e3630392d31322e352d31322e3532392d316102528201527f322e35732d31322e353320352e3539362d31322e35332031322e357635362e326102728201527f3561362e32363420362e3236342030203120312d31322e3532392030563437386102928201527f2e3563302d362e3930342d352e3630392d31322e352d31322e35332d31322e356102b28201527f483639382e353239632d362e393220302d31322e35323920352e3539362d31326102d28201527f2e3532392031322e35763735633020362e39303420352e3630392031322e35206102f28201527f31322e3532392031322e357a22202f3e3c7061746820643d224d3135372e36356103128201527f3520353431632d362e39333220302d31322e3535322d352e3539362d31322e356103328201527f35322d31322e35762d353063302d362e3930342d352e3631392d31322e352d316103528201527f322e3535312d31322e3553313230203437312e35393620313230203437382e356103728201527f763735633020362e39303420352e36322031322e352031322e3535322031322e6103928201527f35683135302e363263362e39333320302031322e3535322d352e3539362031326103b28201527f2e3535322d31322e35762d353063302d362e39303420352e3631392d31322e356103d28201527f2031322e3535322d31322e35683134342e33343563332e343635203020362e326103f28201527f373620322e37393820362e32373620362e3235732d322e38313120362e32352d6104128201527f362e32373620362e3235483332302e383238632d362e39333320302d31322e356104328201527f353220352e3539362d31322e3535322031322e357633372e35633020362e39306104528201527f3420352e3631392031322e352031322e3535322031322e35683135302e3632636104728201527f362e39333320302031322e3535322d352e3539362031322e3535322d31322e356104928201527f762d373563302d362e3930342d352e3631392d31322e352d31322e3535322d316104b28201527f322e35483238332e313732632d362e39333220302d31322e35353120352e35396104d28201527f362d31322e3535312031322e35763530633020362e3930342d352e36313920316104f28201527f322e352d31322e3535322031322e35682d32352e313033632d362e39333320306105128201527f2d31322e3535322d352e3539362d31322e3535322d31322e35762d353063302d6105328201527f362e3930342d352e36322d31322e352d31322e3535322d31322e35732d31322e6105528201527f35353220352e3539362d31322e3535322031322e35763530633020362e3930346105728201527f2d352e3631392031322e352d31322e3535312031322e35682d32352e3130347a6105928201527f6d3330312e3234322d362e3235633020332e3435322d322e38313120362e32356105b28201527f2d362e32373620362e3235483333392e363535632d332e34363520302d362e326105d28201527f37362d322e3739382d362e3237362d362e323573322e3831312d362e323520366105f28201527f2e3237362d362e3235683131322e39363663332e343635203020362e323736206106128201527f322e37393820362e32373620362e32357a4d343937203535332e3831386330206106328201527f362e39323920352e3632382031322e3534362031322e3537312031322e3534366106528201527f6831333261362e323820362e323820302030203120362e32383620362e3237326106728201527f20362e323820362e32382030203020312d362e32383620362e323733682d31336106928201527f32632d362e39343320302d31322e35373120352e3631362d31322e35373120316106b28201527f322e3534364131322e35362031322e3536203020302030203530392e353731206106d28201527f363034683135302e38353863362e39343320302031322e3537312d352e3631366106f28201527f2031322e3537312d31322e353435762d3131322e393163302d362e3932382d356107128201527f2e3632382d31322e3534352d31322e3537312d31322e353435483530392e35376107328201527f31632d362e39343320302d31322e35373120352e3631372d31322e35373120316107528201527f322e3534357637352e3237337a6d33372e3731342d36322e373237632d362e396107728201527f343320302d31322e35373120352e3631372d31322e3537312031322e353435766107928201527f32352e303931633020362e39323920352e3632382031322e3534362031322e356107b28201527f37312031322e353436683130302e35373263362e39343320302031322e3537316107d28201527f2d352e3631372031322e3537312d31322e353436762d32352e30393163302d366107f28201527f2e3932382d352e3632382d31322e3534352d31322e3537312d31322e353435486108128201527f3533342e3731347a222066696c6c2d72756c653d226576656e6f646422202f3e6108328201527f3c2f673e3c2f7376673e0000000000000000000000000000000000000000000061085282015261085c01949350505050565b7f7b226e616d65223a20227761676d6920230000000000000000000000000000008152600083516125f28160118501602088016117b3565b7f222c2022696d616765223a2022646174613a696d6167652f7376672b786d6c3b6011918401918201527f6261736536342c00000000000000000000000000000000000000000000000000603182015283516126558160388401602088016117b3565b7f227d00000000000000000000000000000000000000000000000000000000000060389290910191820152603a01949350505050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c0000008152600082516126c381601d8501602087016117b3565b91909101601d0192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015612711576127116126d0565b500390565b60008219821115612729576127296126d0565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361275f5761275f6126d0565b5060010190565b60008261277557612775611aea565b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156127e1576127e16126d0565b500290565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152508360408301526080606083015261282560808301846117df565b9695505050505050565b60006020828403121561284157600080fd5b81516117ac8161175e56fe4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2fa26469706673582212201665a4f9111990d7529375848d3fd02c0121091a940da59e763eba826e7b077064736f6c634300080d0033", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('parameters: blockNumber', async () => { + const [result] = renderComposable(() => + useBytecode({ + address: address.wagmiMintExample, + blockNumber: 15564163n, + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockNumber": 15564163n, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('parameters: blockTag', async () => { + const [result] = renderComposable(() => + useBytecode({ + address: address.wagmiMintExample, + blockTag: 'earliest', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "blockTag": "earliest", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('parameters: chainId', async () => { + const [result] = renderComposable(() => + useBytecode({ + address: address.wagmiMintExample, + chainId: chain.optimism.id, + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": null, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "chainId": 10, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + const contractAddress = ref() + + const [result] = renderComposable(() => + useBytecode({ address: contractAddress }), + ) + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "getBytecode", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "pending", + "suspense": [Function], + } + `) + + contractAddress.value = address.wagmiMintExample + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": "0x608060405234801561001057600080fd5b50600436106101005760003560e01c80636352211e11610097578063a22cb46511610066578063a22cb46514610215578063b88d4fde14610228578063c87b56dd1461023b578063e985e9c51461024e57600080fd5b80636352211e146101d457806370a08231146101e757806395d89b41146101fa578063a0712d681461020257600080fd5b80631249c58b116100d35780631249c58b1461018f57806318160ddd1461019757806323b872dd146101ae57806342842e0e146101c157600080fd5b806301ffc9a71461010557806306fdde031461012d578063081812fc14610142578063095ea7b31461017a575b600080fd5b61011861011336600461178f565b610297565b60405190151581526020015b60405180910390f35b61013561037c565b6040516101249190611829565b61015561015036600461183c565b61040e565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610124565b61018d61018836600461187e565b6104d3565b005b61018d61062b565b6101a060065481565b604051908152602001610124565b61018d6101bc3660046118a8565b61067d565b61018d6101cf3660046118a8565b610704565b6101556101e236600461183c565b61071f565b6101a06101f53660046118e4565b6107b7565b61013561086b565b61018d61021036600461183c565b61087a565b61018d6102233660046118ff565b610902565b61018d61023636600461196a565b610911565b61013561024936600461183c565b61099f565b61011861025c366004611a64565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd00000000000000000000000000000000000000000000000000000000148061032a57507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b8061037657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60606000805461038b90611a97565b80601f01602080910402602001604051908101604052809291908181526020018280546103b790611a97565b80156104045780601f106103d957610100808354040283529160200191610404565b820191906000526020600020905b8154815290600101906020018083116103e757829003601f168201915b5050505050905090565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff166104aa5760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a20617070726f76656420717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084015b60405180910390fd5b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006104de8261071f565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105815760405162461bcd60e51b815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084016104a1565b3373ffffffffffffffffffffffffffffffffffffffff821614806105aa57506105aa813361025c565b61061c5760405162461bcd60e51b815260206004820152603860248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f74206f7760448201527f6e6572206e6f7220617070726f76656420666f7220616c6c000000000000000060648201526084016104a1565b6106268383610b07565b505050565b6007545b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156106615760010161062f565b61066b3382610ba7565b60068054600190810190915501600755565b6106873382610bc1565b6106f95760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b610626838383610d17565b61062683838360405180602001604052806000815250610911565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16806103765760405162461bcd60e51b815260206004820152602960248201527f4552433732313a206f776e657220717565727920666f72206e6f6e657869737460448201527f656e7420746f6b656e000000000000000000000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff82166108425760405162461bcd60e51b815260206004820152602a60248201527f4552433732313a2062616c616e636520717565727920666f7220746865207a6560448201527f726f20616464726573730000000000000000000000000000000000000000000060648201526084016104a1565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461038b90611a97565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156108ec5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e2049442069732074616b656e00000000000000000000000000000060448201526064016104a1565b6108f63382610ba7565b50600680546001019055565b61090d338383610f4a565b5050565b61091b3383610bc1565b61098d5760405162461bcd60e51b815260206004820152603160248201527f4552433732313a207472616e736665722063616c6c6572206973206e6f74206f60448201527f776e6572206e6f7220617070726f76656400000000000000000000000000000060648201526084016104a1565b6109998484848461105d565b50505050565b6040517f666f726567726f756e64000000000000000000000000000000000000000000006020820152602a810182905260609060009061016890604a016040516020818303038152906040528051906020012060001c6109ff9190611b19565b6040517f6261636b67726f756e64000000000000000000000000000000000000000000006020820152602a810185905290915060009061016890604a016040516020818303038152906040528051906020012060001c610a5f9190611b19565b90506000610aba610a6f866110e6565b610aa9610a7b866110e6565b610a84866110e6565b604051602001610a95929190611b2d565b60405160208183030381529060405261121b565b604051602001610a959291906125ba565b9050600081604051602001610acf919061268b565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190529695505050505050565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84169081179091558190610b618261071f565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b61090d82826040518060200160405280600081525061136e565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff16610c585760405162461bcd60e51b815260206004820152602c60248201527f4552433732313a206f70657261746f7220717565727920666f72206e6f6e657860448201527f697374656e7420746f6b656e000000000000000000000000000000000000000060648201526084016104a1565b6000610c638361071f565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480610cd1575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b80610d0f57508373ffffffffffffffffffffffffffffffffffffffff16610cf78461040e565b73ffffffffffffffffffffffffffffffffffffffff16145b949350505050565b8273ffffffffffffffffffffffffffffffffffffffff16610d378261071f565b73ffffffffffffffffffffffffffffffffffffffff1614610dc05760405162461bcd60e51b815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e657200000000000000000000000000000000000000000000000000000060648201526084016104a1565b73ffffffffffffffffffffffffffffffffffffffff8216610e485760405162461bcd60e51b8152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016104a1565b610e53600082610b07565b73ffffffffffffffffffffffffffffffffffffffff83166000908152600360205260408120805460019290610e899084906126ff565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290610ec4908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610fc55760405162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c65720000000000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b611068848484610d17565b611074848484846113f7565b6109995760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b60608160000361112957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115611153578061113d8161272e565b915061114c9050600a83612766565b915061112d565b60008167ffffffffffffffff81111561116e5761116e61193b565b6040519080825280601f01601f191660200182016040528015611198576020820181803683370190505b5090505b8415610d0f576111ad6001836126ff565b91506111ba600a86611b19565b6111c5906030612716565b60f81b8183815181106111da576111da61277a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350611214600a86612766565b945061119c565b6060815160000361123a57505060408051602081019091526000815290565b600060405180606001604052806040815260200161284d60409139905060006003845160026112699190612716565b6112739190612766565b61127e9060046127a9565b67ffffffffffffffff8111156112965761129661193b565b6040519080825280601f01601f1916602001820160405280156112c0576020820181803683370190505b509050600182016020820185865187015b8082101561132c576003820191508151603f8160121c168501518453600184019350603f81600c1c168501518453600184019350603f8160061c168501518453600184019350603f81168501518453506001830192506112d1565b5050600386510660018114611348576002811461135b57611363565b603d6001830353603d6002830353611363565b603d60018303535b509195945050505050565b61137883836115d0565b61138560008484846113f7565b6106265760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b600073ffffffffffffffffffffffffffffffffffffffff84163b156115c5576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a029061146e9033908990889088906004016127e6565b6020604051808303816000875af19250505080156114c7575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526114c49181019061282f565b60015b61157a573d8080156114f5576040519150601f19603f3d011682016040523d82523d6000602084013e6114fa565b606091505b5080516000036115725760405162461bcd60e51b815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e746572000000000000000000000000000060648201526084016104a1565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050610d0f565b506001949350505050565b73ffffffffffffffffffffffffffffffffffffffff82166116335760405162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f206164647265737360448201526064016104a1565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff16156116a55760405162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e7465640000000060448201526064016104a1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054600192906116db908490612716565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461178c57600080fd5b50565b6000602082840312156117a157600080fd5b81356117ac8161175e565b9392505050565b60005b838110156117ce5781810151838201526020016117b6565b838111156109995750506000910152565b600081518084526117f78160208601602086016117b3565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117ac60208301846117df565b60006020828403121561184e57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461187957600080fd5b919050565b6000806040838503121561189157600080fd5b61189a83611855565b946020939093013593505050565b6000806000606084860312156118bd57600080fd5b6118c684611855565b92506118d460208501611855565b9150604084013590509250925092565b6000602082840312156118f657600080fd5b6117ac82611855565b6000806040838503121561191257600080fd5b61191b83611855565b91506020830135801515811461193057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561198057600080fd5b61198985611855565b935061199760208601611855565b925060408501359150606085013567ffffffffffffffff808211156119bb57600080fd5b818701915087601f8301126119cf57600080fd5b8135818111156119e1576119e161193b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611a2757611a2761193b565b816040528281528a6020848701011115611a4057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060408385031215611a7757600080fd5b611a8083611855565b9150611a8e60208401611855565b90509250929050565b600181811c90821680611aab57607f821691505b602082108103611ae4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082611b2857611b28611aea565b500690565b7f3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323081527f30302f737667222077696474683d223130323422206865696768743d2231303260208201527f34222066696c6c3d226e6f6e65223e3c706174682066696c6c3d2268736c2800604082015260008351611bb181605f8501602088016117b3565b7f2c20313030252c20313025292220643d224d3020306831303234763130323448605f918401918201527f307a22202f3e3c672066696c6c3d2268736c2800000000000000000000000000607f8201528351611c148160928401602088016117b3565b7f2c20313030252c2039302529223e3c7061746820643d224d393033203433372e609292909101918201527f35633020392e3131332d372e3338382031362e352d31362e352031362e35732d60b28201527f31362e352d372e3338372d31362e352d31362e3520372e3338382d31362e352060d28201527f31362e352d31362e352031362e3520372e3338372031362e352031362e357a4d60f28201527f3639382e3532392035363663362e39323120302031322e35332d352e353936206101128201527f31322e35332d31322e35762d353063302d362e39303420352e3630392d31322e6101328201527f352031322e3532392d31322e356832352e30353963362e393220302031322e356101528201527f323920352e3539362031322e3532392031322e35763530633020362e393034206101728201527f352e3630392031322e352031322e35332031322e357331322e3532392d352e356101928201527f39362031322e3532392d31322e35762d353063302d362e39303420352e3630396101b28201527f2d31322e352031322e35332d31322e356832352e30353963362e3932203020316101d28201527f322e35323920352e3539362031322e3532392031322e35763530633020362e396101f28201527f303420352e3630392031322e352031322e3532392031322e356833372e3538396102128201527f63362e393220302031322e3532392d352e3539362031322e3532392d31322e356102328201527f762d373563302d362e3930342d352e3630392d31322e352d31322e3532392d316102528201527f322e35732d31322e353320352e3539362d31322e35332031322e357635362e326102728201527f3561362e32363420362e3236342030203120312d31322e3532392030563437386102928201527f2e3563302d362e3930342d352e3630392d31322e352d31322e35332d31322e356102b28201527f483639382e353239632d362e393220302d31322e35323920352e3539362d31326102d28201527f2e3532392031322e35763735633020362e39303420352e3630392031322e35206102f28201527f31322e3532392031322e357a22202f3e3c7061746820643d224d3135372e36356103128201527f3520353431632d362e39333220302d31322e3535322d352e3539362d31322e356103328201527f35322d31322e35762d353063302d362e3930342d352e3631392d31322e352d316103528201527f322e3535312d31322e3553313230203437312e35393620313230203437382e356103728201527f763735633020362e39303420352e36322031322e352031322e3535322031322e6103928201527f35683135302e363263362e39333320302031322e3535322d352e3539362031326103b28201527f2e3535322d31322e35762d353063302d362e39303420352e3631392d31322e356103d28201527f2031322e3535322d31322e35683134342e33343563332e343635203020362e326103f28201527f373620322e37393820362e32373620362e3235732d322e38313120362e32352d6104128201527f362e32373620362e3235483332302e383238632d362e39333320302d31322e356104328201527f353220352e3539362d31322e3535322031322e357633372e35633020362e39306104528201527f3420352e3631392031322e352031322e3535322031322e35683135302e3632636104728201527f362e39333320302031322e3535322d352e3539362031322e3535322d31322e356104928201527f762d373563302d362e3930342d352e3631392d31322e352d31322e3535322d316104b28201527f322e35483238332e313732632d362e39333220302d31322e35353120352e35396104d28201527f362d31322e3535312031322e35763530633020362e3930342d352e36313920316104f28201527f322e352d31322e3535322031322e35682d32352e313033632d362e39333320306105128201527f2d31322e3535322d352e3539362d31322e3535322d31322e35762d353063302d6105328201527f362e3930342d352e36322d31322e352d31322e3535322d31322e35732d31322e6105528201527f35353220352e3539362d31322e3535322031322e35763530633020362e3930346105728201527f2d352e3631392031322e352d31322e3535312031322e35682d32352e3130347a6105928201527f6d3330312e3234322d362e3235633020332e3435322d322e38313120362e32356105b28201527f2d362e32373620362e3235483333392e363535632d332e34363520302d362e326105d28201527f37362d322e3739382d362e3237362d362e323573322e3831312d362e323520366105f28201527f2e3237362d362e3235683131322e39363663332e343635203020362e323736206106128201527f322e37393820362e32373620362e32357a4d343937203535332e3831386330206106328201527f362e39323920352e3632382031322e3534362031322e3537312031322e3534366106528201527f6831333261362e323820362e323820302030203120362e32383620362e3237326106728201527f20362e323820362e32382030203020312d362e32383620362e323733682d31336106928201527f32632d362e39343320302d31322e35373120352e3631362d31322e35373120316106b28201527f322e3534364131322e35362031322e3536203020302030203530392e353731206106d28201527f363034683135302e38353863362e39343320302031322e3537312d352e3631366106f28201527f2031322e3537312d31322e353435762d3131322e393163302d362e3932382d356107128201527f2e3632382d31322e3534352d31322e3537312d31322e353435483530392e35376107328201527f31632d362e39343320302d31322e35373120352e3631372d31322e35373120316107528201527f322e3534357637352e3237337a6d33372e3731342d36322e373237632d362e396107728201527f343320302d31322e35373120352e3631372d31322e3537312031322e353435766107928201527f32352e303931633020362e39323920352e3632382031322e3534362031322e356107b28201527f37312031322e353436683130302e35373263362e39343320302031322e3537316107d28201527f2d352e3631372031322e3537312d31322e353436762d32352e30393163302d366107f28201527f2e3932382d352e3632382d31322e3534352d31322e3537312d31322e353435486108128201527f3533342e3731347a222066696c6c2d72756c653d226576656e6f646422202f3e6108328201527f3c2f673e3c2f7376673e0000000000000000000000000000000000000000000061085282015261085c01949350505050565b7f7b226e616d65223a20227761676d6920230000000000000000000000000000008152600083516125f28160118501602088016117b3565b7f222c2022696d616765223a2022646174613a696d6167652f7376672b786d6c3b6011918401918201527f6261736536342c00000000000000000000000000000000000000000000000000603182015283516126558160388401602088016117b3565b7f227d00000000000000000000000000000000000000000000000000000000000060389290910191820152603a01949350505050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c0000008152600082516126c381601d8501602087016117b3565b91909101601d0192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015612711576127116126d0565b500390565b60008219821115612729576127296126d0565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361275f5761275f6126d0565b5060010190565b60008261277557612775611aea565b500490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156127e1576127e16126d0565b500290565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152508360408301526080606083015261282560808301846117df565b9695505050505050565b60006020828403121561284157600080fd5b81516117ac8161175e56fe4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2fa26469706673582212201665a4f9111990d7529375848d3fd02c0121091a940da59e763eba826e7b077064736f6c634300080d0033", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getBytecode", + { + "address": undefined, + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const [result] = renderComposable(() => useBytecode()) + + await wait(100) + expect(result.isPending.value).toBe(true) +}) diff --git a/packages/vue/src/composables/useBytecode.ts b/packages/vue/src/composables/useBytecode.ts new file mode 100644 index 0000000000..a697ff1a92 --- /dev/null +++ b/packages/vue/src/composables/useBytecode.ts @@ -0,0 +1,72 @@ +import type { + Config, + GetBytecodeErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetBytecodeData, + type GetBytecodeOptions, + type GetBytecodeQueryKey, + getBytecodeQueryOptions, +} from '@wagmi/core/query' +import type { GetBytecodeQueryFnData } from '@wagmi/core/query' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' + +import { computed } from 'vue' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseBytecodeParameters< + config extends Config = Config, + selectData = GetBytecodeData, +> = Compute< + DeepMaybeRef< + GetBytecodeOptions & + ConfigParameter & + QueryParameter< + GetBytecodeQueryFnData, + GetBytecodeErrorType, + selectData, + GetBytecodeQueryKey + > + > +> + +export type UseBytecodeReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/hooks/useBytecode */ +export function useBytecode< + config extends Config = ResolvedRegister['config'], + selectData = GetBytecodeData, +>( + parameters_: UseBytecodeParameters = {}, +): UseBytecodeReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const chainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + address: contractAddress, + chainId: parametersChainId, + query = {}, + } = parameters.value + + const options = getBytecodeQueryOptions(config, { + ...parameters.value, + address: contractAddress, + chainId: parametersChainId ?? chainId.value, + }) + const enabled = Boolean(contractAddress && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions as any) as UseBytecodeReturnType +} diff --git a/packages/vue/src/composables/useChainId.test-d.ts b/packages/vue/src/composables/useChainId.test-d.ts new file mode 100644 index 0000000000..9f7d060c57 --- /dev/null +++ b/packages/vue/src/composables/useChainId.test-d.ts @@ -0,0 +1,14 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useChainId } from './useChainId.js' + +test('default', () => { + const chainId = useChainId() + expectTypeOf(chainId.value).toEqualTypeOf() +}) + +test('parameters: config', () => { + const chainId = useChainId({ config }) + expectTypeOf(chainId.value).toEqualTypeOf<1 | 456 | 10>() +}) diff --git a/packages/vue/src/composables/useChainId.test.ts b/packages/vue/src/composables/useChainId.test.ts new file mode 100644 index 0000000000..0a14370fc2 --- /dev/null +++ b/packages/vue/src/composables/useChainId.test.ts @@ -0,0 +1,22 @@ +import { config } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useChainId } from './useChainId.js' + +test('default', () => { + const [chainId] = renderComposable(() => useChainId()) + + expect(chainId.value).toMatchInlineSnapshot('1') + + config.setState((x) => ({ ...x, chainId: 456 })) + + expect(chainId.value).toMatchInlineSnapshot('456') +}) + +test('parameters: config', () => { + const [chainId] = renderComposable(() => useChainId({ config }), { + attach() {}, + }) + expect(chainId.value).toBeDefined() +}) diff --git a/packages/vue/src/composables/useChainId.ts b/packages/vue/src/composables/useChainId.ts new file mode 100644 index 0000000000..f459857ca9 --- /dev/null +++ b/packages/vue/src/composables/useChainId.ts @@ -0,0 +1,35 @@ +import { + type Config, + type GetChainIdReturnType, + type ResolvedRegister, + getChainId, + watchChainId, +} from '@wagmi/core' +import { type Ref, onScopeDispose, readonly, ref } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseChainIdParameters = + ConfigParameter + +export type UseChainIdReturnType = Ref< + GetChainIdReturnType +> + +/** https://wagmi.sh/vue/api/composables/useChainId */ +export function useChainId( + parameters: UseChainIdParameters = {}, +): UseChainIdReturnType { + const config = useConfig(parameters) + + const chainId = ref(getChainId(config)) + const unsubscribe = watchChainId(config, { + onChange(data) { + chainId.value = data + }, + }) + onScopeDispose(() => unsubscribe()) + + return readonly(chainId) +} diff --git a/packages/vue/src/composables/useChains.test.ts b/packages/vue/src/composables/useChains.test.ts new file mode 100644 index 0000000000..4ef51405a9 --- /dev/null +++ b/packages/vue/src/composables/useChains.test.ts @@ -0,0 +1,16 @@ +import { config } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { celo } from 'viem/chains' +import { expect, test } from 'vitest' + +import { useChains } from './useChains.js' + +test('default', async () => { + const [chains] = renderComposable(() => useChains()) + + expect(chains.value.length).toBe(3) + + config._internal.chains.setState((x) => [...x, celo]) + + expect(chains.value.length).toBe(4) +}) diff --git a/packages/vue/src/composables/useChains.ts b/packages/vue/src/composables/useChains.ts new file mode 100644 index 0000000000..6cbada538b --- /dev/null +++ b/packages/vue/src/composables/useChains.ts @@ -0,0 +1,35 @@ +import { + type Config, + type GetChainsReturnType, + type ResolvedRegister, + getChains, +} from '@wagmi/core' +import { watchChains } from '@wagmi/core/internal' + +import { type Ref, onScopeDispose, readonly, ref } from 'vue' +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseChainsParameters = + ConfigParameter + +export type UseChainsReturnType = Ref< + GetChainsReturnType +> + +/** https://wagmi.sh/vue/api/composables/useChains */ +export function useChains( + parameters: UseChainsParameters = {}, +): UseChainsReturnType { + const config = useConfig(parameters) + + const chains = ref>(getChains(config)) + const unsubscribe = watchChains(config, { + onChange(data) { + chains.value = data as any + }, + }) + onScopeDispose(() => unsubscribe()) + + return readonly(chains) as UseChainsReturnType +} diff --git a/packages/vue/src/composables/useClient.test-d.ts b/packages/vue/src/composables/useClient.test-d.ts new file mode 100644 index 0000000000..3500f2a404 --- /dev/null +++ b/packages/vue/src/composables/useClient.test-d.ts @@ -0,0 +1,42 @@ +import { chain, config } from '@wagmi/test' +import type { Chain } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useClient } from './useClient.js' + +test('default', () => { + const client = useClient({ config }) + expectTypeOf(client.value.chain).toEqualTypeOf< + (typeof config)['chains'][number] + >() + expectTypeOf(client.value.transport.type).toEqualTypeOf<'http'>() +}) + +test('parameters: chainId', () => { + const client = useClient({ + config, + chainId: chain.mainnet.id, + }) + expectTypeOf(client.value.chain).toEqualTypeOf() + expectTypeOf(client.value.chain).not.toEqualTypeOf() + expectTypeOf(client.value.transport.type).toEqualTypeOf<'http'>() +}) + +test('behavior: unconfigured chain', () => { + { + const client = useClient({ chainId: 123456 }) + if (client.value) { + expectTypeOf(client.value.chain).toEqualTypeOf() + expectTypeOf(client.value.transport.type).toEqualTypeOf() + } else { + expectTypeOf(client.value).toEqualTypeOf() + } + } + + const client = useClient({ + config, + // @ts-expect-error + chainId: 123456, + }) + expectTypeOf(client.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useClient.test.ts b/packages/vue/src/composables/useClient.test.ts new file mode 100644 index 0000000000..0d9d2325b2 --- /dev/null +++ b/packages/vue/src/composables/useClient.test.ts @@ -0,0 +1,41 @@ +import { switchChain } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { ref } from 'vue' + +import { useClient } from './useClient.js' + +test('default', async () => { + const [client] = renderComposable(() => useClient()) + + expect(client?.value?.chain.id).toEqual(1) + + await switchChain(config, { chainId: 456 }) + + expect(client.value?.chain?.id).toEqual(456) +}) + +test('parameters: config', () => { + const [chainId] = renderComposable(() => useClient({ config }), { + attach() {}, + }) + expect(chainId.value).toBeDefined() +}) + +test('behavior: controlled chainId', async () => { + const chainId = ref(456) + + const [client] = renderComposable(() => useClient({ chainId })) + + expect(client?.value?.chain.id).toEqual(456) + + chainId.value = 1 + + await waitFor(client, (client) => client?.chain.id === 1) +}) + +test('behavior: unconfigured chain', () => { + const [client] = renderComposable(() => useClient({ chainId: 123456 })) + expect(client.value).toBeUndefined() +}) diff --git a/packages/vue/src/composables/useClient.ts b/packages/vue/src/composables/useClient.ts new file mode 100644 index 0000000000..e29b38826b --- /dev/null +++ b/packages/vue/src/composables/useClient.ts @@ -0,0 +1,66 @@ +import { + type Config, + type GetClientParameters, + type GetClientReturnType, + type ResolvedRegister, + getClient, + watchClient, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type Ref, + computed, + onScopeDispose, + readonly, + ref, + watchEffect, +} from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { useConfig } from './useConfig.js' + +export type UseClientParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = Compute< + DeepMaybeRef & ConfigParameter> +> + +export type UseClientReturnType< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +> = Ref> + +/** https://wagmi.sh/vue/api/composables/useClient */ +export function useClient< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | number | undefined = + | config['chains'][number]['id'] + | undefined, +>( + parameters: UseClientParameters = {}, +): UseClientReturnType { + const params = computed(() => deepUnref(parameters)) + + const config = useConfig(params) + + const client = ref(getClient(config, params.value as GetClientParameters)) + watchEffect(() => { + client.value = getClient(config, params.value as GetClientParameters) + }) + const unsubscribe = watchClient(config, { + onChange(data) { + if (client.value?.uid === data?.uid) return + client.value = data + }, + }) + onScopeDispose(() => unsubscribe()) + + return readonly(client) as UseClientReturnType +} diff --git a/packages/vue/src/composables/useConfig.test-d.ts b/packages/vue/src/composables/useConfig.test-d.ts new file mode 100644 index 0000000000..f2b9d2bb31 --- /dev/null +++ b/packages/vue/src/composables/useConfig.test-d.ts @@ -0,0 +1,16 @@ +import type { Config } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useConfig } from './useConfig.js' + +test('default', async () => { + const result = useConfig() + expectTypeOf(result).toEqualTypeOf() +}) + +test('parameters: config', async () => { + const result = useConfig({ config }) + expectTypeOf(result).not.toEqualTypeOf() + expectTypeOf(result).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useConfig.test.ts b/packages/vue/src/composables/useConfig.test.ts new file mode 100644 index 0000000000..a7036b65e8 --- /dev/null +++ b/packages/vue/src/composables/useConfig.test.ts @@ -0,0 +1,24 @@ +import { renderComposable } from '@wagmi/test/vue' +import { expect, test, vi } from 'vitest' + +import { useConfig } from './useConfig.js' + +test('default', () => { + const [config] = renderComposable(() => useConfig()) + expect(config).toBeDefined() +}) + +test('behavior: throws when not provided via WagmiPlugin', () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + try { + renderComposable(() => useConfig(), { attach() {} }) + } catch (error) { + expect(error).toMatchInlineSnapshot(` + [WagmiPluginNotFoundError: No \`config\` found in Vue context, use \`WagmiPlugin\` to properly initialize the library. + + Docs: https://wagmi.sh/vue/api/TODO.html + Version: @wagmi/vue@x.y.z] + `) + } +}) diff --git a/packages/vue/src/composables/useConfig.ts b/packages/vue/src/composables/useConfig.ts new file mode 100644 index 0000000000..a827f818a5 --- /dev/null +++ b/packages/vue/src/composables/useConfig.ts @@ -0,0 +1,34 @@ +import type { Config, ResolvedRegister } from '@wagmi/core' +import { hasInjectionContext, inject, unref } from 'vue' + +import { + WagmiInjectionContextError, + WagmiPluginNotFoundError, +} from '../errors/plugin.js' +import { configKey } from '../plugin.js' +import type { ConfigParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' + +export type UseConfigParameters = DeepMaybeRef< + ConfigParameter +> + +export type UseConfigReturnType = config + +/** https://wagmi.sh/vue/api/composables/useConfig */ +export function useConfig( + parameters_: UseConfigParameters = {}, +): UseConfigReturnType { + const parameters = unref(parameters_) + + // passthrough config if provided + if (parameters.config) return parameters.config as UseConfigReturnType + + // ensures that `inject()` can be used + if (!hasInjectionContext()) throw new WagmiInjectionContextError() + + const config = inject(configKey) + if (!config) throw new WagmiPluginNotFoundError() + + return config as UseConfigReturnType +} diff --git a/packages/vue/src/composables/useConnect.test-d.ts b/packages/vue/src/composables/useConnect.test-d.ts new file mode 100644 index 0000000000..44c7b6fdac --- /dev/null +++ b/packages/vue/src/composables/useConnect.test-d.ts @@ -0,0 +1,121 @@ +import type { + ConnectErrorType, + Connector, + CreateConnectorFn, +} from '@wagmi/core' +import { config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useConnect } from './useConnect.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { connect, context, data, error, variables } = useConnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: Connector | CreateConnectorFn + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toMatchTypeOf< + | { + chainId?: number | undefined + connector: Connector | CreateConnectorFn + } + | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + connect( + { connector }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + chainId?: number | undefined + connector: typeof connector | CreateConnectorFn + foo?: string | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useConnect.test.ts b/packages/vue/src/composables/useConnect.test.ts new file mode 100644 index 0000000000..206abd41ee --- /dev/null +++ b/packages/vue/src/composables/useConnect.test.ts @@ -0,0 +1,31 @@ +import { disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { afterEach, expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useConnect } from './useConnect.js' + +const connector = config.connectors[0]! + +afterEach(async () => { + if (config.state.current === connector.uid) + await disconnect(config, { connector }) +}) + +test('default', async () => { + const [account] = renderComposable(() => useAccount()) + const [connect] = renderComposable(() => useConnect()) + + expect(account.address.value).not.toBeDefined() + expect(account.status.value).toEqual('disconnected') + + connect.connect({ + connector: connect.connectors[0]!, + }) + + await waitFor(account.isConnected, (isConnected) => Boolean(isConnected)) + + expect(account.address.value).toBeDefined() + expect(account.status.value).toEqual('connected') +}) diff --git a/packages/vue/src/composables/useConnect.ts b/packages/vue/src/composables/useConnect.ts new file mode 100644 index 0000000000..7659dc3411 --- /dev/null +++ b/packages/vue/src/composables/useConnect.ts @@ -0,0 +1,92 @@ +import { useMutation } from '@tanstack/vue-query' +import type { + Config, + ConnectErrorType, + GetConnectorsReturnType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ConnectData, + type ConnectMutate, + type ConnectMutateAsync, + type ConnectVariables, + connectMutationOptions, +} from '@wagmi/core/query' +import { onScopeDispose } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { useConnectors } from './useConnectors.js' + +export type UseConnectParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > + | undefined + } +> + +export type UseConnectReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + ConnectData, + ConnectErrorType, + ConnectVariables, + context + > & { + connect: ConnectMutate + connectAsync: ConnectMutateAsync + connectors: Compute | config['connectors'] + } +> + +/** https://wagmi.sh/vue/api/composables/useConnect */ +export function useConnect< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseConnectParameters = {}, +): UseConnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = connectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + // Reset mutation back to an idle state when the connector disconnects. + const unsubscribe = config.subscribe( + ({ status }) => status, + (status, previousStatus) => { + if (previousStatus === 'connected' && status === 'disconnected') + result.reset() + }, + ) + onScopeDispose(() => unsubscribe()) + + type Return = UseConnectReturnType + return { + ...(result as Return), + connect: mutate as Return['connect'], + connectAsync: mutateAsync as Return['connectAsync'], + connectors: useConnectors({ config }).value, + } +} diff --git a/packages/vue/src/composables/useConnections.test.ts b/packages/vue/src/composables/useConnections.test.ts new file mode 100644 index 0000000000..d51401b427 --- /dev/null +++ b/packages/vue/src/composables/useConnections.test.ts @@ -0,0 +1,16 @@ +import { connect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useConnections } from './useConnections.js' + +test('default', async () => { + const [connections] = renderComposable(() => useConnections()) + + expect(connections.value).toEqual([]) + + await connect(config, { connector: config.connectors[0]! }) + + expect(connections.value.length).toBe(1) +}) diff --git a/packages/vue/src/composables/useConnections.ts b/packages/vue/src/composables/useConnections.ts new file mode 100644 index 0000000000..7bcc499498 --- /dev/null +++ b/packages/vue/src/composables/useConnections.ts @@ -0,0 +1,30 @@ +import { + type GetConnectionsReturnType, + getConnections, + watchConnections, +} from '@wagmi/core' +import { type Ref, onScopeDispose, readonly, ref } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseConnectionsParameters = ConfigParameter + +export type UseConnectionsReturnType = Ref + +/** https://wagmi.sh/vue/api/composables/useConnections */ +export function useConnections( + parameters: UseConnectionsParameters = {}, +): UseConnectionsReturnType { + const config = useConfig(parameters) + + const connections = ref(getConnections(config)) + const unsubscribe = watchConnections(config, { + onChange(data) { + connections.value = data + }, + }) + onScopeDispose(() => unsubscribe()) + + return readonly(connections) as UseConnectionsReturnType +} diff --git a/packages/vue/src/composables/useConnectorClient.test-d.ts b/packages/vue/src/composables/useConnectorClient.test-d.ts new file mode 100644 index 0000000000..710d8260d4 --- /dev/null +++ b/packages/vue/src/composables/useConnectorClient.test-d.ts @@ -0,0 +1,12 @@ +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useConnectorClient } from './useConnectorClient.js' + +test('parameters: config', async () => { + const client = useConnectorClient({ config }) + expectTypeOf(client.data?.value?.chain?.id!).toEqualTypeOf<1 | 456 | 10>() + + const client2 = useConnectorClient({ config, chainId: 1 }) + expectTypeOf(client2.data?.value?.chain?.id!).toEqualTypeOf<1>() +}) diff --git a/packages/vue/src/composables/useConnectorClient.test.ts b/packages/vue/src/composables/useConnectorClient.test.ts new file mode 100644 index 0000000000..7a361702ac --- /dev/null +++ b/packages/vue/src/composables/useConnectorClient.test.ts @@ -0,0 +1,156 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useConnect } from './useConnect.js' +import { useConnectorClient } from './useConnectorClient.js' +import { useDisconnect } from './useDisconnect.js' +import { useSwitchChain } from './useSwitchChain.js' + +const connector = config.connectors[0]! + +test('default', async () => { + const [client] = renderComposable(() => useConnectorClient()) + + expect(deepUnref(client)).toMatchInlineSnapshot(` + { + "data": undefined, + "dataUpdatedAt": 0, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": false, + "isFetchedAfterMount": false, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": true, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": false, + "queryKey": [ + "connectorClient", + { + "chainId": 1, + "connectorUid": undefined, + }, + ], + "refetch": [Function], + "status": "pending", + "suspense": [Function], + } + `) +}) + +test('behavior: connected on mount', async () => { + await connect(config, { connector }) + + const [client] = renderComposable(() => useConnectorClient()) + + await waitFor(client.isSuccess, (isSuccess) => isSuccess === true) + + const { data, queryKey: _, ...rest } = deepUnref(client) + expect(data).toMatchObject( + expect.objectContaining({ + account: expect.any(Object), + chain: expect.any(Object), + }), + ) + expect(rest).toMatchInlineSnapshot(` + { + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": false, + "isSuccess": true, + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: connect and disconnect', async () => { + const [connect] = renderComposable(() => useConnect()) + const [client] = renderComposable(() => useConnectorClient()) + const [disconnect] = renderComposable(() => useDisconnect()) + + expect(client.data.value).not.toBeDefined() + + connect.connect({ + connector: connect.connectors[0]!, + }) + + await waitFor(client.data, (data) => data !== undefined) + + disconnect.disconnect() + + await waitFor(client.data, (data) => data === undefined) +}) + +test('behavior: switch chains', async () => { + await connect(config, { connector }) + + const [connectorClient] = renderComposable(() => useConnectorClient()) + const [switchChain] = renderComposable(() => useSwitchChain()) + + expect(connectorClient.data.value).not.toBeDefined() + + await waitFor(connectorClient.data, (data) => data !== undefined) + + switchChain.switchChain({ chainId: 456 }) + await waitFor(switchChain.isSuccess, (isSuccess) => isSuccess === true) + await waitFor(connectorClient.data, (data) => data !== undefined) + expect(connectorClient.data?.value?.chain.id).toEqual(456) + + switchChain.switchChain({ chainId: 1 }) + await waitFor(switchChain.isSuccess, (isSuccess) => isSuccess === true) + await waitFor(connectorClient.data, (data) => data !== undefined) + expect(connectorClient.data?.value?.chain.id).toEqual(1) + + await disconnect(config, { connector }) +}) + +test('behavior: disabled when properties missing', async () => { + const [connectorClient] = renderComposable(() => useConnectorClient()) + + await wait(100) + expect(connectorClient.isPending.value).toBe(true) +}) + +test('behavior: disabled when connecting', async () => { + const [connectorClient] = renderComposable(() => useConnectorClient()) + + config.setState((x) => ({ ...x, status: 'connecting' })) + + await wait(100) + expect(connectorClient.isLoading.value).not.toBeTruthy() +}) diff --git a/packages/vue/src/composables/useConnectorClient.ts b/packages/vue/src/composables/useConnectorClient.ts new file mode 100644 index 0000000000..08735718bf --- /dev/null +++ b/packages/vue/src/composables/useConnectorClient.ts @@ -0,0 +1,132 @@ +import { useQueryClient } from '@tanstack/vue-query' +import type { + Config, + Connector, + GetConnectorClientErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute, Omit } from '@wagmi/core/internal' +import { + type GetConnectorClientData, + type GetConnectorClientOptions, + type GetConnectorClientQueryFnData, + type GetConnectorClientQueryKey, + getConnectorClientQueryOptions, +} from '@wagmi/core/query' +import { computed, ref, watchEffect } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { DeepMaybeRef, DeepUnwrapRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { + type UseQueryParameters, + type UseQueryReturnType, + useQuery, +} from '../utils/query.js' +import { useAccount } from './useAccount.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseConnectorClientParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +> = Compute< + DeepMaybeRef< + GetConnectorClientOptions & + ConfigParameter & { + query?: + | Compute< + Omit< + DeepUnwrapRef< + UseQueryParameters< + GetConnectorClientQueryFnData, + GetConnectorClientErrorType, + selectData, + GetConnectorClientQueryKey + > + >, + 'gcTime' | 'staleTime' + > + > + | undefined + } + > +> + +export type UseConnectorClientReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useConnectorClient */ +export function useConnectorClient< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetConnectorClientData, +>( + parameters_: UseConnectorClientParameters = {}, +): UseConnectorClientReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const queryClient = useQueryClient() + const { + address, + connector: accountConnector, + status, + } = useAccount({ config }) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + chainId = configChainId.value, + connector = accountConnector.value, + query = {}, + } = parameters.value + const { queryKey, ...options } = getConnectorClientQueryOptions< + config, + chainId + >(config as config, { + ...deepUnref(parameters), + chainId: chainId as chainId, + connector: connector as Connector, + }) + const enabled = Boolean( + (status.value === 'connected' || + (status.value === 'reconnecting' && connector?.getProvider)) && + (query.enabled ?? true), + ) + return { + ...query, + ...options, + queryKey, + enabled, + staleTime: Number.POSITIVE_INFINITY, + } + }) + + const addressRef = ref(address) + watchEffect(() => { + const previousAddress = addressRef.value + if (!address && previousAddress) { + // remove when account is disconnected + queryClient.removeQueries({ queryKey: queryOptions.value.queryKey }) + addressRef.value = undefined + } else if (address.value !== previousAddress) { + // invalidate when address changes + queryClient.invalidateQueries({ queryKey: queryOptions.value.queryKey }) + addressRef.value = address.value + } + }) + + return useQuery(queryOptions as any) as UseConnectorClientReturnType< + config, + chainId, + selectData + > +} diff --git a/packages/vue/src/composables/useConnectors.test.ts b/packages/vue/src/composables/useConnectors.test.ts new file mode 100644 index 0000000000..16fec78dfe --- /dev/null +++ b/packages/vue/src/composables/useConnectors.test.ts @@ -0,0 +1,21 @@ +import { mock } from '@wagmi/connectors' +import { accounts, config } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useConnectors } from './useConnectors.js' + +test('default', async () => { + const [connectors] = renderComposable(() => useConnectors()) + + const count = config.connectors.length + expect(connectors.value.length).toBe(count) + expect(connectors.value).toEqual(config.connectors) + + config._internal.connectors.setState(() => [ + ...config.connectors, + config._internal.connectors.setup(mock({ accounts })), + ]) + + expect(connectors.value.length).toBe(count + 1) +}) diff --git a/packages/vue/src/composables/useConnectors.ts b/packages/vue/src/composables/useConnectors.ts new file mode 100644 index 0000000000..4d12d81806 --- /dev/null +++ b/packages/vue/src/composables/useConnectors.ts @@ -0,0 +1,37 @@ +import { + type Config, + type GetConnectorsReturnType, + type ResolvedRegister, + getConnectors, + watchConnectors, +} from '@wagmi/core' +import { type Ref, onScopeDispose, ref } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import { useConfig } from './useConfig.js' + +export type UseConnectorsParameters = + ConfigParameter + +export type UseConnectorsReturnType = Ref< + GetConnectorsReturnType +> + +/** https://wagmi.sh/vue/api/composables/useConnectors */ +export function useConnectors< + config extends Config = ResolvedRegister['config'], +>( + parameters: UseConnectorsParameters = {}, +): UseConnectorsReturnType { + const config = useConfig(parameters) + + const connectors = ref(getConnectors(config)) + const unsubscribe = watchConnectors(config, { + onChange(data) { + connectors.value = data as never + }, + }) + onScopeDispose(() => unsubscribe()) + + return connectors +} diff --git a/packages/vue/src/composables/useDisconnect.test-d.ts b/packages/vue/src/composables/useDisconnect.test-d.ts new file mode 100644 index 0000000000..3be3fe9488 --- /dev/null +++ b/packages/vue/src/composables/useDisconnect.test-d.ts @@ -0,0 +1,87 @@ +import type { Connector, DisconnectErrorType } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useDisconnect } from './useDisconnect.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('parameter', () => { + expectTypeOf(useDisconnect().disconnect) + .parameter(0) + .toEqualTypeOf<{ connector?: Connector | undefined } | undefined>() + expectTypeOf(useDisconnect().disconnect) + .parameter(0) + .toEqualTypeOf<{ connector?: Connector | undefined } | undefined>() +}) + +test('context', () => { + const { context, data, disconnect, error, variables } = useDisconnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + disconnect( + { connector }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + { connector?: Connector | undefined } | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useDisconnect.test.ts b/packages/vue/src/composables/useDisconnect.test.ts new file mode 100644 index 0000000000..935c75827d --- /dev/null +++ b/packages/vue/src/composables/useDisconnect.test.ts @@ -0,0 +1,30 @@ +import { connect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { beforeEach, expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useDisconnect } from './useDisconnect.js' + +const connector = config.connectors[0]! + +beforeEach(async () => { + await connect(config, { connector }) +}) + +test('default', async () => { + const [account] = renderComposable(() => useAccount()) + const [disconnect] = renderComposable(() => useDisconnect()) + + expect(account.address.value).toBeDefined() + expect(account.status.value).toEqual('connected') + + disconnect.disconnect() + + await waitFor(account.isDisconnected, (isDisconnected) => + Boolean(isDisconnected), + ) + + expect(account.address.value).not.toBeDefined() + expect(account.status.value).toEqual('disconnected') +}) diff --git a/packages/vue/src/composables/useDisconnect.ts b/packages/vue/src/composables/useDisconnect.ts new file mode 100644 index 0000000000..540e588124 --- /dev/null +++ b/packages/vue/src/composables/useDisconnect.ts @@ -0,0 +1,70 @@ +import { useMutation } from '@tanstack/vue-query' +import type { Connector, DisconnectErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type DisconnectData, + type DisconnectMutate, + type DisconnectMutateAsync, + type DisconnectVariables, + disconnectMutationOptions, +} from '@wagmi/core/query' +import { type Ref, computed } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { useConnections } from './useConnections.js' + +export type UseDisconnectParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > + | undefined + } +> + +export type UseDisconnectReturnType = Compute< + UseMutationReturnType< + DisconnectData, + DisconnectErrorType, + DisconnectVariables, + context + > & { + connectors: Ref + disconnect: DisconnectMutate + disconnectAsync: DisconnectMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useDisconnect */ +export function useDisconnect( + parameters: UseDisconnectParameters = {}, +): UseDisconnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + const connections = useConnections({ config }) + + const mutationOptions = disconnectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: computed(() => + connections.value.map((connection) => connection.connector), + ), + disconnect: mutate, + disconnectAsync: mutateAsync, + } +} diff --git a/packages/vue/src/composables/useEnsAddress.test.ts b/packages/vue/src/composables/useEnsAddress.test.ts new file mode 100644 index 0000000000..01d35769b7 --- /dev/null +++ b/packages/vue/src/composables/useEnsAddress.test.ts @@ -0,0 +1,52 @@ +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useEnsAddress } from './useEnsAddress.js' + +test('default', async () => { + const [result] = renderComposable(() => + useEnsAddress({ + name: 'wevm.eth', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensAddress", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) diff --git a/packages/vue/src/composables/useEnsAddress.ts b/packages/vue/src/composables/useEnsAddress.ts new file mode 100644 index 0000000000..ecb27ac143 --- /dev/null +++ b/packages/vue/src/composables/useEnsAddress.ts @@ -0,0 +1,65 @@ +import type { + Config, + GetEnsAddressErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsAddressData, + type GetEnsAddressOptions, + type GetEnsAddressQueryFnData, + type GetEnsAddressQueryKey, + getEnsAddressQueryOptions, +} from '@wagmi/core/query' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsAddressParameters< + config extends Config = Config, + selectData = GetEnsAddressData, +> = Compute< + DeepMaybeRef< + GetEnsAddressOptions & + ConfigParameter & + QueryParameter< + GetEnsAddressQueryFnData, + GetEnsAddressErrorType, + selectData, + GetEnsAddressQueryKey + > + > +> + +export type UseEnsAddressReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useEnsAddress */ +export function useEnsAddress< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsAddressData, +>( + parameters_: UseEnsAddressParameters = {}, +): UseEnsAddressReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { chainId = configChainId.value, name, query = {} } = parameters.value + const options = getEnsAddressQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(name && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions as any) as UseEnsAddressReturnType +} diff --git a/packages/vue/src/composables/useEnsAvatar.test.ts b/packages/vue/src/composables/useEnsAvatar.test.ts new file mode 100644 index 0000000000..ab024c89b5 --- /dev/null +++ b/packages/vue/src/composables/useEnsAvatar.test.ts @@ -0,0 +1,52 @@ +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useEnsAvatar } from './useEnsAvatar.js' + +test('default', async () => { + const [result] = renderComposable(() => + useEnsAvatar({ + name: 'wevm.eth', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": "https://euc.li/wevm.eth", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensAvatar", + { + "chainId": 1, + "name": "wevm.eth", + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) diff --git a/packages/vue/src/composables/useEnsAvatar.ts b/packages/vue/src/composables/useEnsAvatar.ts new file mode 100644 index 0000000000..ff328ad87c --- /dev/null +++ b/packages/vue/src/composables/useEnsAvatar.ts @@ -0,0 +1,65 @@ +import type { + Config, + GetEnsAvatarErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsAvatarData, + type GetEnsAvatarOptions, + type GetEnsAvatarQueryFnData, + type GetEnsAvatarQueryKey, + getEnsAvatarQueryOptions, +} from '@wagmi/core/query' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsAvatarParameters< + config extends Config = Config, + selectData = GetEnsAvatarData, +> = Compute< + DeepMaybeRef< + GetEnsAvatarOptions & + ConfigParameter & + QueryParameter< + GetEnsAvatarQueryFnData, + GetEnsAvatarErrorType, + selectData, + GetEnsAvatarQueryKey + > + > +> + +export type UseEnsAvatarReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useEnsAvatar */ +export function useEnsAvatar< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsAvatarData, +>( + parameters_: UseEnsAvatarParameters = {}, +): UseEnsAvatarReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { chainId = configChainId.value, name, query = {} } = parameters.value + const options = getEnsAvatarQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(name && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions as any) as UseEnsAvatarReturnType +} diff --git a/packages/vue/src/composables/useEnsName.test.ts b/packages/vue/src/composables/useEnsName.test.ts new file mode 100644 index 0000000000..ee5b8cfab6 --- /dev/null +++ b/packages/vue/src/composables/useEnsName.test.ts @@ -0,0 +1,52 @@ +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useEnsName } from './useEnsName.js' + +test('default', async () => { + const [result] = renderComposable(() => + useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": "wevm.eth", + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "ensName", + { + "address": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "chainId": 1, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) diff --git a/packages/vue/src/composables/useEnsName.ts b/packages/vue/src/composables/useEnsName.ts new file mode 100644 index 0000000000..ba251aa5a3 --- /dev/null +++ b/packages/vue/src/composables/useEnsName.ts @@ -0,0 +1,65 @@ +import type { Config, GetEnsNameErrorType, ResolvedRegister } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetEnsNameData, + type GetEnsNameOptions, + type GetEnsNameQueryFnData, + type GetEnsNameQueryKey, + getEnsNameQueryOptions, +} from '@wagmi/core/query' + +import { computed } from 'vue' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseEnsNameParameters< + config extends Config = Config, + selectData = GetEnsNameData, +> = Compute< + DeepMaybeRef< + GetEnsNameOptions & + ConfigParameter & + QueryParameter< + GetEnsNameQueryFnData, + GetEnsNameErrorType, + selectData, + GetEnsNameQueryKey + > + > +> + +export type UseEnsNameReturnType = + UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useEnsName */ +export function useEnsName< + config extends Config = ResolvedRegister['config'], + selectData = GetEnsNameData, +>( + parameters_: UseEnsNameParameters = {}, +): UseEnsNameReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + address, + chainId = configChainId.value, + query = {}, + } = parameters.value + const options = getEnsNameQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(address && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions as any) as UseEnsNameReturnType +} diff --git a/packages/vue/src/composables/useEstimateGas.test-d.ts b/packages/vue/src/composables/useEstimateGas.test-d.ts new file mode 100644 index 0000000000..ba084419d4 --- /dev/null +++ b/packages/vue/src/composables/useEstimateGas.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useEstimateGas } from './useEstimateGas.js' + +test('select data', () => { + const result = useEstimateGas({ + query: { + select(data) { + return data.toString() + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useEstimateGas.test.ts b/packages/vue/src/composables/useEstimateGas.test.ts new file mode 100644 index 0000000000..f8212f11da --- /dev/null +++ b/packages/vue/src/composables/useEstimateGas.test.ts @@ -0,0 +1,117 @@ +import { accounts, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { ref } from 'vue' +import { deepUnref } from '../utils/cloneDeep.js' +import { useEstimateGas } from './useEstimateGas.js' + +test('default', async () => { + const [result] = renderComposable(() => + useEstimateGas({ + account: accounts[0], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": 21000n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateGas", + { + "account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "chainId": 1, + "to": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "value": 10000000000000000n, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('behavior: address: undefined -> defined', async () => { + const address = ref() + + const [result] = renderComposable(() => + useEstimateGas({ + account: address, + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }), + ) + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') + + address.value = accounts[0] + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": 21000n, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "estimateGas", + { + "account": undefined, + "chainId": 1, + "to": "0xd2135CfB216b74109775236E36d4b433F1DF507B", + "value": 10000000000000000n, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) diff --git a/packages/vue/src/composables/useEstimateGas.ts b/packages/vue/src/composables/useEstimateGas.ts new file mode 100644 index 0000000000..fd885ec712 --- /dev/null +++ b/packages/vue/src/composables/useEstimateGas.ts @@ -0,0 +1,83 @@ +import type { + Config, + EstimateGasErrorType, + ResolvedRegister, +} from '@wagmi/core' +import { + type EstimateGasData, + type EstimateGasOptions, + type EstimateGasQueryFnData, + type EstimateGasQueryKey, + estimateGasQueryOptions, +} from '@wagmi/core/query' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { useConnectorClient } from './useConnectorClient.js' + +export type UseEstimateGasParameters< + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = EstimateGasData, +> = DeepMaybeRef< + EstimateGasOptions & + ConfigParameter & + QueryParameter< + EstimateGasQueryFnData, + EstimateGasErrorType, + selectData, + EstimateGasQueryKey + > +> + +export type UseEstimateGasReturnType = + UseQueryReturnType + +/** https://wagmi.sh/react/api/hooks/useEstimateGas */ +export function useEstimateGas< + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = EstimateGasData, +>( + parameters?: UseEstimateGasParameters, +): UseEstimateGasReturnType + +export function useEstimateGas( + parameters_: UseEstimateGasParameters = {}, +): UseEstimateGasReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const { data: connectorClient } = useConnectorClient( + computed(() => ({ + connector: parameters.value.connector, + query: { enabled: parameters.value.account === undefined }, + })), + ) + + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + account = connectorClient?.value?.account, + chainId = configChainId.value, + connector, + query = {}, + } = parameters.value + const options = estimateGasQueryOptions(config, { + ...parameters.value, + account, + chainId, + connector, + }) + const enabled = Boolean((account || connector) && (query.enabled ?? true)) + return { ...query, ...options, enabled } + }) + + return useQuery(queryOptions) +} diff --git a/packages/vue/src/composables/useReadContract.test-d.ts b/packages/vue/src/composables/useReadContract.test-d.ts new file mode 100644 index 0000000000..0685dde8f4 --- /dev/null +++ b/packages/vue/src/composables/useReadContract.test-d.ts @@ -0,0 +1,99 @@ +import { abi } from '@wagmi/test' +import type { Address } from 'viem' +import { assertType, expectTypeOf, test } from 'vitest' + +import type { DeepUnwrapRef } from '../types/ref.js' +import { + type UseReadContractParameters, + type UseReadContractReturnType, + useReadContract, +} from './useReadContract.js' + +test('select data', () => { + const result = useReadContract({ + address: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + query: { + select(data) { + expectTypeOf(data).toEqualTypeOf() + return data?.toString() + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) + +test('UseReadContractParameters', () => { + type Result = DeepUnwrapRef< + UseReadContractParameters + > + expectTypeOf<{ + functionName?: + | 'symbol' + | 'name' + | 'allowance' + | 'balanceOf' + | 'decimals' + | 'totalSupply' + | undefined + args?: readonly [Address] | undefined + }>().toEqualTypeOf>() +}) + +test('UseReadContractReturnType', () => { + type Result = UseReadContractReturnType + expectTypeOf().toEqualTypeOf() +}) + +test('overloads', () => { + const result1 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + }) + assertType(result1.data.value) + + const result2 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: [], + }) + assertType(result2.data.value) + + const result3 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x'], + }) + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + assertType(result3.data) + + const result4 = useReadContract({ + address: '0x', + abi: abi.viewOverloads, + functionName: 'foo', + args: ['0x', '0x'], + }) + assertType< + | { + foo: `0x${string}` + bar: `0x${string}` + } + | undefined + // @ts-ignore – TODO: Fix https://github.com/wevm/viem/issues/1916 + >(result4.data) +}) + +test('deployless read (bytecode)', () => { + const result = useReadContract({ + code: '0x', + abi: abi.erc20, + functionName: 'balanceOf', + args: ['0x'], + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useReadContract.test.ts b/packages/vue/src/composables/useReadContract.test.ts new file mode 100644 index 0000000000..2fe2fb7033 --- /dev/null +++ b/packages/vue/src/composables/useReadContract.test.ts @@ -0,0 +1,105 @@ +import { abi, address, bytecode, chain, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { ref } from 'vue' + +import { useReadContract } from './useReadContract.js' + +test('default', async () => { + const [result] = renderComposable(() => + useReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + }), + ) + + await waitFor(result.isSuccess) + + expect(result.data.value).toBe(4n) + expect(result.queryKey).toMatchInlineSnapshot(` + [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 1, + "functionName": "balanceOf", + }, + ] + `) +}) + +test('parameters: chainId', async () => { + const [result] = renderComposable(() => + useReadContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'balanceOf', + args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'], + chainId: chain.mainnet2.id, + }), + ) + + await waitFor(result.isSuccess) + + expect(result.data.value).toBe(4n) + expect(result.queryKey).toMatchInlineSnapshot(` + [ + "readContract", + { + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": [ + "0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC", + ], + "chainId": 456, + "functionName": "balanceOf", + }, + ] + `) +}) + +test('parameters: deployless read (bytecode)', async () => { + const [result] = renderComposable(() => + useReadContract({ + abi: abi.wagmiMintExample, + functionName: 'name', + code: bytecode.wagmiMintExample, + }), + ) + + await waitFor(result.isSuccess) + + expect(result.data.value).toMatchInlineSnapshot(`"wagmi"`) +}) + +test.skip('behavior: disabled when missing properties', async () => { + const addressRef = ref() + const abiRef = ref() + const functionNameRef = ref() + + const [result] = renderComposable(() => + useReadContract({ + abi: abiRef, + address: addressRef, + functionName: functionNameRef, + }), + ) + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') + + addressRef.value = '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2' + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') + + abiRef.value = abi.wagmiMintExample + functionNameRef.value = 'totalSupply' + + await wait(100) + expect(result.fetchStatus.value).toBe('fetching') +}) diff --git a/packages/vue/src/composables/useReadContract.ts b/packages/vue/src/composables/useReadContract.ts new file mode 100644 index 0000000000..10ecc859ff --- /dev/null +++ b/packages/vue/src/composables/useReadContract.ts @@ -0,0 +1,116 @@ +import type { + Config, + ReadContractErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { UnionCompute } from '@wagmi/core/internal' +import { + type ReadContractData, + type ReadContractOptions, + type ReadContractQueryFnData, + type ReadContractQueryKey, + readContractQueryOptions, + structuralSharing, +} from '@wagmi/core/query' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseReadContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + selectData = ReadContractData, +> = UnionCompute< + DeepMaybeRef< + ReadContractOptions & + ConfigParameter & + QueryParameter< + ReadContractQueryFnData, + ReadContractErrorType, + selectData, + ReadContractQueryKey + > + > +> + +export type UseReadContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'pure' | 'view' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'pure' | 'view', + functionName + > = ContractFunctionArgs, + selectData = ReadContractData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/hooks/useReadContract */ +export function useReadContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + config extends Config = ResolvedRegister['config'], + selectData = ReadContractData, +>( + parameters_: UseReadContractParameters< + abi, + functionName, + args, + config, + selectData + > = {} as any, +): UseReadContractReturnType { + const parameters = computed(() => deepUnref(parameters_)) as any + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + abi, + address, + chainId = configChainId.value, + code, + functionName, + query = {}, + } = parameters.value + const options = readContractQueryOptions( + config as any, + { ...parameters.value, chainId }, + ) + const enabled = Boolean( + (address || code) && abi && functionName && (query.enabled ?? true), + ) + return { + ...query, + ...options, + enabled, + structuralSharing: query.structuralSharing ?? structuralSharing, + } + }) + + return useQuery(queryOptions) as UseReadContractReturnType< + abi, + functionName, + args, + selectData + > +} diff --git a/packages/vue/src/composables/useReconnect.test-d.ts b/packages/vue/src/composables/useReconnect.test-d.ts new file mode 100644 index 0000000000..bc4ecd8d8a --- /dev/null +++ b/packages/vue/src/composables/useReconnect.test-d.ts @@ -0,0 +1,154 @@ +import type { + Connector, + CreateConnectorFn, + ReconnectErrorType, +} from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Address } from 'viem' +import { useReconnect } from './useReconnect.js' + +const connectors = [config.connectors[0]!] +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, reconnect, variables } = useReconnect({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(data).toEqualTypeOf< + { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + >() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toEqualTypeOf< + | { + connectors?: readonly (CreateConnectorFn | Connector)[] | undefined + } + | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + reconnect( + { connectors }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(data).toEqualTypeOf< + { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + >() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + connector: Connector + }[] + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf< + | { + connectors?: + | readonly (CreateConnectorFn | Connector)[] + | undefined + } + | undefined + >() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useReconnect.test.ts b/packages/vue/src/composables/useReconnect.test.ts new file mode 100644 index 0000000000..49cf7f73e5 --- /dev/null +++ b/packages/vue/src/composables/useReconnect.test.ts @@ -0,0 +1,81 @@ +import { mock } from '@wagmi/connectors' +import { connect, disconnect } from '@wagmi/core' +import { accounts, config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { afterEach, expect, test } from 'vitest' + +import { useReconnect } from './useReconnect.js' + +const connector = config._internal.connectors.setup( + mock({ + accounts, + features: { reconnect: true }, + }), +) + +afterEach(async () => { + if (config.state.current) await disconnect(config) +}) + +test('default', async () => { + await connect(config, { connector }) + + const [reconnect] = renderComposable(() => useReconnect()) + + reconnect.reconnect() + await waitFor(reconnect.isSuccess) + + expect(reconnect.data.value).toStrictEqual([]) +}) + +test('parameters: connectors (Connector)', async () => { + await connect(config, { connector }) + + const [reconnect] = renderComposable(() => useReconnect()) + + reconnect.reconnect({ connectors: [connector] }) + await waitFor(reconnect.isSuccess) + + expect(reconnect.data.value).toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) +}) + +test('parameters: connectors (CreateConnectorFn)', async () => { + const connector = mock({ + accounts, + features: { reconnect: true }, + }) + await connect(config, { connector }) + + const [reconnect] = renderComposable(() => useReconnect()) + + reconnect.reconnect({ connectors: [connector] }) + await waitFor(reconnect.isSuccess) + + expect(reconnect.data.value).toMatchObject( + expect.arrayContaining([ + expect.objectContaining({ + accounts: expect.any(Array), + chainId: expect.any(Number), + }), + ]), + ) +}) + +test("behavior: doesn't reconnect if already reconnecting", async () => { + const previousStatus = config.state.status + config.setState((x) => ({ ...x, status: 'reconnecting' })) + + const [reconnect] = renderComposable(() => useReconnect()) + + await expect( + reconnect.reconnectAsync({ connectors: [connector] }), + ).resolves.toStrictEqual([]) + config.setState((x) => ({ ...x, status: previousStatus })) +}) diff --git a/packages/vue/src/composables/useReconnect.ts b/packages/vue/src/composables/useReconnect.ts new file mode 100644 index 0000000000..cfab97d858 --- /dev/null +++ b/packages/vue/src/composables/useReconnect.ts @@ -0,0 +1,65 @@ +import { useMutation } from '@tanstack/vue-query' +import type { Connector, ReconnectErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type ReconnectData, + type ReconnectMutate, + type ReconnectMutateAsync, + type ReconnectVariables, + reconnectMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseReconnectParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context + > + | undefined + } +> + +export type UseReconnectReturnType = Compute< + UseMutationReturnType< + ReconnectData, + ReconnectErrorType, + ReconnectVariables, + context + > & { + connectors: readonly Connector[] + reconnect: ReconnectMutate + reconnectAsync: ReconnectMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useReconnect */ +export function useReconnect( + parameters: UseReconnectParameters = {}, +): UseReconnectReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = reconnectMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: config.connectors, + reconnect: mutate, + reconnectAsync: mutateAsync, + } +} diff --git a/packages/vue/src/composables/useSendTransaction.test-d.ts b/packages/vue/src/composables/useSendTransaction.test-d.ts new file mode 100644 index 0000000000..4670e879cf --- /dev/null +++ b/packages/vue/src/composables/useSendTransaction.test-d.ts @@ -0,0 +1,78 @@ +import type { SendTransactionErrorType } from '@wagmi/core' +import type { Hash } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useSendTransaction } from './useSendTransaction.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, sendTransaction, variables } = + useSendTransaction({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + sendTransaction( + { to: '0x' }, + { + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useSendTransaction.test.ts b/packages/vue/src/composables/useSendTransaction.test.ts new file mode 100644 index 0000000000..ae069766ee --- /dev/null +++ b/packages/vue/src/composables/useSendTransaction.test.ts @@ -0,0 +1,25 @@ +import { connect, disconnect } from '@wagmi/core' +import { config, transactionHashRegex } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { parseEther } from 'viem' +import { expect, test } from 'vitest' + +import { useSendTransaction } from './useSendTransaction.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [result] = renderComposable(() => useSendTransaction()) + + result.sendTransaction({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) + await waitFor(result.isSuccess) + + expect(result.data.value).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) diff --git a/packages/vue/src/composables/useSendTransaction.ts b/packages/vue/src/composables/useSendTransaction.ts new file mode 100644 index 0000000000..d5bc718337 --- /dev/null +++ b/packages/vue/src/composables/useSendTransaction.ts @@ -0,0 +1,76 @@ +import { useMutation } from '@tanstack/vue-query' +import type { + Config, + ResolvedRegister, + SendTransactionErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SendTransactionData, + type SendTransactionMutate, + type SendTransactionMutateAsync, + type SendTransactionVariables, + sendTransactionMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSendTransactionParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > + | undefined + } +> + +export type UseSendTransactionReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SendTransactionData, + SendTransactionErrorType, + SendTransactionVariables, + context + > & { + sendTransaction: SendTransactionMutate + sendTransactionAsync: SendTransactionMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useSendTransaction */ +export function useSendTransaction< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSendTransactionParameters = {}, +): UseSendTransactionReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = sendTransactionMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + sendTransaction: mutate, + sendTransactionAsync: mutateAsync, + } as UseSendTransactionReturnType +} diff --git a/packages/vue/src/composables/useSignMessage.test-d.ts b/packages/vue/src/composables/useSignMessage.test-d.ts new file mode 100644 index 0000000000..5c8da53bba --- /dev/null +++ b/packages/vue/src/composables/useSignMessage.test-d.ts @@ -0,0 +1,64 @@ +import type { SignMessageErrorType } from '@wagmi/core' +import type { SignMessageVariables } from '@wagmi/core/query' +import { expectTypeOf, test } from 'vitest' + +import { useSignMessage } from './useSignMessage.js' + +const message = 'hello world' +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, signMessage, variables } = useSignMessage({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf<`0x${string}`>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toEqualTypeOf< + SignMessageVariables | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + signMessage( + { message }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf<`0x${string}`>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf<`0x${string}` | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useSignMessage.test.ts b/packages/vue/src/composables/useSignMessage.test.ts new file mode 100644 index 0000000000..a00691fb7f --- /dev/null +++ b/packages/vue/src/composables/useSignMessage.test.ts @@ -0,0 +1,43 @@ +import { connect, disconnect, getAccount } from '@wagmi/core' +import { config, privateKey } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { recoverMessageAddress } from 'viem' +import { expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { useSignMessage } from './useSignMessage.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [result] = renderComposable(() => useSignMessage()) + + result.signMessage({ message: 'foo bar baz' }) + await waitFor(result.isSuccess) + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: result.data.value!, + }), + ).resolves.toEqual(getAccount(config).address) + + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const [result] = renderComposable(() => useSignMessage()) + + const account = privateKeyToAccount(privateKey) + result.signMessage({ account, message: 'foo bar baz' }) + await waitFor(result.isSuccess) + + await expect( + recoverMessageAddress({ + message: 'foo bar baz', + signature: result.data.value!, + }), + ).resolves.toEqual(account.address) +}) diff --git a/packages/vue/src/composables/useSignMessage.ts b/packages/vue/src/composables/useSignMessage.ts new file mode 100644 index 0000000000..2f7222b6a4 --- /dev/null +++ b/packages/vue/src/composables/useSignMessage.ts @@ -0,0 +1,63 @@ +import type { SignMessageErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SignMessageData, + type SignMessageMutate, + type SignMessageMutateAsync, + type SignMessageVariables, + signMessageMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import { + type UseMutationParameters, + type UseMutationReturnType, + useMutation, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSignMessageParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > + | undefined + } +> + +export type UseSignMessageReturnType = Compute< + UseMutationReturnType< + SignMessageData, + SignMessageErrorType, + SignMessageVariables, + context + > & { + signMessage: SignMessageMutate + signMessageAsync: SignMessageMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useSignMessage */ +export function useSignMessage( + parameters: UseSignMessageParameters = {}, +): UseSignMessageReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = signMessageMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + signMessage: mutate, + signMessageAsync: mutateAsync, + } +} diff --git a/packages/vue/src/composables/useSignTypedData.test-d.ts b/packages/vue/src/composables/useSignTypedData.test-d.ts new file mode 100644 index 0000000000..ba5c253a8f --- /dev/null +++ b/packages/vue/src/composables/useSignTypedData.test-d.ts @@ -0,0 +1,95 @@ +import type { + SignTypedDataErrorType, + SignTypedDataReturnType, +} from '@wagmi/core' +import type { SignTypedDataVariables } from '@wagmi/core/query' +import { typedData } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useSignTypedData } from './useSignTypedData.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, signTypedData, variables } = useSignTypedData({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toMatchTypeOf< + SignTypedDataVariables | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + signTypedData( + { + types: typedData.basic.types, + primaryType: 'Person', + message: { + name: 'Bob', + wallet: '0x', + }, + }, + { + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toMatchTypeOf<{ + types: typeof typedData.basic.types + primaryType: 'Person' + message: { + name: string + wallet: `0x${string}` + } + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useSignTypedData.test.ts b/packages/vue/src/composables/useSignTypedData.test.ts new file mode 100644 index 0000000000..e3636d21eb --- /dev/null +++ b/packages/vue/src/composables/useSignTypedData.test.ts @@ -0,0 +1,56 @@ +import { connect, disconnect, getAccount } from '@wagmi/core' +import { config, privateKey, typedData } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { recoverTypedDataAddress } from 'viem' +import { expect, test } from 'vitest' + +import { privateKeyToAccount } from 'viem/accounts' +import { useSignTypedData } from './useSignTypedData.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [result] = renderComposable(() => useSignTypedData()) + + result.signTypedData({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await waitFor(result.isSuccess) + + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature: result.data.value!, + }), + ).resolves.toEqual(getAccount(config).address) + + await disconnect(config, { connector }) +}) + +test('behavior: local account', async () => { + const [result] = renderComposable(() => useSignTypedData()) + + const account = privateKeyToAccount(privateKey) + result.signTypedData({ + account, + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + }) + await waitFor(result.isSuccess) + + await expect( + recoverTypedDataAddress({ + types: typedData.basic.types, + primaryType: 'Mail', + message: typedData.basic.message, + signature: result.data.value!, + }), + ).resolves.toEqual(account.address) +}) diff --git a/packages/vue/src/composables/useSignTypedData.ts b/packages/vue/src/composables/useSignTypedData.ts new file mode 100644 index 0000000000..f0bdbf6c64 --- /dev/null +++ b/packages/vue/src/composables/useSignTypedData.ts @@ -0,0 +1,64 @@ +import type { SignTypedDataErrorType } from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SignTypedDataData, + type SignTypedDataMutate, + type SignTypedDataMutateAsync, + type SignTypedDataVariables, + signTypedDataMutationOptions, +} from '@wagmi/core/query' + +import type { ConfigParameter } from '../types/properties.js' +import { + type UseMutationParameters, + type UseMutationReturnType, + useMutation, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseSignTypedDataParameters = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables, + context + > + | undefined + } +> + +export type UseSignTypedDataReturnType = Compute< + UseMutationReturnType< + SignTypedDataData, + SignTypedDataErrorType, + SignTypedDataVariables, + context + > & { + signTypedData: SignTypedDataMutate + signTypedDataAsync: SignTypedDataMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useSignTypedData */ +export function useSignTypedData( + parameters: UseSignTypedDataParameters = {}, +): UseSignTypedDataReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = signTypedDataMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSignTypedDataReturnType + return { + ...result, + signTypedData: mutate as Return['signTypedData'], + signTypedDataAsync: mutateAsync as Return['signTypedDataAsync'], + } +} diff --git a/packages/vue/src/composables/useSimulateContract.test-d.ts b/packages/vue/src/composables/useSimulateContract.test-d.ts new file mode 100644 index 0000000000..e8d2451caa --- /dev/null +++ b/packages/vue/src/composables/useSimulateContract.test-d.ts @@ -0,0 +1,96 @@ +import { abi, type config } from '@wagmi/test' +import type { Address } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { + type UseSimulateContractReturnType, + useSimulateContract, +} from './useSimulateContract.js' + +test('default', () => { + const result = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + }) + + expectTypeOf(result.data.value).toMatchTypeOf< + | { + result: boolean + request: { + chainId?: undefined + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) + +test('select data', () => { + // @ts-ignore TODO: Type instantiation is excessively deep and possibly infinite. + const result = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + query: { + select(data) { + expectTypeOf(data.result).toEqualTypeOf() + return data.request.args + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf< + readonly [Address, Address, bigint] | undefined + >() +}) + +test('UseSimulateContractReturnType', () => { + type Result = UseSimulateContractReturnType< + typeof abi.erc20, + 'transferFrom', + ['0x', '0x', 123n], + typeof config, + 1 + > + expectTypeOf().toMatchTypeOf< + | { + result: boolean + request: { + chainId: number + abi: readonly [ + { + readonly name: 'transferFrom' + readonly type: 'function' + readonly stateMutability: 'nonpayable' + readonly inputs: readonly [ + { readonly type: 'address'; readonly name: 'sender' }, + { readonly type: 'address'; readonly name: 'recipient' }, + { readonly type: 'uint256'; readonly name: 'amount' }, + ] + readonly outputs: readonly [{ type: 'bool' }] + }, + ] + functionName: 'approve' | 'transfer' | 'transferFrom' + args: readonly [Address, Address, bigint] + } + } + | undefined + >() +}) diff --git a/packages/vue/src/composables/useSimulateContract.test.ts b/packages/vue/src/composables/useSimulateContract.test.ts new file mode 100644 index 0000000000..1118a51d68 --- /dev/null +++ b/packages/vue/src/composables/useSimulateContract.test.ts @@ -0,0 +1,59 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useSimulateContract } from './useSimulateContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [result] = renderComposable(() => + useSimulateContract({ + address: address.wagmiMintExample, + abi: abi.wagmiMintExample, + functionName: 'mint', + }), + ) + + await waitFor(result.isSuccess) + + expect(result.data.value).toMatchInlineSnapshot(` + { + "chainId": 1, + "request": { + "abi": [ + { + "inputs": [], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function", + }, + ], + "account": { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "type": "json-rpc", + }, + "address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "args": undefined, + "chainId": 1, + "dataSuffix": undefined, + "functionName": "mint", + }, + "result": undefined, + } + `) + + await disconnect(config, { connector }) +}) + +test('behavior: disabled when properties missing', async () => { + const [result] = renderComposable(() => useSimulateContract()) + + await wait(100) + + expect(result.fetchStatus.value).toBe('idle') +}) diff --git a/packages/vue/src/composables/useSimulateContract.ts b/packages/vue/src/composables/useSimulateContract.ts new file mode 100644 index 0000000000..7ffce57b8e --- /dev/null +++ b/packages/vue/src/composables/useSimulateContract.ts @@ -0,0 +1,145 @@ +import type { + Config, + ResolvedRegister, + SimulateContractErrorType, +} from '@wagmi/core' +import { + type SimulateContractData, + type SimulateContractOptions, + type SimulateContractQueryFnData, + type SimulateContractQueryKey, + simulateContractQueryOptions, +} from '@wagmi/core/query' +import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem' +import { type MaybeRef, computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' +import { useConnectorClient } from './useConnectorClient.js' + +export type UseSimulateContractParameters< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +> = MaybeRef< + DeepMaybeRef< + SimulateContractOptions + > & + ConfigParameter & + QueryParameter< + SimulateContractQueryFnData, + SimulateContractErrorType, + selectData, + SimulateContractQueryKey + > +> + +export type UseSimulateContractReturnType< + abi extends Abi | readonly unknown[] = Abi, + functionName extends ContractFunctionName< + abi, + 'nonpayable' | 'payable' + > = ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + > = ContractFunctionArgs, + config extends Config = Config, + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useSimulateContract */ +export function useSimulateContract< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, + config extends Config = ResolvedRegister['config'], + chainId extends config['chains'][number]['id'] | undefined = undefined, + selectData = SimulateContractData, +>( + parameters_: UseSimulateContractParameters< + abi, + functionName, + args, + config, + chainId, + selectData + > = {} as any, +): UseSimulateContractReturnType< + abi, + functionName, + args, + config, + chainId, + selectData +> { + const parameters = computed(() => deepUnref(parameters_)) as any + + const config = useConfig(parameters) + const { data: connectorClient } = useConnectorClient( + computed(() => ({ + connector: parameters.value.connector, + query: { enabled: parameters.value.account === undefined }, + })), + ) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + abi, + account = connectorClient?.value?.account, + address, + chainId = configChainId.value, + functionName, + query = {}, + } = parameters.value + const options = simulateContractQueryOptions< + config, + abi, + functionName, + args, + chainId + >(config as any, { + ...parameters.value, + account, + chainId, + }) + const enabled = Boolean( + abi && address && functionName && (query.enabled ?? true), + ) + return { + ...query, + ...options, + enabled, + } + }) + + return useQuery(queryOptions as any) as UseSimulateContractReturnType< + abi, + functionName, + args, + config, + chainId, + selectData + > +} diff --git a/packages/vue/src/composables/useSwitchAccount.test-d.ts b/packages/vue/src/composables/useSwitchAccount.test-d.ts new file mode 100644 index 0000000000..d55e4b9d5e --- /dev/null +++ b/packages/vue/src/composables/useSwitchAccount.test-d.ts @@ -0,0 +1,89 @@ +import type { Connector, SwitchAccountErrorType } from '@wagmi/core' +import { config } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { Address } from 'viem' +import { useSwitchAccount } from './useSwitchAccount.js' + +const connector = config.connectors[0]! +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { context, data, error, switchAccount, variables } = useSwitchAccount({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toEqualTypeOf< + { connector: Connector } | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + switchAccount( + { connector }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(data).toEqualTypeOf<{ + accounts: readonly [Address, ...Address[]] + chainId: number + }>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf< + | { + accounts: readonly [Address, ...Address[]] + chainId: number + } + | undefined + >() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ connector: Connector }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useSwitchAccount.test.ts b/packages/vue/src/composables/useSwitchAccount.test.ts new file mode 100644 index 0000000000..aade28e3d2 --- /dev/null +++ b/packages/vue/src/composables/useSwitchAccount.test.ts @@ -0,0 +1,42 @@ +import { connect, disconnect } from '@wagmi/core' +import { config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useSwitchAccount } from './useSwitchAccount.js' + +const connector1 = config.connectors[0]! +const connector2 = config.connectors[1]! + +test('default', async () => { + const [account] = renderComposable(() => useAccount()) + const [switchAccount] = renderComposable(() => useSwitchAccount()) + + expect(switchAccount.connectors.value).toEqual([]) + + await connect(config, { connector: connector2 }) + await connect(config, { connector: connector1 }) + + expect(switchAccount.connectors.value.length).toEqual(2) + + const address1 = account.address.value + expect(address1).toBeDefined() + + switchAccount.switchAccount({ connector: connector2 }) + await waitFor(switchAccount.isSuccess) + + const address2 = account.address.value + expect(address2).toBeDefined() + expect(address1).not.toBe(address2) + + switchAccount.switchAccount({ connector: connector1 }) + await waitFor(switchAccount.isSuccess) + + const address3 = account.address.value + expect(address3).toBeDefined() + expect(address1).toBe(address3) + + await disconnect(config, { connector: connector1 }) + await disconnect(config, { connector: connector2 }) +}) diff --git a/packages/vue/src/composables/useSwitchAccount.ts b/packages/vue/src/composables/useSwitchAccount.ts new file mode 100644 index 0000000000..0c8f76a486 --- /dev/null +++ b/packages/vue/src/composables/useSwitchAccount.ts @@ -0,0 +1,84 @@ +import { useMutation } from '@tanstack/vue-query' +import type { + Config, + Connector, + ResolvedRegister, + SwitchAccountErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchAccountData, + type SwitchAccountMutate, + type SwitchAccountMutateAsync, + type SwitchAccountVariables, + switchAccountMutationOptions, +} from '@wagmi/core/query' +import { type Ref, computed } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' +import { useConnections } from './useConnections.js' + +export type UseSwitchAccountParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > + | undefined + } +> + +export type UseSwitchAccountReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SwitchAccountData, + SwitchAccountErrorType, + SwitchAccountVariables, + context + > & { + connectors: Ref + switchAccount: SwitchAccountMutate + switchAccountAsync: SwitchAccountMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useSwitchAccount */ +export function useSwitchAccount< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchAccountParameters = {}, +): UseSwitchAccountReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + const connections = useConnections({ config }) + + const mutationOptions = switchAccountMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + return { + ...result, + connectors: computed(() => + connections.value.map((connection) => connection.connector), + ), + switchAccount: mutate, + switchAccountAsync: mutateAsync, + } +} diff --git a/packages/vue/src/composables/useSwitchChain.test-d.ts b/packages/vue/src/composables/useSwitchChain.test-d.ts new file mode 100644 index 0000000000..e770460946 --- /dev/null +++ b/packages/vue/src/composables/useSwitchChain.test-d.ts @@ -0,0 +1,118 @@ +import type { Connector, SwitchChainErrorType } from '@wagmi/core' +import type { Chain } from '@wagmi/core/chains' +import type { Compute, ExactPartial } from '@wagmi/core/internal' +import { chain } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import type { AddEthereumChainParameter } from 'viem' +import { useSwitchChain } from './useSwitchChain.js' + +const chainId = chain.mainnet.id +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { chains, context, data, error, switchChain, variables } = + useSwitchChain({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(data).toEqualTypeOf>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + }) + + expectTypeOf(chains.value).toEqualTypeOf() + expectTypeOf(data.value).toEqualTypeOf | undefined>() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toEqualTypeOf< + | { + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + } + | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + switchChain( + { chainId }, + { + onError(error, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess(data, variables, context) { + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(data).toEqualTypeOf>() + expectTypeOf(context).toEqualTypeOf() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf | undefined>() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(variables).toEqualTypeOf<{ + addEthereumChainParameter?: + | ExactPartial> + | undefined + chainId: number + connector?: Connector | undefined + }>() + expectTypeOf(context).toEqualTypeOf() + }, + }, + ) +}) diff --git a/packages/vue/src/composables/useSwitchChain.test.ts b/packages/vue/src/composables/useSwitchChain.test.ts new file mode 100644 index 0000000000..12e7c21c2e --- /dev/null +++ b/packages/vue/src/composables/useSwitchChain.test.ts @@ -0,0 +1,35 @@ +import { connect, disconnect } from '@wagmi/core' +import { chain, config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useAccount } from './useAccount.js' +import { useSwitchChain } from './useSwitchChain.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [account] = renderComposable(() => useAccount()) + const [switchChain] = renderComposable(() => useSwitchChain()) + + const chainId1 = account.chainId.value + expect(chainId1).toBeDefined() + + switchChain.switchChain({ chainId: chain.mainnet2.id }) + await waitFor(switchChain.isSuccess) + + const chainId2 = account.chainId.value + expect(chainId2).toBeDefined() + expect(chainId1).not.toBe(chainId2) + + switchChain.switchChain({ chainId: chain.mainnet.id }) + await waitFor(switchChain.isSuccess) + + const chainId3 = account.chainId.value + expect(chainId3).toBeDefined() + expect(chainId1).toBe(chainId3) + + await disconnect(config, { connector }) +}) diff --git a/packages/vue/src/composables/useSwitchChain.ts b/packages/vue/src/composables/useSwitchChain.ts new file mode 100644 index 0000000000..8ff43c0fa9 --- /dev/null +++ b/packages/vue/src/composables/useSwitchChain.ts @@ -0,0 +1,81 @@ +import { useMutation } from '@tanstack/vue-query' +import type { + Config, + ResolvedRegister, + SwitchChainErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type SwitchChainData, + type SwitchChainMutate, + type SwitchChainMutateAsync, + type SwitchChainVariables, + switchChainMutationOptions, +} from '@wagmi/core/query' +import type { Ref } from 'vue' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useChains } from './useChains.js' +import { useConfig } from './useConfig.js' + +export type UseSwitchChainParameters< + config extends Config = Config, + context = unknown, +> = Compute< + ConfigParameter & { + mutation?: + | UseMutationParameters< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > + | undefined + } +> + +export type UseSwitchChainReturnType< + config extends Config = Config, + context = unknown, +> = Compute< + UseMutationReturnType< + SwitchChainData, + SwitchChainErrorType, + SwitchChainVariables, + context + > & { + chains: Ref + switchChain: SwitchChainMutate + switchChainAsync: SwitchChainMutateAsync + } +> + +/** https://wagmi.sh/vue/api/composables/useSwitchChain */ +export function useSwitchChain< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseSwitchChainParameters = {}, +): UseSwitchChainReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = switchChainMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseSwitchChainReturnType + return { + ...result, + chains: useChains({ config }) as unknown as Ref, + switchChain: mutate as Return['switchChain'], + switchChainAsync: mutateAsync as Return['switchChainAsync'], + } as Return +} diff --git a/packages/vue/src/composables/useTransaction.test-d.ts b/packages/vue/src/composables/useTransaction.test-d.ts new file mode 100644 index 0000000000..bb795814e8 --- /dev/null +++ b/packages/vue/src/composables/useTransaction.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransaction } from './useTransaction.js' + +test('select data', () => { + const result = useTransaction({ + query: { + select(data) { + return data?.nonce + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useTransaction.test.ts b/packages/vue/src/composables/useTransaction.test.ts new file mode 100644 index 0000000000..06659958f2 --- /dev/null +++ b/packages/vue/src/composables/useTransaction.test.ts @@ -0,0 +1,74 @@ +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { deepUnref } from '../utils/cloneDeep.js' +import { useTransaction } from './useTransaction.js' + +test('default', async () => { + const [result] = renderComposable(() => + useTransaction({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": { + "accessList": [], + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "chainId": 1, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gas": 21000n, + "gasPrice": 9371645552n, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "input": "0x", + "maxFeePerGas": 13644824566n, + "maxPriorityFeePerGas": 1500000000n, + "nonce": 86, + "r": "0x40174f9a38df876c1a7ce2587848819d4082ccd6d67a88aa5cabe59bf594e14f", + "s": "0x7c0c82f62a8a5a9b0e9cf30a54a72fdae8fc54b5b79ddafef0acd30e94e83872", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionIndex": 144, + "type": "eip1559", + "typeHex": "0x2", + "v": 0n, + "value": 100000000000000000n, + "yParity": 0, + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "transaction", + { + "chainId": 1, + "hash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) diff --git a/packages/vue/src/composables/useTransaction.ts b/packages/vue/src/composables/useTransaction.ts new file mode 100644 index 0000000000..eea49e68c1 --- /dev/null +++ b/packages/vue/src/composables/useTransaction.ts @@ -0,0 +1,91 @@ +import type { + Config, + GetTransactionErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetTransactionData, + type GetTransactionOptions, + type GetTransactionQueryFnData, + type GetTransactionQueryKey, + getTransactionQueryOptions, +} from '@wagmi/core/query' + +import { computed } from 'vue' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +> = Compute< + DeepMaybeRef< + GetTransactionOptions & + ConfigParameter & + QueryParameter< + GetTransactionQueryFnData, + GetTransactionErrorType, + selectData, + GetTransactionQueryKey + > + > +> + +export type UseTransactionReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useTransaction */ +export function useTransaction< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionData, +>( + parameters_: UseTransactionParameters = {}, +): UseTransactionReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { + blockHash, + blockNumber, + blockTag, + chainId = configChainId.value, + hash, + query = {}, + } = parameters.value + const options = getTransactionQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean( + !(blockHash && blockNumber && blockTag && hash) && + (query.enabled ?? true), + ) + return { + ...query, + ...options, + enabled, + } + }) + + return useQuery(queryOptions as any) as UseTransactionReturnType< + config, + chainId, + selectData + > +} diff --git a/packages/vue/src/composables/useTransactionReceipt.test-d.ts b/packages/vue/src/composables/useTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..180d8354d8 --- /dev/null +++ b/packages/vue/src/composables/useTransactionReceipt.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useTransactionReceipt } from './useTransactionReceipt.js' + +test('select data', () => { + const result = useTransactionReceipt({ + query: { + select(data) { + return data?.blockNumber + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useTransactionReceipt.test.ts b/packages/vue/src/composables/useTransactionReceipt.test.ts new file mode 100644 index 0000000000..0092671427 --- /dev/null +++ b/packages/vue/src/composables/useTransactionReceipt.test.ts @@ -0,0 +1,208 @@ +import { chain, wait } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { ref } from 'vue' +import { deepUnref } from '../utils/cloneDeep.js' +import { useTransactionReceipt } from './useTransactionReceipt.js' + +test('default', async () => { + const [result] = renderComposable(() => + useTransactionReceipt({ + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 1, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('parameters: chainId', async () => { + const [result] = renderComposable(() => + useTransactionReceipt({ + chainId: chain.mainnet2.id, + hash: '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871', + }), + ) + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 456, + "hash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('behavior: hash: undefined -> defined', async () => { + const hash = ref() + + const [result] = renderComposable(() => + useTransactionReceipt({ + hash, + }), + ) + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') + + hash.value = + '0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871' + + await waitFor(result.isSuccess) + + expect(deepUnref(result)).toMatchInlineSnapshot(` + { + "data": { + "blockHash": "0xb932f77cf770d1d1c8f861153eec1e990f5d56b6ffdb4ac06aef3cca51ef37d4", + "blockNumber": 16280769n, + "contractAddress": null, + "cumulativeGasUsed": 21000n, + "effectiveGasPrice": 33427926161n, + "from": "0x043022ef9fca1066024d19d681e2ccf44ff90de3", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0x318a5fb4f1604fc46375a1db9a9018b6e423b345", + "transactionHash": "0xbf7d27700d053765c9638d3b9d39eb3c56bfc48377583e8be483d61f9f18a871", + "transactionIndex": 0, + "type": "legacy", + }, + "dataUpdatedAt": 1675209600000, + "error": null, + "errorUpdateCount": 0, + "errorUpdatedAt": 0, + "failureCount": 0, + "failureReason": null, + "fetchStatus": "idle", + "isError": false, + "isFetched": true, + "isFetchedAfterMount": true, + "isFetching": false, + "isInitialLoading": false, + "isLoading": false, + "isLoadingError": false, + "isPaused": false, + "isPending": false, + "isPlaceholderData": false, + "isRefetchError": false, + "isRefetching": false, + "isStale": true, + "isSuccess": true, + "queryKey": [ + "getTransactionReceipt", + { + "chainId": 1, + "hash": undefined, + }, + ], + "refetch": [Function], + "status": "success", + "suspense": [Function], + } + `) +}) + +test('behavior: disabled when properties missing', async () => { + const [result] = renderComposable(() => useTransactionReceipt()) + + await wait(100) + expect(result.fetchStatus.value).toBe('idle') +}) diff --git a/packages/vue/src/composables/useTransactionReceipt.ts b/packages/vue/src/composables/useTransactionReceipt.ts new file mode 100644 index 0000000000..cba5c232ae --- /dev/null +++ b/packages/vue/src/composables/useTransactionReceipt.ts @@ -0,0 +1,85 @@ +import type { + Config, + GetTransactionReceiptErrorType, + ResolvedRegister, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type GetTransactionReceiptData, + type GetTransactionReceiptOptions, + type GetTransactionReceiptQueryKey, + getTransactionReceiptQueryOptions, +} from '@wagmi/core/query' +import type { GetTransactionReceiptQueryFnData } from '@wagmi/core/query' + +import { computed } from 'vue' +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +> = Compute< + DeepMaybeRef< + GetTransactionReceiptOptions & + ConfigParameter & + QueryParameter< + GetTransactionReceiptQueryFnData, + GetTransactionReceiptErrorType, + selectData, + GetTransactionReceiptQueryKey + > + > +> + +export type UseTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useTransactionReceipt */ +export function useTransactionReceipt< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = GetTransactionReceiptData, +>( + parameters_: UseTransactionReceiptParameters< + config, + chainId, + selectData + > = {}, +): UseTransactionReceiptReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + const queryOptions = computed(() => { + const { chainId = configChainId.value, hash, query = {} } = parameters.value + const options = getTransactionReceiptQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(hash && (query.enabled ?? true)) + return { + ...(query as any), + ...options, + enabled, + } + }) + + return useQuery(queryOptions) as UseTransactionReceiptReturnType< + config, + chainId, + selectData + > +} diff --git a/packages/vue/src/composables/useWaitForTransactionReceipt.test-d.ts b/packages/vue/src/composables/useWaitForTransactionReceipt.test-d.ts new file mode 100644 index 0000000000..aa5dcad64f --- /dev/null +++ b/packages/vue/src/composables/useWaitForTransactionReceipt.test-d.ts @@ -0,0 +1,14 @@ +import { expectTypeOf, test } from 'vitest' + +import { useWaitForTransactionReceipt } from './useWaitForTransactionReceipt.js' + +test('select data', () => { + const result = useWaitForTransactionReceipt({ + query: { + select(data) { + return data?.blockNumber + }, + }, + }) + expectTypeOf(result.data.value).toEqualTypeOf() +}) diff --git a/packages/vue/src/composables/useWaitForTransactionReceipt.test.ts b/packages/vue/src/composables/useWaitForTransactionReceipt.test.ts new file mode 100644 index 0000000000..3cfcbe0feb --- /dev/null +++ b/packages/vue/src/composables/useWaitForTransactionReceipt.test.ts @@ -0,0 +1,46 @@ +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { wait } from '../../../test/src/utils.js' +import { useWaitForTransactionReceipt } from './useWaitForTransactionReceipt.js' + +test('default', async () => { + const [result] = renderComposable(() => + useWaitForTransactionReceipt({ + hash: '0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30', + }), + ) + + await waitFor(result.isSuccess) + + expect(result.data.value).toMatchInlineSnapshot(` + { + "blockHash": "0xd725a38b51e5ceec8c5f6c9ccfdb2cc423af993bb650af5eedca5e4be7156ba7", + "blockNumber": 15189204n, + "chainId": 1, + "contractAddress": null, + "cumulativeGasUsed": 12949744n, + "effectiveGasPrice": 9371645552n, + "from": "0xa0cf798816d4b9b9866b5330eea46a18382f251e", + "gasUsed": 21000n, + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "to": "0xd2135cfb216b74109775236e36d4b433f1df507b", + "transactionHash": "0x60668ed8c2dc110d61d945a936fcd45b8f13654e5c78481c8c825d1148c7ef30", + "transactionIndex": 144, + "type": "eip1559", + } + `) +}) + +test('disabled when hash is undefined', async () => { + const [result] = renderComposable(() => + useWaitForTransactionReceipt({ + hash: undefined, + }), + ) + + await wait(100) + + expect(result.isPending.value).toBe(true) +}) diff --git a/packages/vue/src/composables/useWaitForTransactionReceipt.ts b/packages/vue/src/composables/useWaitForTransactionReceipt.ts new file mode 100644 index 0000000000..6aea7d8e06 --- /dev/null +++ b/packages/vue/src/composables/useWaitForTransactionReceipt.ts @@ -0,0 +1,84 @@ +import type { + Config, + ResolvedRegister, + WaitForTransactionReceiptErrorType, +} from '@wagmi/core' +import type { Compute } from '@wagmi/core/internal' +import { + type WaitForTransactionReceiptData, + type WaitForTransactionReceiptOptions, + type WaitForTransactionReceiptQueryFnData, + type WaitForTransactionReceiptQueryKey, + waitForTransactionReceiptQueryOptions, +} from '@wagmi/core/query' +import { computed } from 'vue' + +import type { ConfigParameter, QueryParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { type UseQueryReturnType, useQuery } from '../utils/query.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWaitForTransactionReceiptParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = Compute< + DeepMaybeRef< + WaitForTransactionReceiptOptions & + ConfigParameter & + QueryParameter< + WaitForTransactionReceiptQueryFnData, + WaitForTransactionReceiptErrorType, + selectData, + WaitForTransactionReceiptQueryKey + > + > +> + +export type UseWaitForTransactionReceiptReturnType< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +> = UseQueryReturnType + +/** https://wagmi.sh/vue/api/composables/useWaitForTransactionReceipt */ +export function useWaitForTransactionReceipt< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], + selectData = WaitForTransactionReceiptData, +>( + parameters_: UseWaitForTransactionReceiptParameters< + config, + chainId, + selectData + > = {}, +): UseWaitForTransactionReceiptReturnType { + const parameters = computed(() => deepUnref(parameters_)) + const config = useConfig(parameters_) + const configChainId = useChainId() + + const queryOptions = computed(() => { + const { chainId = configChainId.value, hash, query = {} } = parameters.value + + const options = waitForTransactionReceiptQueryOptions(config, { + ...parameters.value, + chainId, + }) + const enabled = Boolean(hash && (query.enabled ?? true)) + + return { + ...query, + ...options, + enabled, + } + }) + + return useQuery( + queryOptions as any, + ) as UseWaitForTransactionReceiptReturnType +} diff --git a/packages/vue/src/composables/useWatchBlockNumber.test-d.ts b/packages/vue/src/composables/useWatchBlockNumber.test-d.ts new file mode 100644 index 0000000000..6ab0347168 --- /dev/null +++ b/packages/vue/src/composables/useWatchBlockNumber.test-d.ts @@ -0,0 +1,75 @@ +import { createConfig } from '@wagmi/core' +import { http, webSocket } from 'viem' +import { mainnet, optimism } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import type { DeepUnwrapRef } from '../types/ref.js' +import { + type UseWatchBlockNumberParameters, + useWatchBlockNumber, +} from './useWatchBlockNumber.js' + +test('default', () => { + useWatchBlockNumber({ + poll: false, + onBlockNumber() {}, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + type Result = DeepUnwrapRef< + UseWatchBlockNumberParameters< + typeof config, + typeof mainnet.id | typeof optimism.id + > + > + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + poll: false, + onBlockNumber() {}, + }) + + type Result2 = DeepUnwrapRef< + UseWatchBlockNumberParameters + > + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + chainId: mainnet.id, + poll: true, + onBlockNumber() {}, + }) + useWatchBlockNumber({ + config, + chainId: mainnet.id, + // @ts-expect-error + poll: false, + onBlockNumber() {}, + }) + + type Result3 = DeepUnwrapRef< + UseWatchBlockNumberParameters + > + expectTypeOf().toEqualTypeOf() + useWatchBlockNumber({ + config, + chainId: optimism.id, + poll: true, + onBlockNumber() {}, + }) + useWatchBlockNumber({ + config, + chainId: optimism.id, + poll: false, + onBlockNumber() {}, + }) +}) diff --git a/packages/vue/src/composables/useWatchBlockNumber.test.ts b/packages/vue/src/composables/useWatchBlockNumber.test.ts new file mode 100644 index 0000000000..1b074d766c --- /dev/null +++ b/packages/vue/src/composables/useWatchBlockNumber.test.ts @@ -0,0 +1,67 @@ +import { testClient, wait } from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { expect, test } from 'vitest' +import { ref } from 'vue' +import { useWatchBlockNumber } from './useWatchBlockNumber.js' + +test('default', async () => { + const blockNumbers: bigint[] = [] + renderComposable(() => + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + blockNumbers.push(blockNumber) + }, + }), + ) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blockNumbers.length).toBe(3) + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) +}) + +test('parameters: enabled', async () => { + const enabled = ref(true) + + const blockNumbers: bigint[] = [] + renderComposable(() => + useWatchBlockNumber({ + enabled, + onBlockNumber(blockNumber) { + blockNumbers.push(blockNumber) + }, + }), + ) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(blockNumbers.length).toBe(3) + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) + + enabled.value = false + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect( + blockNumbers.map((blockNumber) => blockNumber - blockNumbers[0]!), + ).toEqual([0n, 1n, 2n]) +}) diff --git a/packages/vue/src/composables/useWatchBlockNumber.ts b/packages/vue/src/composables/useWatchBlockNumber.ts new file mode 100644 index 0000000000..96dd437ec1 --- /dev/null +++ b/packages/vue/src/composables/useWatchBlockNumber.ts @@ -0,0 +1,63 @@ +import { + type Config, + type ResolvedRegister, + type WatchBlockNumberParameters, + watchBlockNumber, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import { computed, watchEffect } from 'vue' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchBlockNumberParameters< + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = DeepMaybeRef< + UnionCompute< + UnionExactPartial> & + ConfigParameter & + EnabledParameter + > +> + +export type UseWatchBlockNumberReturnType = void + +/** https://wagmi.sh/vue/api/composables/useWatchBlockNumber */ +export function useWatchBlockNumber< + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters_: UseWatchBlockNumberParameters = {} as any, +): UseWatchBlockNumberReturnType { + const parameters = computed(() => deepUnref(parameters_)) + + const config = useConfig(parameters) + const configChainId = useChainId({ config }) + + watchEffect((onCleanup) => { + const { + chainId = configChainId.value, + enabled = true, + onBlockNumber, + config: _, + ...rest + } = parameters.value + + if (!enabled) return + if (!onBlockNumber) return + + const unwatch = watchBlockNumber(config, { + ...(rest as any), + chainId, + onBlockNumber, + emitOnBegin: true, + }) + onCleanup(unwatch) + }) +} diff --git a/packages/vue/src/composables/useWatchContractEvent.test-d.ts b/packages/vue/src/composables/useWatchContractEvent.test-d.ts new file mode 100644 index 0000000000..17568222dd --- /dev/null +++ b/packages/vue/src/composables/useWatchContractEvent.test-d.ts @@ -0,0 +1,126 @@ +import { http, createConfig, webSocket } from '@wagmi/core' +import { mainnet, optimism } from '@wagmi/core/chains' +import { abi } from '@wagmi/test' +import { expectTypeOf, test } from 'vitest' + +import { useWatchContractEvent } from './useWatchContractEvent.js' + +test('default', () => { + useWatchContractEvent({ + address: '0x', + abi: abi.erc20, + eventName: 'Transfer', + poll: false, + args: { + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf<{ + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + }>() + }, + }) +}) + +test('behavior: no eventName', () => { + useWatchContractEvent({ + address: '0x', + abi: abi.erc20, + args: { + // TODO: Figure out why this is not working + // @ts-ignore + from: '0x', + to: '0x', + }, + onLogs(logs) { + expectTypeOf(logs[0]!.eventName).toEqualTypeOf<'Transfer' | 'Approval'>() + expectTypeOf(logs[0]!.args).toEqualTypeOf< + | Record + | readonly unknown[] + | { + from?: `0x${string}` | undefined + to?: `0x${string}` | undefined + value?: bigint | undefined + } + | { + owner?: `0x${string}` | undefined + spender?: `0x${string}` | undefined + value?: bigint | undefined + } + >() + }, + }) +}) + +test('differing transports', () => { + const config = createConfig({ + chains: [mainnet, optimism], + transports: { + [mainnet.id]: http(), + [optimism.id]: webSocket(), + }, + }) + + // TODO: Fix inference for `poll` (`DeepMaybeRef` wrapping `UseWatchContractEventParameters` not working as expected) + // type Result = UseWatchContractEventParameters< + // typeof abi.erc20, + // 'Transfer' | 'Approval', + // true, + // typeof config, + // typeof mainnet.id | typeof optimism.id + // > + // expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + // type Result2 = UseWatchContractEventParameters< + // typeof abi.erc20, + // 'Transfer' | 'Approval', + // true, + // typeof config, + // typeof mainnet.id + // > + // expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + chainId: mainnet.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + + // type Result3 = UseWatchContractEventParameters< + // typeof abi.erc20, + // 'Transfer' | 'Approval', + // true, + // typeof config, + // typeof optimism.id + // > + // expectTypeOf().toEqualTypeOf() + useWatchContractEvent({ + config, + chainId: optimism.id, + poll: true, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) + useWatchContractEvent({ + config, + chainId: optimism.id, + poll: false, + address: '0x', + abi: abi.erc20, + onLogs() {}, + }) +}) diff --git a/packages/vue/src/composables/useWatchContractEvent.test.ts b/packages/vue/src/composables/useWatchContractEvent.test.ts new file mode 100644 index 0000000000..968c46d0cd --- /dev/null +++ b/packages/vue/src/composables/useWatchContractEvent.test.ts @@ -0,0 +1,106 @@ +import { connect, disconnect, getBalance, writeContract } from '@wagmi/core' +import { + abi, + accounts, + address, + config, + testClient, + transactionHashRegex, + wait, +} from '@wagmi/test' +import { renderComposable } from '@wagmi/test/vue' +import { http, createWalletClient, parseEther } from 'viem' +import type { WatchEventOnLogsParameter } from 'viem/actions' +import { expect, test } from 'vitest' + +import { ref } from 'vue' +import { useWatchContractEvent } from './useWatchContractEvent.js' + +const connector = config.connectors[0]! + +test('default', async () => { + const data = await connect(config, { connector }) + const connectedAddress = data.accounts[0] + + // impersonate usdc holder account and transfer usdc to connected account + await testClient.mainnet.impersonateAccount({ address: address.usdcHolder }) + await testClient.mainnet.setBalance({ + address: address.usdcHolder, + value: 10000000000000000000000n, + }) + await createWalletClient({ + account: address.usdcHolder, + chain: testClient.mainnet.chain, + transport: http(), + }).writeContract({ + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [connectedAddress, parseEther('10', 'gwei')], + }) + await testClient.mainnet.mine({ blocks: 1 }) + await testClient.mainnet.stopImpersonatingAccount({ + address: address.usdcHolder, + }) + + const balance = await getBalance(config, { + address: connectedAddress, + token: address.usdc, + }) + expect(balance.value).toBeGreaterThan(0n) + + // start watching transfer events + let logs: WatchEventOnLogsParameter = [] + renderComposable(() => + useWatchContractEvent({ + address: address.usdc, + abi: abi.erc20, + eventName: 'Transfer', + onLogs(next) { + logs = logs.concat(next) + }, + }), + ) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[1], parseEther('1', 'gwei')], + }) + + await writeContract(config, { + address: address.usdc, + abi: abi.erc20, + functionName: 'transfer', + args: [accounts[3], parseEther('1', 'gwei')], + }) + + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + await testClient.mainnet.mine({ blocks: 1 }) + await wait(100) + + expect(logs.length).toBe(2) + expect(logs[0]?.transactionHash).toMatch(transactionHashRegex) + + await disconnect(config, { connector }) +}) + +test('parameters: enabled', async () => { + const enabled = ref(true) + + renderComposable(() => + useWatchContractEvent({ + address: address.usdc, + abi: abi.erc20, + eventName: 'Transfer', + }), + ) + + renderComposable(() => + useWatchContractEvent({ + enabled, + }), + ) +}) diff --git a/packages/vue/src/composables/useWatchContractEvent.ts b/packages/vue/src/composables/useWatchContractEvent.ts new file mode 100644 index 0000000000..ffd988b40b --- /dev/null +++ b/packages/vue/src/composables/useWatchContractEvent.ts @@ -0,0 +1,77 @@ +import { + type Config, + type ResolvedRegister, + type WatchContractEventParameters, + watchContractEvent, +} from '@wagmi/core' +import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal' +import type { Abi, ContractEventName } from 'viem' +import { computed, watchEffect } from 'vue' + +import type { ConfigParameter, EnabledParameter } from '../types/properties.js' +import type { DeepMaybeRef } from '../types/ref.js' +import { deepUnref } from '../utils/cloneDeep.js' +import { useChainId } from './useChainId.js' +import { useConfig } from './useConfig.js' + +export type UseWatchContractEventParameters< + abi extends Abi | readonly unknown[] = Abi, + eventName extends ContractEventName = ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = Config, + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +> = DeepMaybeRef< + UnionCompute< + UnionExactPartial< + WatchContractEventParameters + > & + ConfigParameter & + EnabledParameter + > +> + +export type UseWatchContractEventReturnType = void + +/** https://wagmi.sh/vue/api/composables/useWatchContractEvent */ +export function useWatchContractEvent< + const abi extends Abi | readonly unknown[], + eventName extends ContractEventName, + strict extends boolean | undefined = undefined, + config extends Config = ResolvedRegister['config'], + chainId extends + config['chains'][number]['id'] = config['chains'][number]['id'], +>( + parameters: UseWatchContractEventParameters< + abi, + eventName, + strict, + config, + chainId + > = {} as any, +): UseWatchContractEventReturnType { + const parameters_ = computed(() => deepUnref(parameters)) + + const config = useConfig(parameters_) + const configChainId = useChainId({ config }) + + watchEffect((onCleanup) => { + const { + chainId = configChainId.value, + enabled = true, + onLogs, + config: _, + ...rest + } = parameters_.value + + if (!enabled) return + if (!onLogs) return + + const unwatch = watchContractEvent(config, { + ...(rest as any), + chainId, + onLogs, + }) + onCleanup(unwatch) + }) +} diff --git a/packages/vue/src/composables/useWriteContract.test-d.ts b/packages/vue/src/composables/useWriteContract.test-d.ts new file mode 100644 index 0000000000..33a9b14342 --- /dev/null +++ b/packages/vue/src/composables/useWriteContract.test-d.ts @@ -0,0 +1,136 @@ +import type { WriteContractErrorType } from '@wagmi/core' +import { abi } from '@wagmi/test' +import type { Abi, Address, Hash } from 'viem' +import { expectTypeOf, test } from 'vitest' + +import { useSimulateContract } from './useSimulateContract.js' +import { useWriteContract } from './useWriteContract.js' + +const contextValue = { foo: 'bar' } as const + +test('context', () => { + const { + context, + data, + error, + writeContract: write, + variables, + } = useWriteContract({ + mutation: { + onMutate(variables) { + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + return contextValue + }, + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: Abi + functionName: string + args?: readonly unknown[] | undefined + }>() + }, + }, + }) + + expectTypeOf(data.value).toEqualTypeOf() + expectTypeOf(error.value).toEqualTypeOf() + expectTypeOf(variables.value).toMatchTypeOf< + { chainId?: number | undefined } | undefined + >() + expectTypeOf(context.value).toEqualTypeOf() + + write( + { + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }, + { + onError(error, variables, context) { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + onSuccess(data, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables.functionName).toEqualTypeOf<'transferFrom'>() + expectTypeOf(variables.args).toEqualTypeOf< + readonly [Address, Address, bigint] + >() + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + onSettled(data, error, variables, context) { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + + expectTypeOf(variables).toMatchTypeOf<{ + chainId?: number | undefined + abi: typeof abi.erc20 + functionName: 'transferFrom' + args: readonly [Address, Address, bigint] + }>() + }, + }, + ) +}) + +test('useSimulateContract', () => { + const { data } = useSimulateContract({ + address: '0x', + abi: abi.erc20, + functionName: 'transferFrom', + args: ['0x', '0x', 123n], + chainId: 1, + }) + const { writeContract } = useWriteContract() + + const request = data?.value?.request + if (request) writeContract(request) +}) diff --git a/packages/vue/src/composables/useWriteContract.test.ts b/packages/vue/src/composables/useWriteContract.test.ts new file mode 100644 index 0000000000..ba7c8cb4cb --- /dev/null +++ b/packages/vue/src/composables/useWriteContract.test.ts @@ -0,0 +1,25 @@ +import { connect, disconnect } from '@wagmi/core' +import { abi, address, config } from '@wagmi/test' +import { renderComposable, waitFor } from '@wagmi/test/vue' +import { expect, test } from 'vitest' + +import { useWriteContract } from './useWriteContract.js' + +const connector = config.connectors[0]! + +test('default', async () => { + await connect(config, { connector }) + + const [result] = renderComposable(() => useWriteContract()) + + result.writeContract({ + abi: abi.wagmiMintExample, + address: address.wagmiMintExample, + functionName: 'mint', + }) + await waitFor(result.isSuccess) + + expect(result.data.value).toBeDefined() + + await disconnect(config, { connector }) +}) diff --git a/packages/vue/src/composables/useWriteContract.ts b/packages/vue/src/composables/useWriteContract.ts new file mode 100644 index 0000000000..21ac3a82d4 --- /dev/null +++ b/packages/vue/src/composables/useWriteContract.ts @@ -0,0 +1,85 @@ +import { useMutation } from '@tanstack/vue-query' +import type { + Config, + ResolvedRegister, + WriteContractErrorType, +} from '@wagmi/core' +import { + type WriteContractData, + type WriteContractMutate, + type WriteContractMutateAsync, + type WriteContractVariables, + writeContractMutationOptions, +} from '@wagmi/core/query' +import type { Abi } from 'viem' + +import type { ConfigParameter } from '../types/properties.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../utils/query.js' +import { useConfig } from './useConfig.js' + +export type UseWriteContractParameters< + config extends Config = Config, + context = unknown, +> = ConfigParameter & { + mutation?: + | UseMutationParameters< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context + > + | undefined +} + +export type UseWriteContractReturnType< + config extends Config = Config, + context = unknown, +> = UseMutationReturnType< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + Abi, + string, + readonly unknown[], + config, + config['chains'][number]['id'] + >, + context +> & { + writeContract: WriteContractMutate + writeContractAsync: WriteContractMutateAsync +} + +/** https://wagmi.sh/vue/api/composables/useWriteContract */ +export function useWriteContract< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteContractParameters = {}, +): UseWriteContractReturnType { + const { mutation } = parameters + + const config = useConfig(parameters) + + const mutationOptions = writeContractMutationOptions(config) + const { mutate, mutateAsync, ...result } = useMutation({ + ...mutation, + ...mutationOptions, + }) + + type Return = UseWriteContractReturnType + return { + ...result, + writeContract: mutate as Return['writeContract'], + writeContractAsync: mutateAsync as Return['writeContractAsync'], + } +} diff --git a/packages/vue/src/errors/base.test.ts b/packages/vue/src/errors/base.test.ts new file mode 100644 index 0000000000..3457d4be1a --- /dev/null +++ b/packages/vue/src/errors/base.test.ts @@ -0,0 +1,155 @@ +import { expect, test } from 'vitest' + +import { BaseError } from './base.js' + +test('BaseError', () => { + expect(new BaseError('An error occurred.')).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Version: @wagmi/vue@x.y.z] + `) + + expect( + new BaseError('An error occurred.', { details: 'details' }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: @wagmi/vue@x.y.z] + `) + + expect(new BaseError('', { details: 'details' })).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Details: details + Version: @wagmi/vue@x.y.z] + `) +}) + +test('BaseError (w/ docsPath)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/vue/lol.html + Details: details + Version: @wagmi/vue@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error', { docsPath: '/docs' }), + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/vue/docs.html + Version: @wagmi/vue@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + cause: new BaseError('error'), + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/vue/lol.html + Version: @wagmi/vue@x.y.z] + `) + expect( + new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + docsSlug: 'test', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Docs: https://wagmi.sh/vue/lol.html#test + Details: details + Version: @wagmi/vue@x.y.z] + `) +}) + +test('BaseError (w/ metaMessages)', () => { + expect( + new BaseError('An error occurred.', { + details: 'details', + metaMessages: ['Reason: idk', 'Cause: lol'], + }), + ).toMatchInlineSnapshot(` + [WagmiError: An error occurred. + + Reason: idk + Cause: lol + + Details: details + Version: @wagmi/vue@x.y.z] + `) +}) + +test('inherited BaseError', () => { + const err = new BaseError('An error occurred.', { + details: 'details', + docsPath: '/lol', + }) + expect( + new BaseError('An internal error occurred.', { + cause: err, + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/vue/lol.html + Details: details + Version: @wagmi/vue@x.y.z] + `) +}) + +test('inherited Error', () => { + const err = new Error('details') + expect( + new BaseError('An internal error occurred.', { + cause: err, + docsPath: '/lol', + }), + ).toMatchInlineSnapshot(` + [WagmiError: An internal error occurred. + + Docs: https://wagmi.sh/vue/lol.html + Details: details + Version: @wagmi/vue@x.y.z] + `) +}) + +test('walk: no predicate fn (walks to leaf)', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk()).toMatchInlineSnapshot(` + [WagmiError: test3 + + Version: @wagmi/vue@x.y.z] + `) +}) + +test('walk: predicate fn', () => { + class FooError extends BaseError {} + class BarError extends BaseError {} + + const err = new BaseError('test1', { + cause: new FooError('test2', { cause: new BarError('test3') }), + }) + expect(err.walk((err) => err instanceof FooError)).toMatchInlineSnapshot(` + [WagmiError: test2 + + Version: @wagmi/vue@x.y.z] + `) +}) diff --git a/packages/vue/src/errors/base.ts b/packages/vue/src/errors/base.ts new file mode 100644 index 0000000000..bb65ac5fef --- /dev/null +++ b/packages/vue/src/errors/base.ts @@ -0,0 +1,14 @@ +import { BaseError as CoreError } from '@wagmi/core' + +import { getVersion } from '../utils/getVersion.js' + +export type BaseErrorType = BaseError & { name: 'WagmiError' } +export class BaseError extends CoreError { + override name = 'WagmiError' + override get docsBaseUrl() { + return 'https://wagmi.sh/vue' + } + override get version() { + return getVersion() + } +} diff --git a/packages/vue/src/errors/plugin.test.ts b/packages/vue/src/errors/plugin.test.ts new file mode 100644 index 0000000000..64ff3acda8 --- /dev/null +++ b/packages/vue/src/errors/plugin.test.ts @@ -0,0 +1,24 @@ +import { expect, test } from 'vitest' + +import { + WagmiInjectionContextError, + WagmiPluginNotFoundError, +} from './plugin.js' + +test('WagmiPluginNotFoundError', () => { + expect(new WagmiPluginNotFoundError()).toMatchInlineSnapshot(` + [WagmiPluginNotFoundError: No \`config\` found in Vue context, use \`WagmiPlugin\` to properly initialize the library. + + Docs: https://wagmi.sh/vue/api/TODO.html + Version: @wagmi/vue@x.y.z] + `) +}) + +test('WagmiInjectionContextError', () => { + expect(new WagmiInjectionContextError()).toMatchInlineSnapshot(` + [WagmiInjectionContextError: Wagmi composables can only be used inside \`setup()\` function or functions that support injection context. + + Docs: https://wagmi.sh/vue/api/TODO.html + Version: @wagmi/vue@x.y.z] + `) +}) diff --git a/packages/vue/src/errors/plugin.ts b/packages/vue/src/errors/plugin.ts new file mode 100644 index 0000000000..bed7cda47e --- /dev/null +++ b/packages/vue/src/errors/plugin.ts @@ -0,0 +1,31 @@ +import { BaseError } from './base.js' + +export type WagmiPluginNotFoundErrorType = WagmiPluginNotFoundError & { + name: 'WagmiPluginNotFoundError' +} +export class WagmiPluginNotFoundError extends BaseError { + override name = 'WagmiPluginNotFoundError' + constructor() { + super( + 'No `config` found in Vue context, use `WagmiPlugin` to properly initialize the library.', + { + docsPath: '/api/TODO', + }, + ) + } +} + +export type WagmiInjectionContextErrorType = WagmiInjectionContextError & { + name: 'WagmiInjectionContextError' +} +export class WagmiInjectionContextError extends BaseError { + override name = 'WagmiInjectionContextError' + constructor() { + super( + 'Wagmi composables can only be used inside `setup()` function or functions that support injection context.', + { + docsPath: '/api/TODO', + }, + ) + } +} diff --git a/packages/vue/src/exports/actions.test.ts b/packages/vue/src/exports/actions.test.ts new file mode 100644 index 0000000000..eaaedba14f --- /dev/null +++ b/packages/vue/src/exports/actions.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from 'vitest' + +import * as actions from './actions.js' + +test('exports', () => { + expect(Object.keys(actions)).toMatchInlineSnapshot(` + [ + "call", + "connect", + "deployContract", + "disconnect", + "estimateGas", + "estimateFeesPerGas", + "estimateMaxPriorityFeePerGas", + "getAccount", + "getBalance", + "fetchBalance", + "getBlock", + "getBlockNumber", + "fetchBlockNumber", + "getBlockTransactionCount", + "getBytecode", + "getCallsStatus", + "getCapabilities", + "getChainId", + "getChains", + "getClient", + "getConnections", + "getConnectors", + "getConnectorClient", + "getEnsAddress", + "fetchEnsAddress", + "getEnsAvatar", + "fetchEnsAvatar", + "getEnsName", + "fetchEnsName", + "getEnsResolver", + "fetchEnsResolver", + "getEnsText", + "getFeeHistory", + "getGasPrice", + "getProof", + "getPublicClient", + "getStorageAt", + "getToken", + "fetchToken", + "getTransaction", + "fetchTransaction", + "getTransactionConfirmations", + "getTransactionCount", + "getTransactionReceipt", + "getWalletClient", + "multicall", + "prepareTransactionRequest", + "readContract", + "readContracts", + "reconnect", + "sendCalls", + "sendTransaction", + "showCallsStatus", + "signMessage", + "signTypedData", + "simulateContract", + "switchAccount", + "switchChain", + "switchNetwork", + "verifyMessage", + "verifyTypedData", + "waitForCallsStatus", + "watchAccount", + "watchAsset", + "watchBlocks", + "watchBlockNumber", + "watchChainId", + "watchClient", + "watchConnections", + "watchConnectors", + "watchContractEvent", + "watchPendingTransactions", + "watchPublicClient", + "waitForTransactionReceipt", + "waitForTransaction", + "writeContract", + ] + `) +}) diff --git a/packages/vue/src/exports/actions.ts b/packages/vue/src/exports/actions.ts new file mode 100644 index 0000000000..3ff9c743c5 --- /dev/null +++ b/packages/vue/src/exports/actions.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/actions +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/actions' diff --git a/packages/vue/src/exports/actions/experimental.test.ts b/packages/vue/src/exports/actions/experimental.test.ts new file mode 100644 index 0000000000..7c4b92df8c --- /dev/null +++ b/packages/vue/src/exports/actions/experimental.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from 'vitest' + +import * as experimentalActions from './experimental.js' + +test('exports', () => { + expect(Object.keys(experimentalActions)).toMatchInlineSnapshot(` + [ + "getCallsStatus", + "getCapabilities", + "sendCalls", + "showCallsStatus", + "waitForCallsStatus", + "writeContracts", + "getCallsStatusQueryOptions", + "getCallsStatusQueryKey", + "getCapabilitiesQueryOptions", + "getCapabilitiesQueryKey", + "sendCallsMutationOptions", + "showCallsStatusMutationOptions", + "waitForCallsStatusQueryKey", + "waitForCallsStatusQueryOptions", + "writeContractsMutationOptions", + ] + `) +}) diff --git a/packages/vue/src/exports/actions/experimental.ts b/packages/vue/src/exports/actions/experimental.ts new file mode 100644 index 0000000000..6ee0334af5 --- /dev/null +++ b/packages/vue/src/exports/actions/experimental.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/experimental +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/experimental' diff --git a/packages/vue/src/exports/chains.ts b/packages/vue/src/exports/chains.ts new file mode 100644 index 0000000000..1fca7f537f --- /dev/null +++ b/packages/vue/src/exports/chains.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// viem/chains +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from 'viem/chains' diff --git a/packages/vue/src/exports/connectors.test.ts b/packages/vue/src/exports/connectors.test.ts new file mode 100644 index 0000000000..068db8227c --- /dev/null +++ b/packages/vue/src/exports/connectors.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' + +import * as connectors from './connectors.js' + +test('exports', () => { + expect(Object.keys(connectors)).toMatchInlineSnapshot(` + [ + "injected", + "mock", + "coinbaseWallet", + "metaMask", + "safe", + "walletConnect", + "version", + ] + `) +}) diff --git a/packages/vue/src/exports/connectors.ts b/packages/vue/src/exports/connectors.ts new file mode 100644 index 0000000000..e10367e318 --- /dev/null +++ b/packages/vue/src/exports/connectors.ts @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/connectors +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/connectors' diff --git a/packages/vue/src/exports/index.test.ts b/packages/vue/src/exports/index.test.ts new file mode 100644 index 0000000000..bb8fb0a1bd --- /dev/null +++ b/packages/vue/src/exports/index.test.ts @@ -0,0 +1,74 @@ +import { expect, test } from 'vitest' + +import * as vue from './index.js' + +test('exports', () => { + expect(Object.keys(vue)).toMatchInlineSnapshot(` + [ + "configKey", + "WagmiPlugin", + "BaseError", + "WagmiPluginNotFoundError", + "WagmiInjectionContextError", + "useAccount", + "useAccountEffect", + "useBalance", + "useBlockNumber", + "useBytecode", + "useChainId", + "useClient", + "useConnectorClient", + "useChains", + "useConfig", + "useConnect", + "useConnections", + "useConnectors", + "useDisconnect", + "useEnsAddress", + "useEnsAvatar", + "useEnsName", + "useEstimateGas", + "useReadContract", + "useReconnect", + "useSendTransaction", + "useSignMessage", + "useSignTypedData", + "useSimulateContract", + "useSwitchAccount", + "useSwitchChain", + "useTransaction", + "useTransactionReceipt", + "useWatchBlockNumber", + "useWatchContractEvent", + "useWaitForTransactionReceipt", + "useWriteContract", + "createConfig", + "createConnector", + "injected", + "mock", + "ChainNotConfiguredError", + "ConnectorAlreadyConnectedError", + "ConnectorNotFoundError", + "ConnectorAccountNotFoundError", + "ConnectorChainMismatchError", + "ConnectorUnavailableReconnectingError", + "ProviderNotFoundError", + "SwitchChainNotSupportedError", + "createStorage", + "noopStorage", + "custom", + "fallback", + "http", + "webSocket", + "unstable_connector", + "cookieStorage", + "cookieToInitialState", + "deepEqual", + "deserialize", + "normalizeChainId", + "parseCookie", + "serialize", + "version", + ] + `) +}) diff --git a/packages/vue/src/exports/index.ts b/packages/vue/src/exports/index.ts new file mode 100644 index 0000000000..e7267878cf --- /dev/null +++ b/packages/vue/src/exports/index.ts @@ -0,0 +1,280 @@ +//////////////////////////////////////////////////////////////////////////////// +// Plugin +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { configKey, type WagmiPluginOptions, WagmiPlugin } from '../plugin.js' + +//////////////////////////////////////////////////////////////////////////////// +// Errors +//////////////////////////////////////////////////////////////////////////////// + +export { type BaseErrorType, BaseError } from '../errors/base.js' + +export { + type WagmiPluginNotFoundErrorType, + WagmiPluginNotFoundError, + type WagmiInjectionContextErrorType, + WagmiInjectionContextError, +} from '../errors/plugin.js' + +//////////////////////////////////////////////////////////////////////////////// +// Composables +//////////////////////////////////////////////////////////////////////////////// + +export { + type UseAccountParameters, + type UseAccountReturnType, + useAccount, +} from '../composables/useAccount.js' + +export { + type UseAccountEffectParameters, + useAccountEffect, +} from '../composables/useAccountEffect.js' + +export { + type UseBalanceParameters, + type UseBalanceReturnType, + useBalance, +} from '../composables/useBalance.js' + +export { + type UseBlockNumberParameters, + type UseBlockNumberReturnType, + useBlockNumber, +} from '../composables/useBlockNumber.js' + +export { + type UseBytecodeParameters, + type UseBytecodeReturnType, + useBytecode, +} from '../composables/useBytecode.js' + +export { + type UseChainIdParameters, + type UseChainIdReturnType, + useChainId, +} from '../composables/useChainId.js' + +export { + type UseClientParameters, + type UseClientReturnType, + useClient, +} from '../composables/useClient.js' + +export { + type UseConnectorClientParameters, + type UseConnectorClientReturnType, + useConnectorClient, +} from '../composables/useConnectorClient.js' + +export { + type UseChainsParameters, + type UseChainsReturnType, + useChains, +} from '../composables/useChains.js' + +export { + type UseConfigParameters, + type UseConfigReturnType, + useConfig, +} from '../composables/useConfig.js' + +export { + type UseConnectParameters, + type UseConnectReturnType, + useConnect, +} from '../composables/useConnect.js' + +export { + type UseConnectionsParameters, + type UseConnectionsReturnType, + useConnections, +} from '../composables/useConnections.js' + +export { + type UseConnectorsParameters, + type UseConnectorsReturnType, + useConnectors, +} from '../composables/useConnectors.js' + +export { + type UseDisconnectParameters, + type UseDisconnectReturnType, + useDisconnect, +} from '../composables/useDisconnect.js' + +export { + type UseEnsAddressParameters, + type UseEnsAddressReturnType, + useEnsAddress, +} from '../composables/useEnsAddress.js' + +export { + type UseEnsAvatarParameters, + type UseEnsAvatarReturnType, + useEnsAvatar, +} from '../composables/useEnsAvatar.js' + +export { + type UseEnsNameParameters, + type UseEnsNameReturnType, + useEnsName, +} from '../composables/useEnsName.js' + +export { + type UseEstimateGasParameters, + type UseEstimateGasReturnType, + useEstimateGas, +} from '../composables/useEstimateGas.js' + +export { + type UseReadContractParameters, + type UseReadContractReturnType, + useReadContract, +} from '../composables/useReadContract.js' + +export { + type UseReconnectParameters, + type UseReconnectReturnType, + useReconnect, +} from '../composables/useReconnect.js' + +export { + type UseSendTransactionParameters, + type UseSendTransactionReturnType, + useSendTransaction, +} from '../composables/useSendTransaction.js' + +export { + type UseSignMessageParameters, + type UseSignMessageReturnType, + useSignMessage, +} from '../composables/useSignMessage.js' + +export { + type UseSignTypedDataParameters, + type UseSignTypedDataReturnType, + useSignTypedData, +} from '../composables/useSignTypedData.js' + +export { + type UseSimulateContractParameters, + type UseSimulateContractReturnType, + useSimulateContract, +} from '../composables/useSimulateContract.js' + +export { + type UseSwitchAccountParameters, + type UseSwitchAccountReturnType, + useSwitchAccount, +} from '../composables/useSwitchAccount.js' + +export { + type UseSwitchChainParameters, + type UseSwitchChainReturnType, + useSwitchChain, +} from '../composables/useSwitchChain.js' + +export { + type UseTransactionParameters, + type UseTransactionReturnType, + useTransaction, +} from '../composables/useTransaction.js' + +export { + type UseTransactionReceiptParameters, + type UseTransactionReceiptReturnType, + useTransactionReceipt, +} from '../composables/useTransactionReceipt.js' + +export { + type UseWatchBlockNumberParameters, + type UseWatchBlockNumberReturnType, + useWatchBlockNumber, +} from '../composables/useWatchBlockNumber.js' + +export { + type UseWatchContractEventParameters, + type UseWatchContractEventReturnType, + useWatchContractEvent, +} from '../composables/useWatchContractEvent.js' + +export { + type UseWaitForTransactionReceiptParameters, + type UseWaitForTransactionReceiptReturnType, + useWaitForTransactionReceipt, +} from '../composables/useWaitForTransactionReceipt.js' + +export { + type UseWriteContractParameters, + type UseWriteContractReturnType, + useWriteContract, +} from '../composables/useWriteContract.js' + +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core +//////////////////////////////////////////////////////////////////////////////// + +export { + // Config + type Connection, + type Connector, + type Config, + type CreateConfigParameters, + type PartializedState, + type State, + createConfig, + // Connector + type ConnectorEventMap, + type CreateConnectorFn, + createConnector, + injected, + mock, + // Errors + type ChainNotConfiguredErrorType, + ChainNotConfiguredError, + type ConnectorAlreadyConnectedErrorType, + ConnectorAlreadyConnectedError, + type ConnectorNotFoundErrorType, + ConnectorNotFoundError, + type ConnectorAccountNotFoundErrorType, + ConnectorAccountNotFoundError, + type ConnectorChainMismatchErrorType, + ConnectorChainMismatchError, + type ConnectorUnavailableReconnectingErrorType, + ConnectorUnavailableReconnectingError, + type ProviderNotFoundErrorType, + ProviderNotFoundError, + type SwitchChainNotSupportedErrorType, + SwitchChainNotSupportedError, + // Storage + type CreateStorageParameters, + type Storage, + createStorage, + noopStorage, + // Transports + custom, + fallback, + http, + webSocket, + unstable_connector, + // Types + type Register, + type ResolvedRegister, + // Utilities + cookieStorage, + cookieToInitialState, + deepEqual, + deserialize, + normalizeChainId, + parseCookie, + serialize, +} from '@wagmi/core' + +//////////////////////////////////////////////////////////////////////////////// +// Version +//////////////////////////////////////////////////////////////////////////////// + +export { version } from '../version.js' diff --git a/packages/vue/src/exports/nuxt.test.ts b/packages/vue/src/exports/nuxt.test.ts new file mode 100644 index 0000000000..7e5ecf9588 --- /dev/null +++ b/packages/vue/src/exports/nuxt.test.ts @@ -0,0 +1,11 @@ +import { expect, test } from 'vitest' + +import * as nuxt from './nuxt.js' + +test('exports', () => { + expect(Object.keys(nuxt)).toMatchInlineSnapshot(` + [ + "default", + ] + `) +}) diff --git a/packages/vue/src/exports/nuxt.ts b/packages/vue/src/exports/nuxt.ts new file mode 100644 index 0000000000..e3e2e184a3 --- /dev/null +++ b/packages/vue/src/exports/nuxt.ts @@ -0,0 +1,4 @@ +import { wagmiModule } from '../nuxt/module.js' + +export type { WagmiModuleOptions } from '../nuxt/module.js' +export default wagmiModule diff --git a/packages/vue/src/exports/query.test.ts b/packages/vue/src/exports/query.test.ts new file mode 100644 index 0000000000..758d45c907 --- /dev/null +++ b/packages/vue/src/exports/query.test.ts @@ -0,0 +1,99 @@ +import { expect, test } from 'vitest' + +import * as query from './query.js' + +test('exports', () => { + expect(Object.keys(query)).toMatchInlineSnapshot(` + [ + "callQueryKey", + "callQueryOptions", + "connectMutationOptions", + "deployContractMutationOptions", + "disconnectMutationOptions", + "estimateFeesPerGasQueryKey", + "estimateFeesPerGasQueryOptions", + "estimateGasQueryKey", + "estimateGasQueryOptions", + "estimateMaxPriorityFeePerGasQueryKey", + "estimateMaxPriorityFeePerGasQueryOptions", + "getBalanceQueryKey", + "getBalanceQueryOptions", + "getBlockQueryKey", + "getBlockQueryOptions", + "getBlockNumberQueryKey", + "getBlockNumberQueryOptions", + "getBlockTransactionCountQueryKey", + "getBlockTransactionCountQueryOptions", + "getBytecodeQueryKey", + "getBytecodeQueryOptions", + "getCallsStatusQueryKey", + "getCallsStatusQueryOptions", + "getCapabilitiesQueryKey", + "getCapabilitiesQueryOptions", + "getConnectorClientQueryKey", + "getConnectorClientQueryOptions", + "getEnsAddressQueryKey", + "getEnsAddressQueryOptions", + "getEnsAvatarQueryKey", + "getEnsAvatarQueryOptions", + "getEnsNameQueryKey", + "getEnsNameQueryOptions", + "getEnsResolverQueryKey", + "getEnsResolverQueryOptions", + "getEnsTextQueryKey", + "getEnsTextQueryOptions", + "getFeeHistoryQueryKey", + "getFeeHistoryQueryOptions", + "getGasPriceQueryKey", + "getGasPriceQueryOptions", + "getProofQueryKey", + "getProofQueryOptions", + "getStorageAtQueryKey", + "getStorageAtQueryOptions", + "getTokenQueryKey", + "getTokenQueryOptions", + "getTransactionQueryKey", + "getTransactionQueryOptions", + "getTransactionConfirmationsQueryKey", + "getTransactionConfirmationsQueryOptions", + "getTransactionCountQueryKey", + "getTransactionCountQueryOptions", + "getTransactionReceiptQueryKey", + "getTransactionReceiptQueryOptions", + "getWalletClientQueryKey", + "getWalletClientQueryOptions", + "infiniteReadContractsQueryKey", + "infiniteReadContractsQueryOptions", + "prepareTransactionRequestQueryKey", + "prepareTransactionRequestQueryOptions", + "readContractQueryKey", + "readContractQueryOptions", + "readContractsQueryKey", + "readContractsQueryOptions", + "reconnectMutationOptions", + "sendCallsMutationOptions", + "showCallsStatusMutationOptions", + "sendTransactionMutationOptions", + "signMessageMutationOptions", + "signTypedDataMutationOptions", + "switchAccountMutationOptions", + "simulateContractQueryKey", + "simulateContractQueryOptions", + "switchChainMutationOptions", + "verifyMessageQueryKey", + "verifyMessageQueryOptions", + "verifyTypedDataQueryKey", + "verifyTypedDataQueryOptions", + "waitForCallsStatusQueryKey", + "waitForCallsStatusQueryOptions", + "waitForTransactionReceiptQueryKey", + "waitForTransactionReceiptQueryOptions", + "watchAssetMutationOptions", + "writeContractMutationOptions", + "hashFn", + "structuralSharing", + "useMutation", + "useQuery", + ] + `) +}) diff --git a/packages/vue/src/exports/query.ts b/packages/vue/src/exports/query.ts new file mode 100644 index 0000000000..cd9169c0e7 --- /dev/null +++ b/packages/vue/src/exports/query.ts @@ -0,0 +1,19 @@ +//////////////////////////////////////////////////////////////////////////////// +// @wagmi/core/query +//////////////////////////////////////////////////////////////////////////////// + +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '@wagmi/core/query' + +export { + type UseMutationParameters, + type UseMutationReturnType, + useMutation, +} from '../utils/query.js' + +export { + type UseQueryParameters, + type UseQueryReturnType, + useQuery, +} from '../utils/query.js' diff --git a/packages/vue/src/nuxt/module.ts b/packages/vue/src/nuxt/module.ts new file mode 100644 index 0000000000..452dcefac4 --- /dev/null +++ b/packages/vue/src/nuxt/module.ts @@ -0,0 +1,60 @@ +import type { NuxtModule } from '@nuxt/schema' +import { addImports, createResolver, defineNuxtModule } from 'nuxt/kit' + +// biome-ignore lint/complexity/noBannedTypes: +export type WagmiModuleOptions = {} + +export const wagmiModule: NuxtModule = + defineNuxtModule({ + meta: { + name: '@wagmi/vue', + configKey: 'wagmi', + compatibility: { + nuxt: '^3.0.0', + }, + }, + setup(_options, nuxt) { + const { resolve } = createResolver(import.meta.url) + + // Add types + nuxt.hook('prepare:types', ({ references }) => { + references.push({ types: '@wagmi/vue/nuxt' }) + }) + + // Add auto imports + const composables = resolve('./runtime/composables') + const names = [ + 'useAccount', + 'useAccountEffect', + 'useBalance', + 'useBlockNumber', + 'useChainId', + 'useChains', + 'useClient', + 'useConfig', + 'useConnect', + 'useConnections', + 'useConnectorClient', + 'useConnectors', + 'useDisconnect', + 'useEnsAddress', + 'useEnsAvatar', + 'useEnsName', + 'useEstimateGas', + 'useReadContract', + 'useReconnect', + 'useSendTransaction', + 'useSignMessage', + 'useSignTypedData', + 'useSimulateContract', + 'useSwitchAccount', + 'useSwitchChain', + 'useTransaction', + 'useTransactionReceipt', + 'useWaitForTransactionReceipt', + 'useWatchBlockNumber', + 'useWriteContract', + ] + addImports(names.map((name) => ({ from: composables, name }))) + }, + }) diff --git a/packages/vue/src/nuxt/runtime/composables.ts b/packages/vue/src/nuxt/runtime/composables.ts new file mode 100644 index 0000000000..2340689357 --- /dev/null +++ b/packages/vue/src/nuxt/runtime/composables.ts @@ -0,0 +1,3 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +// biome-ignore lint/performance/noReExportAll: entrypoint module +export * from '../../exports/index.js' diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts new file mode 100644 index 0000000000..ae0d7f919a --- /dev/null +++ b/packages/vue/src/plugin.ts @@ -0,0 +1,22 @@ +import { type ResolvedRegister, type State, hydrate } from '@wagmi/core' +import type { Plugin } from 'vue' + +export const configKey = Symbol() + +export type WagmiPluginOptions = { + config: ResolvedRegister['config'] + initialState?: State | undefined + reconnectOnMount?: boolean | undefined +} + +export const WagmiPlugin = { + install(app, options) { + const { config, reconnectOnMount = true } = options + app.provide(configKey, config) + // TODO: check this works in SSR env. + // - reconnect on mount. + // - hydrate initial state. + const { onMount } = hydrate(config, { ...options, reconnectOnMount }) + onMount() + }, +} satisfies Plugin diff --git a/packages/vue/src/types/properties.ts b/packages/vue/src/types/properties.ts new file mode 100644 index 0000000000..e09fdb0158 --- /dev/null +++ b/packages/vue/src/types/properties.ts @@ -0,0 +1,27 @@ +import type { DefaultError, QueryKey } from '@tanstack/vue-query' +import type { Config } from '@wagmi/core' +import type { MaybeRef } from 'vue' +import type { UseQueryParameters } from '../utils/query.js' +import type { DeepUnwrapRef } from './ref.js' + +export type ConfigParameter = { + config?: Config | config | undefined +} + +export type EnabledParameter = { + enabled?: MaybeRef | undefined +} + +export type QueryParameter< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = { + query?: + | Omit< + DeepUnwrapRef>, + 'queryFn' | 'queryHash' | 'queryKey' | 'queryKeyHashFn' | 'throwOnError' + > + | undefined +} diff --git a/packages/vue/src/types/ref.ts b/packages/vue/src/types/ref.ts new file mode 100644 index 0000000000..d9cd8120ec --- /dev/null +++ b/packages/vue/src/types/ref.ts @@ -0,0 +1,39 @@ +// Credit: https://github.com/TanStack/query/blob/01ce023826b81e6c41e354f27691f65c9725af67/packages/vue-query/src/types.ts + +import type { Config, Connector } from '@wagmi/core' +import type { MaybeRef, Ref, UnwrapRef } from 'vue' + +type Primitive = string | number | boolean | bigint | symbol | undefined | null +type UnwrapLeaf = + | Primitive + // biome-ignore lint/complexity/noBannedTypes: we need to support all types + | Function + | Date + | Error + | RegExp + | Map + | WeakMap + | Set + | WeakSet + +export type DeepMaybeRef = MaybeRef< + // biome-ignore lint/complexity/noBannedTypes: + value extends Function | Config | Connector + ? value + : value extends object | any[] + ? { + [key in keyof value]: DeepMaybeRef + } + : value +> + +export type DeepUnwrapRef = T extends UnwrapLeaf + ? T + : T extends Ref + ? DeepUnwrapRef + : // biome-ignore lint/complexity/noBannedTypes: + T extends {} + ? { + [Property in keyof T]: DeepUnwrapRef + } + : UnwrapRef diff --git a/packages/vue/src/utils/cloneDeep.ts b/packages/vue/src/utils/cloneDeep.ts new file mode 100644 index 0000000000..0bbf48deb8 --- /dev/null +++ b/packages/vue/src/utils/cloneDeep.ts @@ -0,0 +1,44 @@ +// Credit: https://github.com/TanStack/query/blob/01ce023826b81e6c41e354f27691f65c9725af67/packages/vue-query/src/utils.ts + +import { isRef, unref } from 'vue' + +import type { DeepMaybeRef, DeepUnwrapRef } from '../types/ref.js' + +function cloneDeep( + value: DeepMaybeRef, + customize?: (val: DeepMaybeRef) => value | undefined, +): value { + if (customize) { + const result = customize(value) + // If it's a ref of undefined, return undefined + if (result === undefined && isRef(value)) return result as value + if (result !== undefined) return result + } + + if (Array.isArray(value)) + return value.map((val) => cloneDeep(val, customize)) as unknown as value + + if (typeof value === 'object' && isPlainObject(value)) { + const entries = Object.entries(value).map(([key, val]) => [ + key, + cloneDeep(val, customize), + ]) + return Object.fromEntries(entries) + } + + return value as value +} + +export function deepUnref(value: value): DeepUnwrapRef { + return cloneDeep(value as any, (val) => { + if (isRef(val)) return deepUnref(unref(val)) + return undefined + }) +} + +// biome-ignore lint/complexity/noBannedTypes: +function isPlainObject(value: unknown): value is Object { + if (Object.prototype.toString.call(value) !== '[object Object]') return false + const prototype = Object.getPrototypeOf(value) + return prototype === null || prototype === Object.prototype +} diff --git a/packages/vue/src/utils/getVersion.test.ts b/packages/vue/src/utils/getVersion.test.ts new file mode 100644 index 0000000000..a2ae6fb1c2 --- /dev/null +++ b/packages/vue/src/utils/getVersion.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from 'vitest' + +import { getVersion } from './getVersion.js' + +test('default', () => { + expect(getVersion()).toMatchInlineSnapshot(`"@wagmi/vue@x.y.z"`) +}) diff --git a/packages/vue/src/utils/getVersion.ts b/packages/vue/src/utils/getVersion.ts new file mode 100644 index 0000000000..1c33c5302a --- /dev/null +++ b/packages/vue/src/utils/getVersion.ts @@ -0,0 +1,3 @@ +import { version } from '../version.js' + +export const getVersion = () => `@wagmi/vue@${version}` diff --git a/packages/vue/src/utils/query.ts b/packages/vue/src/utils/query.ts new file mode 100644 index 0000000000..0ea82af9b7 --- /dev/null +++ b/packages/vue/src/utils/query.ts @@ -0,0 +1,161 @@ +import { + type DefaultError, + type MutationObserverOptions, + type QueryKey, + type UseQueryOptions, + type UseMutationReturnType as tanstack_UseMutationReturnType, + type UseQueryReturnType as tanstack_UseQueryReturnType, + useQuery as tanstack_useQuery, + useMutation, +} from '@tanstack/vue-query' +import type { + Compute, + ExactPartial, + Omit, + UnionStrictOmit, +} from '@wagmi/core/internal' +import { hashFn } from '@wagmi/core/query' +import { type MaybeRef, computed, unref } from 'vue' + +import type { DeepMaybeRef, DeepUnwrapRef } from '../types/ref.js' + +export type UseMutationParameters< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + DeepMaybeRef< + Omit< + DeepUnwrapRef< + MutationObserverOptions, context> + >, + 'mutationFn' | 'mutationKey' | 'throwOnError' + > + > +> + +export type UseMutationReturnType< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Compute< + UnionStrictOmit< + tanstack_UseMutationReturnType, + 'mutate' | 'mutateAsync' + > +> + +export { useMutation } + +//////////////////////////////////////////////////////////////////////////////// + +export type UseQueryParameters< + queryFnData = unknown, + error = DefaultError, + data = queryFnData, + queryKey extends QueryKey = QueryKey, +> = Compute< + DeepMaybeRef< + ExactPartial< + Omit< + DeepUnwrapRef< + UseQueryOptions + >, + 'initialData' + > + > & { + // Fix `initialData` type + initialData?: + | DeepUnwrapRef< + UseQueryOptions + >['initialData'] + | undefined + } + > +> + +export type UseQueryReturnType = Compute< + tanstack_UseQueryReturnType & { + queryKey: QueryKey + } +> + +// Adding some basic customization. +// Ideally we don't have this function, but `import('@tanstack/vue-query').useQuery` currently has some quirks where it is super hard to +// pass down the inferred `initialData` type because of it's discriminated overload in the on `useQuery`. +export function useQuery( + parameters: MaybeRef< + UseQueryParameters & { + queryKey: QueryKey + } + >, +): UseQueryReturnType { + const options = computed(() => ({ + ...(unref(parameters) as any), + queryKeyHashFn: hashFn, + })) + const result = tanstack_useQuery(options) as UseQueryReturnType + result.queryKey = unref(options).queryKey as QueryKey + return result +} + +//////////////////////////////////////////////////////////////////////////////// + +// export type UseInfiniteQueryParameters< +// queryFnData = unknown, +// error = DefaultError, +// data = queryFnData, +// queryData = queryFnData, +// queryKey extends QueryKey = QueryKey, +// pageParam = unknown, +// > = Compute< +// Omit< +// UseInfiniteQueryOptions< +// queryFnData, +// error, +// data, +// queryData, +// queryKey, +// pageParam +// >, +// 'initialData' +// > & { +// // Fix `initialData` type +// initialData?: +// | UseInfiniteQueryOptions< +// queryFnData, +// error, +// data, +// queryKey +// >['initialData'] +// | undefined +// } +// > + +// export type UseInfiniteQueryReturnType< +// data = unknown, +// error = DefaultError, +// > = import('@tanstack/vue-query').UseInfiniteQueryReturnType & { +// queryKey: QueryKey +// } + +// // Adding some basic customization. +// export function useInfiniteQuery< +// queryFnData, +// error, +// data, +// queryKey extends QueryKey, +// >( +// parameters: UseInfiniteQueryParameters & { +// queryKey: QueryKey +// }, +// ): UseInfiniteQueryReturnType { +// const result = tanstack_useInfiniteQuery({ +// ...(parameters as any), +// queryKeyHashFn: hashFn, // for bigint support +// }) as UseInfiniteQueryReturnType +// result.queryKey = parameters.queryKey +// return result +// } diff --git a/packages/vue/src/utils/updateState.ts b/packages/vue/src/utils/updateState.ts new file mode 100644 index 0000000000..bcd53872d5 --- /dev/null +++ b/packages/vue/src/utils/updateState.ts @@ -0,0 +1,10 @@ +// Credit: https://github.com/TanStack/query/blob/01ce023826b81e6c41e354f27691f65c9725af67/packages/vue-query/src/utils.ts#L11-L18 + +export function updateState( + state: Record, + update: Record, +): void { + for (const key of Object.keys(state)) { + state[key] = update[key] + } +} diff --git a/packages/vue/src/version.ts b/packages/vue/src/version.ts new file mode 100644 index 0000000000..4a292eed46 --- /dev/null +++ b/packages/vue/src/version.ts @@ -0,0 +1 @@ +export const version = '0.1.20' diff --git a/packages/vue/test/setup.ts b/packages/vue/test/setup.ts new file mode 100644 index 0000000000..5c0dcc071d --- /dev/null +++ b/packages/vue/test/setup.ts @@ -0,0 +1,8 @@ +import { vi } from 'vitest' + +// Make dates stable across runs +Date.now = vi.fn(() => new Date(Date.UTC(2023, 1, 1)).valueOf()) + +vi.mock('../src/version.ts', () => { + return { version: 'x.y.z' } +}) diff --git a/packages/vue/tsconfig.build.json b/packages/vue/tsconfig.build.json new file mode 100644 index 0000000000..fbed2b1036 --- /dev/null +++ b/packages/vue/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts", "src/**/*.test-d.ts"], + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 0000000000..bacbc9228c --- /dev/null +++ b/packages/vue/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.build.json", + "include": ["src/**/*.ts", "test/**/*.ts"], + "exclude": [] +} diff --git a/packages/wallet/core/CHANGELOG.md b/packages/wallet/core/CHANGELOG.md deleted file mode 100644 index 23fc45fe44..0000000000 --- a/packages/wallet/core/CHANGELOG.md +++ /dev/null @@ -1,289 +0,0 @@ -# @0xsequence/wallet-core - -## 3.0.5 - -### Patch Changes - -- Account federation support -- Updated dependencies - - @0xsequence/guard@3.0.5 - - @0xsequence/relayer@3.0.5 - - @0xsequence/wallet-primitives@3.0.5 - -## 3.0.4 - -### Patch Changes - -- id-token login support -- Updated dependencies - - @0xsequence/guard@3.0.4 - - @0xsequence/relayer@3.0.4 - - @0xsequence/wallet-primitives@3.0.4 - -## 3.0.3 - -### Patch Changes - -- 3.0.3 -- Updated dependencies - - @0xsequence/guard@3.0.3 - - @0xsequence/relayer@3.0.3 - - @0xsequence/wallet-primitives@3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer -- Updated dependencies - - @0xsequence/guard@3.0.2 - - @0xsequence/relayer@3.0.2 - - @0xsequence/wallet-primitives@3.0.2 - -## 3.0.1 - -### Patch Changes - -- Network and session fixes -- Updated dependencies - - @0xsequence/guard@3.0.1 - - @0xsequence/relayer@3.0.1 - - @0xsequence/wallet-primitives@3.0.1 - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade -- Updated dependencies [f68be62] -- Updated dependencies [49d8a2f] -- Updated dependencies [3411232] -- Updated dependencies [23cb9e9] -- Updated dependencies [f5f6a7a] -- Updated dependencies [e7de3b1] -- Updated dependencies [493836f] -- Updated dependencies [30e1f1a] -- Updated dependencies [d5017e8] -- Updated dependencies [24a5fab] -- Updated dependencies [e5e1a03] -- Updated dependencies [0b63113] -- Updated dependencies [a89134a] -- Updated dependencies [7c6c811] -- Updated dependencies -- Updated dependencies [98ce38b] -- Updated dependencies [747e6b5] -- Updated dependencies [40c19ff] -- Updated dependencies [6d5de25] -- Updated dependencies [934acd1] - - @0xsequence/guard@3.0.0 - - @0xsequence/relayer@3.0.0 - - @0xsequence/wallet-primitives@3.0.0 - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.19 - - @0xsequence/relayer@3.0.0-beta.19 - - @0xsequence/wallet-primitives@3.0.0-beta.19 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.18 - - @0xsequence/relayer@3.0.0-beta.18 - - @0xsequence/wallet-primitives@3.0.0-beta.18 - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.17 - - @0xsequence/relayer@3.0.0-beta.17 - - @0xsequence/wallet-primitives@3.0.0-beta.17 - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.16 - - @0xsequence/relayer@3.0.0-beta.16 - - @0xsequence/wallet-primitives@3.0.0-beta.16 - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.15 - - @0xsequence/relayer@3.0.0-beta.15 - - @0xsequence/wallet-primitives@3.0.0-beta.15 - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.14 - - @0xsequence/relayer@3.0.0-beta.14 - - @0xsequence/wallet-primitives@3.0.0-beta.14 - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.13 - - @0xsequence/relayer@3.0.0-beta.13 - - @0xsequence/wallet-primitives@3.0.0-beta.13 - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.12 - - @0xsequence/relayer@3.0.0-beta.12 - - @0xsequence/wallet-primitives@3.0.0-beta.12 - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.11 - - @0xsequence/relayer@3.0.0-beta.11 - - @0xsequence/wallet-primitives@3.0.0-beta.11 - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.10 - - @0xsequence/relayer@3.0.0-beta.10 - - @0xsequence/wallet-primitives@3.0.0-beta.10 - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.9 - - @0xsequence/relayer@3.0.0-beta.9 - - @0xsequence/wallet-primitives@3.0.0-beta.9 - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.8 - - @0xsequence/relayer@3.0.0-beta.8 - - @0xsequence/wallet-primitives@3.0.0-beta.8 - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.7 - - @0xsequence/relayer@3.0.0-beta.7 - - @0xsequence/wallet-primitives@3.0.0-beta.7 - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.6 - - @0xsequence/relayer@3.0.0-beta.6 - - @0xsequence/wallet-primitives@3.0.0-beta.6 - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.5 - - @0xsequence/relayer@3.0.0-beta.5 - - @0xsequence/wallet-primitives@3.0.0-beta.5 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.4 - - @0xsequence/relayer@3.0.0-beta.4 - - @0xsequence/wallet-primitives@3.0.0-beta.4 - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.3 - - @0xsequence/relayer@3.0.0-beta.3 - - @0xsequence/wallet-primitives@3.0.0-beta.3 - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.2 - - @0xsequence/relayer@3.0.0-beta.2 - - @0xsequence/wallet-primitives@3.0.0-beta.2 - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.1 - - @0xsequence/relayer@3.0.0-beta.1 - - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/core/eslint.config.js b/packages/wallet/core/eslint.config.js deleted file mode 100644 index d10bbd1e97..0000000000 --- a/packages/wallet/core/eslint.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { config as baseConfig } from '@repo/eslint-config/base' - -/** @type {import("eslint").Linter.Config} */ -export default [ - ...baseConfig, - { - // files: ['**/*.{test,spec}.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - }, -] diff --git a/packages/wallet/core/package.json b/packages/wallet/core/package.json deleted file mode 100644 index 4133157861..0000000000 --- a/packages/wallet/core/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@0xsequence/wallet-core", - "version": "3.0.5", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@vitest/coverage-v8": "^4.0.18", - "dotenv": "^17.3.1", - "fake-indexeddb": "^6.2.5", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "@0xsequence/guard": "workspace:^", - "@0xsequence/relayer": "workspace:^", - "@0xsequence/wallet-primitives": "workspace:^", - "mipd": "^0.0.7", - "ox": "^0.9.17", - "viem": "^2.40.3" - } -} diff --git a/packages/wallet/core/src/bundler/bundler.ts b/packages/wallet/core/src/bundler/bundler.ts deleted file mode 100644 index baa473b817..0000000000 --- a/packages/wallet/core/src/bundler/bundler.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { UserOperation } from 'ox/erc4337' -import { Relayer } from '@0xsequence/relayer' - -export interface Bundler { - kind: 'bundler' - - id: string - - estimateLimits( - wallet: Address.Address, - payload: Payload.Calls4337_07, - ): Promise<{ speed?: 'slow' | 'standard' | 'fast'; payload: Payload.Calls4337_07 }[]> - relay(entrypoint: Address.Address, userOperation: UserOperation.RpcV07): Promise<{ opHash: Hex.Hex }> - status(opHash: Hex.Hex, chainId: number): Promise - - isAvailable(entrypoint: Address.Address, chainId: number): Promise -} - -export function isBundler(relayer: any): relayer is Bundler { - return 'estimateLimits' in relayer && 'relay' in relayer && 'isAvailable' in relayer -} diff --git a/packages/wallet/core/src/bundler/bundlers/index.ts b/packages/wallet/core/src/bundler/bundlers/index.ts deleted file mode 100644 index b2a53a17e2..0000000000 --- a/packages/wallet/core/src/bundler/bundlers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './pimlico.js' diff --git a/packages/wallet/core/src/bundler/bundlers/pimlico.ts b/packages/wallet/core/src/bundler/bundlers/pimlico.ts deleted file mode 100644 index 4837babee0..0000000000 --- a/packages/wallet/core/src/bundler/bundlers/pimlico.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Payload } from '@0xsequence/wallet-primitives' -import { Bundler } from '../bundler.js' -import { Provider, Hex, Address, RpcTransport } from 'ox' -import { UserOperation } from 'ox/erc4337' -import { Relayer } from '@0xsequence/relayer' - -type FeePerGasPair = { - maxFeePerGas: Hex.Hex | bigint - maxPriorityFeePerGas: Hex.Hex | bigint -} - -type PimlicoGasPrice = { - slow: FeePerGasPair - standard: FeePerGasPair - fast: FeePerGasPair -} - -export class PimlicoBundler implements Bundler { - public readonly kind = 'bundler' - public readonly id: string - - public readonly provider: Provider.Provider - public readonly bundlerRpcUrl: string - private readonly fetcher: typeof fetch - - constructor(bundlerRpcUrl: string, provider: Provider.Provider | string, fetcher?: typeof fetch) { - this.id = `pimlico-erc4337-${bundlerRpcUrl}` - this.provider = typeof provider === 'string' ? Provider.from(RpcTransport.fromHttp(provider)) : provider - this.bundlerRpcUrl = bundlerRpcUrl - const resolvedFetch = fetcher ?? (globalThis as any).fetch - if (!resolvedFetch) { - throw new Error('fetch is not available') - } - this.fetcher = resolvedFetch - } - - async isAvailable(entrypoint: Address.Address, chainId: number): Promise { - const [bundlerChainId, supportedEntryPoints] = await Promise.all([ - this.bundlerRpc('eth_chainId', []), - this.bundlerRpc('eth_supportedEntryPoints', []), - ]) - - if (chainId !== Number(bundlerChainId)) { - return false - } - - return supportedEntryPoints.some((ep) => Address.isEqual(ep, entrypoint)) - } - - async relay(entrypoint: Address.Address, userOperation: UserOperation.RpcV07): Promise<{ opHash: Hex.Hex }> { - const status = await this.bundlerRpc('eth_sendUserOperation', [userOperation, entrypoint]) - return { opHash: status } - } - - async estimateLimits( - wallet: Address.Address, - payload: Payload.Calls4337_07, - ): Promise< - { - speed?: 'slow' | 'standard' | 'fast' - payload: Payload.Calls4337_07 - }[] - > { - const gasPrice = await this.bundlerRpc('pimlico_getUserOperationGasPrice', []) - - const dummyOp = Payload.to4337UserOperation(payload, wallet, '0x000010000000000000000000000000000000000000000000') - const rpcOp = UserOperation.toRpc(dummyOp) - const est = await this.bundlerRpc('eth_estimateUserOperationGas', [rpcOp, payload.entrypoint]) - - const estimatedFields = { - callGasLimit: BigInt(est.callGasLimit), - verificationGasLimit: BigInt(est.verificationGasLimit), - preVerificationGas: BigInt(est.preVerificationGas), - paymasterVerificationGasLimit: est.paymasterVerificationGasLimit - ? BigInt(est.paymasterVerificationGasLimit) - : payload.paymasterVerificationGasLimit, - paymasterPostOpGasLimit: est.paymasterPostOpGasLimit - ? BigInt(est.paymasterPostOpGasLimit) - : payload.paymasterPostOpGasLimit, - } - - const passthroughOptions = - payload.maxFeePerGas > 0n || payload.maxPriorityFeePerGas > 0n - ? [this.createEstimateLimitVariation(payload, estimatedFields, undefined, gasPrice.standard)] - : [] - - return [ - ...passthroughOptions, - this.createEstimateLimitVariation(payload, estimatedFields, 'slow', gasPrice.slow), - this.createEstimateLimitVariation(payload, estimatedFields, 'standard', gasPrice.standard), - this.createEstimateLimitVariation(payload, estimatedFields, 'fast', gasPrice.fast), - ] - } - - private createEstimateLimitVariation( - payload: Payload.Calls4337_07, - estimatedFields: any, - speed?: 'slow' | 'standard' | 'fast', - feePerGasPair?: FeePerGasPair, - ) { - return { - speed, - payload: { - ...payload, - ...estimatedFields, - maxFeePerGas: BigInt(feePerGasPair?.maxFeePerGas ?? payload.maxFeePerGas), - maxPriorityFeePerGas: BigInt(feePerGasPair?.maxPriorityFeePerGas ?? payload.maxPriorityFeePerGas), - }, - } - } - - async status(opHash: Hex.Hex, _chainId: number): Promise { - try { - type PimlicoStatusResp = { - status: 'not_found' | 'not_submitted' | 'submitted' | 'rejected' | 'included' | 'failed' | 'reverted' - transactionHash: Hex.Hex | null - } - - let pimlico: PimlicoStatusResp | undefined - try { - pimlico = await this.bundlerRpc('pimlico_getUserOperationStatus', [opHash]) - } catch { - /* ignore - not Pimlico or endpoint down */ - } - - if (pimlico) { - switch (pimlico.status) { - case 'not_submitted': - case 'submitted': - return { status: 'pending' } - case 'rejected': - return { status: 'failed', reason: 'rejected by bundler' } - case 'failed': - case 'reverted': - return { - status: 'failed', - transactionHash: pimlico.transactionHash ?? undefined, - reason: pimlico.status, - } - case 'included': - // fall through to receipt lookup for full info - break - case 'not_found': - default: - return { status: 'unknown' } - } - } - - // Fallback to standard method - const receipt = await this.bundlerRpc('eth_getUserOperationReceipt', [opHash]) - - if (!receipt) return { status: 'pending' } - - const txHash: Hex.Hex | undefined = - (receipt.receipt?.transactionHash as Hex.Hex) ?? (receipt.transactionHash as Hex.Hex) ?? undefined - - const ok = receipt.success === true || receipt.receipt?.status === '0x1' || receipt.receipt?.status === 1 - - return ok - ? { status: 'confirmed', transactionHash: txHash ?? opHash, data: receipt } - : { - status: 'failed', - transactionHash: txHash, - reason: receipt.revertReason ?? 'UserOp reverted', - } - } catch (err: any) { - console.error('[PimlicoBundler.status]', err) - return { status: 'unknown', reason: err?.message ?? 'status lookup failed' } - } - } - - private async bundlerRpc(method: string, params: any[]): Promise { - const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) - const res = await this.fetcher(this.bundlerRpcUrl, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body, - }) - const json = await res.json() - if (json.error) throw new Error(json.error.message ?? 'bundler error') - return json.result - } -} diff --git a/packages/wallet/core/src/bundler/index.ts b/packages/wallet/core/src/bundler/index.ts deleted file mode 100644 index 53c531a9be..0000000000 --- a/packages/wallet/core/src/bundler/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Export the core interfaces and type guards -export * from './bundler.js' - -// Group and export implementations -export * as Bundlers from './bundlers/index.js' diff --git a/packages/wallet/core/src/env.ts b/packages/wallet/core/src/env.ts deleted file mode 100644 index 0a463dd1d8..0000000000 --- a/packages/wallet/core/src/env.ts +++ /dev/null @@ -1,68 +0,0 @@ -export type StorageLike = { - getItem: (key: string) => string | null - setItem: (key: string, value: string) => void - removeItem: (key: string) => void -} - -export type CryptoLike = { - subtle: SubtleCrypto - getRandomValues: (array: T) => T -} - -export type TextEncodingLike = { - TextEncoder: typeof TextEncoder - TextDecoder: typeof TextDecoder -} - -export type CoreEnv = { - fetch?: typeof fetch - crypto?: CryptoLike - storage?: StorageLike - indexedDB?: IDBFactory - text?: Partial -} - -function isStorageLike(value: unknown): value is StorageLike { - if (!value || typeof value !== 'object') return false - const candidate = value as StorageLike - return ( - typeof candidate.getItem === 'function' && - typeof candidate.setItem === 'function' && - typeof candidate.removeItem === 'function' - ) -} - -export function resolveCoreEnv(env?: CoreEnv): CoreEnv { - const globalObj = globalThis as any - const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {}) - let storage: StorageLike | undefined - let text: Partial | undefined - - if (isStorageLike(env?.storage)) { - storage = env.storage - } else if (isStorageLike(windowObj.localStorage)) { - storage = windowObj.localStorage - } else if (isStorageLike(globalObj.localStorage)) { - storage = globalObj.localStorage - } - - if (env?.text) { - if (!env.text.TextEncoder || !env.text.TextDecoder) { - throw new Error('env.text must provide both TextEncoder and TextDecoder') - } - text = env.text - } else { - text = { - TextEncoder: windowObj.TextEncoder ?? globalObj.TextEncoder, - TextDecoder: windowObj.TextDecoder ?? globalObj.TextDecoder, - } - } - - return { - fetch: env?.fetch ?? windowObj.fetch ?? globalObj.fetch, - crypto: env?.crypto ?? windowObj.crypto ?? globalObj.crypto, - storage, - indexedDB: env?.indexedDB ?? windowObj.indexedDB ?? globalObj.indexedDB, - text, - } -} diff --git a/packages/wallet/core/src/envelope.ts b/packages/wallet/core/src/envelope.ts deleted file mode 100644 index 0f1a02c723..0000000000 --- a/packages/wallet/core/src/envelope.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Config, Payload, Signature } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' - -export type Envelope = { - readonly wallet: Address.Address - readonly chainId: number - readonly configuration: Config.Config - readonly payload: T -} - -export type Signature = { - address: Address.Address - signature: Signature.SignatureOfSignerLeaf -} - -// Address not included as it is included in the signature -export type SapientSignature = { - imageHash: Hex.Hex - signature: Signature.SignatureOfSapientSignerLeaf -} - -export function isSignature(sig: any): sig is Signature { - return typeof sig === 'object' && 'address' in sig && 'signature' in sig && !('imageHash' in sig) -} - -export function isSapientSignature(sig: any): sig is SapientSignature { - return typeof sig === 'object' && 'signature' in sig && 'imageHash' in sig -} - -export type Signed = Envelope & { - signatures: (Signature | SapientSignature)[] -} - -export function signatureForLeaf(envelope: Signed, leaf: Config.Leaf) { - if (Config.isSignerLeaf(leaf)) { - return envelope.signatures.find((sig) => isSignature(sig) && Address.isEqual(sig.address, leaf.address)) - } - - if (Config.isSapientSignerLeaf(leaf)) { - return envelope.signatures.find( - (sig) => - isSapientSignature(sig) && - sig.imageHash === leaf.imageHash && - Address.isEqual(sig.signature.address, leaf.address), - ) - } - - return undefined -} - -export function weightOf(envelope: Signed): { weight: bigint; threshold: bigint } { - const { maxWeight } = Config.getWeight(envelope.configuration, (s) => !!signatureForLeaf(envelope, s)) - return { - weight: maxWeight, - threshold: envelope.configuration.threshold, - } -} - -export function reachedThreshold(envelope: Signed): boolean { - const { weight, threshold } = weightOf(envelope) - return weight >= threshold -} - -export function encodeSignature(envelope: Signed): Signature.RawSignature { - const topology = Signature.fillLeaves( - envelope.configuration.topology, - (s) => signatureForLeaf(envelope, s)?.signature, - ) - return { - noChainId: envelope.chainId === 0, - configuration: { ...envelope.configuration, topology }, - } -} - -export function toSigned( - envelope: Envelope, - signatures: (Signature | SapientSignature)[] = [], -): Signed { - return { - ...envelope, - signatures, - } -} - -export function addSignature( - envelope: Signed, - signature: Signature | SapientSignature, - args?: { replace?: boolean }, -) { - if (isSapientSignature(signature)) { - // Find if the signature already exists in envelope - const prev = envelope.signatures.find( - (sig) => - isSapientSignature(sig) && - Address.isEqual(sig.signature.address, signature.signature.address) && - sig.imageHash === signature.imageHash, - ) as SapientSignature | undefined - - if (prev) { - // If the signatures are identical, then we can do nothing - if (prev.signature.data === signature.signature.data) { - return - } - - // If not and we are replacing, then remove the previous signature - if (args?.replace) { - envelope.signatures = envelope.signatures.filter((sig) => sig !== prev) - } else { - throw new Error('Signature already defined for signer') - } - } - - envelope.signatures.push(signature) - } else if (isSignature(signature)) { - // Find if the signature already exists in envelope - const prev = envelope.signatures.find( - (sig) => isSignature(sig) && Address.isEqual(sig.address, signature.address), - ) as Signature | undefined - - if (prev) { - // If the signatures are identical, then we can do nothing - if (prev.signature.type === 'erc1271' && signature.signature.type === 'erc1271') { - if (prev.signature.data === signature.signature.data) { - return - } - } else if (prev.signature.type !== 'erc1271' && signature.signature.type !== 'erc1271') { - if (prev.signature.r === signature.signature.r && prev.signature.s === signature.signature.s) { - return - } - } - - // If not and we are replacing, then remove the previous signature - if (args?.replace) { - envelope.signatures = envelope.signatures.filter((sig) => sig !== prev) - } else { - throw new Error('Signature already defined for signer') - } - } - - envelope.signatures.push(signature) - } else { - throw new Error('Unsupported signature type') - } -} - -export function isSigned(envelope: Envelope): envelope is Signed { - return typeof envelope === 'object' && 'signatures' in envelope -} diff --git a/packages/wallet/core/src/index.ts b/packages/wallet/core/src/index.ts deleted file mode 100644 index 27c54b8f53..0000000000 --- a/packages/wallet/core/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './wallet.js' - -export * as Signers from './signers/index.js' -export * as State from './state/index.js' -export * as Bundler from './bundler/index.js' -export * as Envelope from './envelope.js' -export * as Utils from './utils/index.js' -export * from './env.js' -export { - type ExplicitSessionConfig, - type ExplicitSession, - type ImplicitSession, - type Session, -} from './utils/session/types.js' diff --git a/packages/wallet/core/src/signers/guard.ts b/packages/wallet/core/src/signers/guard.ts deleted file mode 100644 index 6ee2f21302..0000000000 --- a/packages/wallet/core/src/signers/guard.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Address, Bytes, TypedData, Signature, Hash } from 'ox' -import { Attestation, Payload } from '@0xsequence/wallet-primitives' -import * as GuardService from '@0xsequence/guard' -import * as Envelope from '../envelope.js' - -export type GuardToken = { - id: 'TOTP' | 'PIN' | 'recovery' - code: string - resetAuth?: boolean -} - -export class Guard { - public readonly address: Address.Address - - constructor(private readonly guard: GuardService.Guard) { - this.address = this.guard.address - } - - async signEnvelope( - envelope: Envelope.Signed, - token?: GuardToken, - ): Promise { - // Important: guard must always sign without parent wallets, even if the payload is parented - const unparentedPayload = { - ...envelope.payload, - parentWallets: undefined, - } - - const payloadType = toGuardType(envelope.payload) - const { message, digest } = toGuardPayload(envelope.wallet, envelope.chainId, unparentedPayload) - const previousSignatures = envelope.signatures.map(toGuardSignature) - - const signature = await this.guard.signPayload( - envelope.wallet, - envelope.chainId, - payloadType, - digest, - message, - previousSignatures, - token ? { id: token.id, token: token.code, resetAuth: token.resetAuth } : undefined, - ) - return { - address: this.guard.address, - signature: { - type: 'hash', - ...signature, - }, - } - } -} - -function toGuardType(type: Payload.Payload): GuardService.PayloadType { - switch (type.type) { - case 'call': - return GuardService.PayloadType.Calls - case 'message': - return GuardService.PayloadType.Message - case 'config-update': - return GuardService.PayloadType.ConfigUpdate - case 'session-implicit-authorize': - return GuardService.PayloadType.SessionImplicitAuthorize - } - throw new Error(`Payload type not supported by Guard: ${type.type}`) -} - -function toGuardPayload(wallet: Address.Address, chainId: number, payload: Payload.Payload) { - if (Payload.isSessionImplicitAuthorize(payload)) { - return { - message: Bytes.fromString(Attestation.toJson(payload.attestation)), - digest: Hash.keccak256(Attestation.encode(payload.attestation)), - } - } - const typedData = Payload.toTyped(wallet, chainId, payload) - return { - message: Bytes.fromString(TypedData.serialize(typedData)), - digest: Bytes.fromHex(TypedData.getSignPayload(typedData)), - } -} - -function toGuardSignature(signature: Envelope.Signature | Envelope.SapientSignature): GuardService.Signature { - if (Envelope.isSapientSignature(signature)) { - return { - type: GuardService.SignatureType.Sapient, - address: signature.signature.address, - imageHash: signature.imageHash, - data: signature.signature.data, - } - } - - if (signature.signature.type == 'erc1271') { - return { - type: GuardService.SignatureType.Erc1271, - address: signature.signature.address, - data: signature.signature.data, - } - } - - const type = { - eth_sign: GuardService.SignatureType.EthSign, - hash: GuardService.SignatureType.Hash, - }[signature.signature.type] - if (!type) { - throw new Error(`Signature type not supported by Guard: ${signature.signature.type}`) - } - - return { - type, - address: signature.address, - data: Signature.toHex(signature.signature), - } -} diff --git a/packages/wallet/core/src/signers/index.ts b/packages/wallet/core/src/signers/index.ts deleted file mode 100644 index 28a8815b20..0000000000 --- a/packages/wallet/core/src/signers/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Config, Payload, Signature } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import * as State from '../state/index.js' - -export * as Pk from './pk/index.js' -export * as Passkey from './passkey.js' -export * as Session from './session/index.js' -export * from './session-manager.js' -export * from './guard.js' - -export interface Signer { - readonly address: MaybePromise - - sign: ( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - ) => Config.SignerSignature -} - -export interface SapientSigner { - readonly address: MaybePromise - readonly imageHash: MaybePromise - - signSapient: ( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - imageHash: Hex.Hex, - ) => Config.SignerSignature -} - -export interface Witnessable { - witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: object) => Promise -} - -type MaybePromise = T | Promise - -export function isSapientSigner(signer: Signer | SapientSigner): signer is SapientSigner { - return 'signSapient' in signer -} - -export function isSigner(signer: Signer | SapientSigner): signer is Signer { - return 'sign' in signer -} diff --git a/packages/wallet/core/src/signers/passkey.ts b/packages/wallet/core/src/signers/passkey.ts deleted file mode 100644 index 770b6b1811..0000000000 --- a/packages/wallet/core/src/signers/passkey.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { Hex, Bytes, Address, P256, Hash } from 'ox' -import { Payload, Extensions } from '@0xsequence/wallet-primitives' -import type { Signature as SignatureTypes } from '@0xsequence/wallet-primitives' -import { WebAuthnP256 } from 'ox' -import { State } from '../index.js' -import { SapientSigner, Witnessable } from './index.js' - -export type WebAuthnLike = Pick - -export type PasskeyOptions = { - extensions: Pick - publicKey: Extensions.Passkeys.PublicKey - credentialId: string - embedMetadata?: boolean - metadata?: Extensions.Passkeys.PasskeyMetadata - webauthn?: WebAuthnLike -} - -export type CreatePasskeyOptions = { - stateProvider?: State.Provider - requireUserVerification?: boolean - credentialName?: string - embedMetadata?: boolean - webauthn?: WebAuthnLike -} - -export type FindPasskeyOptions = { - webauthn?: WebAuthnLike -} - -export type WitnessMessage = { - action: 'consent-to-be-part-of-wallet' - wallet: Address.Address - publicKey: Extensions.Passkeys.PublicKey - timestamp: number - metadata?: Extensions.Passkeys.PasskeyMetadata -} - -export function isWitnessMessage(message: unknown): message is WitnessMessage { - return ( - typeof message === 'object' && - message !== null && - 'action' in message && - message.action === 'consent-to-be-part-of-wallet' - ) -} - -export class Passkey implements SapientSigner, Witnessable { - public readonly credentialId: string - - public readonly publicKey: Extensions.Passkeys.PublicKey - public readonly address: Address.Address - public readonly imageHash: Hex.Hex - public readonly embedMetadata: boolean - public readonly metadata?: Extensions.Passkeys.PasskeyMetadata - private readonly webauthn: WebAuthnLike - - constructor(options: PasskeyOptions) { - this.address = options.extensions.passkeys - this.publicKey = options.publicKey - this.credentialId = options.credentialId - this.embedMetadata = options.embedMetadata ?? false - this.imageHash = Extensions.Passkeys.rootFor(options.publicKey) - this.metadata = options.metadata - this.webauthn = options.webauthn ?? WebAuthnP256 - } - - static async loadFromWitness( - stateReader: State.Reader, - extensions: Pick, - wallet: Address.Address, - imageHash: Hex.Hex, - options?: FindPasskeyOptions, - ) { - // In the witness we will find the public key, and may find the credential id - const witness = await stateReader.getWitnessForSapient(wallet, extensions.passkeys, imageHash) - if (!witness) { - throw new Error('Witness for wallet not found') - } - - const payload = witness.payload - if (!Payload.isMessage(payload)) { - throw new Error('Witness payload is not a message') - } - - const message = JSON.parse(Hex.toString(payload.message)) - if (!isWitnessMessage(message)) { - throw new Error('Witness payload is not a witness message') - } - - const metadata = message.publicKey.metadata || message.metadata - if (typeof metadata === 'string' || !metadata) { - throw new Error('Metadata does not contain credential id') - } - - const decodedSignature = Extensions.Passkeys.decode(Bytes.fromHex(witness.signature.data)) - - return new Passkey({ - credentialId: metadata.credentialId, - extensions, - publicKey: message.publicKey, - embedMetadata: decodedSignature.embedMetadata, - metadata, - webauthn: options?.webauthn, - }) - } - - static async create(extensions: Pick, options?: CreatePasskeyOptions) { - const webauthn = options?.webauthn ?? WebAuthnP256 - const name = options?.credentialName ?? `Sequence (${Date.now()})` - - const credential = await webauthn.createCredential({ - user: { - name, - }, - }) - - const x = Hex.fromNumber(credential.publicKey.x) - const y = Hex.fromNumber(credential.publicKey.y) - - const metadata = { - credentialId: credential.id, - } - - const passkey = new Passkey({ - credentialId: credential.id, - extensions, - publicKey: { - requireUserVerification: options?.requireUserVerification ?? true, - x, - y, - metadata: options?.embedMetadata ? metadata : undefined, - }, - embedMetadata: options?.embedMetadata, - metadata, - webauthn, - }) - - if (options?.stateProvider) { - await options.stateProvider.saveTree(Extensions.Passkeys.toTree(passkey.publicKey)) - } - - return passkey - } - - static async find( - stateReader: State.Reader, - extensions: Pick, - options?: FindPasskeyOptions, - ): Promise { - const webauthn = options?.webauthn ?? WebAuthnP256 - const response = await webauthn.sign({ challenge: Hex.random(32) }) - if (!response.raw) throw new Error('No credential returned') - - const authenticatorDataBytes = Bytes.fromHex(response.metadata.authenticatorData) - const clientDataHash = Hash.sha256(Bytes.fromString(response.metadata.clientDataJSON), { as: 'Bytes' }) - const messageSignedByAuthenticator = Bytes.concat(authenticatorDataBytes, clientDataHash) - - const messageHash = Hash.sha256(messageSignedByAuthenticator, { as: 'Bytes' }) // Use Bytes output - - const publicKey1 = P256.recoverPublicKey({ - payload: messageHash, - signature: { - r: BigInt(response.signature.r), - s: BigInt(response.signature.s), - yParity: 0, - }, - }) - - const publicKey2 = P256.recoverPublicKey({ - payload: messageHash, - signature: { - r: BigInt(response.signature.r), - s: BigInt(response.signature.s), - yParity: 1, - }, - }) - - // Compute the imageHash for all public key combinations - // - requireUserVerification: true / false - // - embedMetadata: true / false - - const base1 = { - x: Hex.fromNumber(publicKey1.x), - y: Hex.fromNumber(publicKey1.y), - } - - const base2 = { - x: Hex.fromNumber(publicKey2.x), - y: Hex.fromNumber(publicKey2.y), - } - - const metadata = { - credentialId: response.raw.id, - } - - const imageHashes = [ - Extensions.Passkeys.rootFor({ ...base1, requireUserVerification: true }), - Extensions.Passkeys.rootFor({ ...base1, requireUserVerification: false }), - Extensions.Passkeys.rootFor({ ...base1, requireUserVerification: true, metadata }), - Extensions.Passkeys.rootFor({ ...base1, requireUserVerification: false, metadata }), - Extensions.Passkeys.rootFor({ ...base2, requireUserVerification: true }), - Extensions.Passkeys.rootFor({ ...base2, requireUserVerification: false }), - Extensions.Passkeys.rootFor({ ...base2, requireUserVerification: true, metadata }), - Extensions.Passkeys.rootFor({ ...base2, requireUserVerification: false, metadata }), - ] - - // Find wallets for all possible image hashes - const signers = await Promise.all( - imageHashes.map(async (imageHash) => { - const wallets = await stateReader.getWalletsForSapient(extensions.passkeys, imageHash) - return Object.keys(wallets).map((wallet) => ({ - wallet: Address.from(wallet), - imageHash, - })) - }), - ) - - // Flatten and remove duplicates - const flattened = signers - .flat() - .filter( - (v, i, self) => self.findIndex((t) => Address.isEqual(t.wallet, v.wallet) && t.imageHash === v.imageHash) === i, - ) - - // If there are no signers, return undefined - if (flattened.length === 0) { - return undefined - } - - // If there are multiple signers log a warning - // but we still return the first one - if (flattened.length > 1) { - console.warn('Multiple signers found for passkey', flattened) - } - - return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash, options) - } - - async signSapient( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - imageHash: Hex.Hex, - ): Promise { - if (this.imageHash !== imageHash) { - // TODO: This should never get called, why do we have this? - throw new Error('Unexpected image hash') - } - - const challenge = Hex.fromBytes(Payload.hash(wallet, chainId, payload)) - - const response = await this.webauthn.sign({ - challenge, - credentialId: this.credentialId, - userVerification: this.publicKey.requireUserVerification ? 'required' : 'discouraged', - }) - - const authenticatorData = Bytes.fromHex(response.metadata.authenticatorData) - const rBytes = Bytes.fromNumber(response.signature.r) - const sBytes = Bytes.fromNumber(response.signature.s) - - const signature = Extensions.Passkeys.encode({ - publicKey: this.publicKey, - r: rBytes, - s: sBytes, - authenticatorData, - clientDataJSON: response.metadata.clientDataJSON, - embedMetadata: this.embedMetadata, - }) - - return { - address: this.address, - data: Bytes.toHex(signature), - type: 'sapient_compact', - } - } - - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { - const payload = Payload.fromMessage( - Hex.fromString( - JSON.stringify({ - action: 'consent-to-be-part-of-wallet', - wallet, - publicKey: this.publicKey, - metadata: this.metadata, - timestamp: Date.now(), - ...extra, - } as WitnessMessage), - ), - ) - - const signature = await this.signSapient(wallet, 0, payload, this.imageHash) - await stateWriter.saveWitnesses(wallet, 0, payload, { - type: 'unrecovered-signer', - weight: 1n, - signature, - }) - } -} diff --git a/packages/wallet/core/src/signers/pk/encrypted.ts b/packages/wallet/core/src/signers/pk/encrypted.ts deleted file mode 100644 index dce0eb3cc7..0000000000 --- a/packages/wallet/core/src/signers/pk/encrypted.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { Hex, Address, PublicKey, Secp256k1, Bytes } from 'ox' -import { resolveCoreEnv, type CoreEnv, type CryptoLike, type StorageLike, type TextEncodingLike } from '../../env.js' -import { PkStore } from './index.js' - -export interface EncryptedData { - iv: BufferSource - data: BufferSource - keyPointer: string - address: Address.Address - publicKey: PublicKey.PublicKey -} - -export class EncryptedPksDb { - private tableName: string - private dbName: string = 'pk-db' - private dbVersion: number = 1 - - constructor( - private readonly localStorageKeyPrefix: string = 'e_pk_key_', - tableName: string = 'e_pk', - private readonly env?: CoreEnv, - ) { - this.tableName = tableName - } - - private computeDbKey(address: Address.Address): string { - return `pk_${address.toLowerCase()}` - } - - private getIndexedDB(): IDBFactory { - const globalObj = globalThis as any - const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB - if (!indexedDb) { - throw new Error('indexedDB is not available') - } - return indexedDb - } - - private getStorage(): StorageLike { - const storage = resolveCoreEnv(this.env).storage - if (!storage) { - throw new Error('storage is not available') - } - return storage - } - - private getCrypto(): CryptoLike { - const globalObj = globalThis as any - const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto - if (!crypto?.subtle || !crypto?.getRandomValues) { - throw new Error('crypto.subtle is not available') - } - return crypto - } - - private getTextEncoderCtor(): TextEncodingLike['TextEncoder'] { - const globalObj = globalThis as any - if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) { - throw new Error('env.text must provide both TextEncoder and TextDecoder') - } - const encoderCtor = this.env?.text?.TextEncoder ?? globalObj.TextEncoder ?? globalObj.window?.TextEncoder - if (!encoderCtor) { - throw new Error('TextEncoder is not available') - } - return encoderCtor - } - - private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] { - const globalObj = globalThis as any - if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) { - throw new Error('env.text must provide both TextEncoder and TextDecoder') - } - const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder - if (!decoderCtor) { - throw new Error('TextDecoder is not available') - } - return decoderCtor - } - - private openDB(): Promise { - return new Promise((resolve, reject) => { - const request = this.getIndexedDB().open(this.dbName, this.dbVersion) - request.onupgradeneeded = () => { - const db = request.result - if (!db.objectStoreNames.contains(this.tableName)) { - db.createObjectStore(this.tableName) - } - } - request.onsuccess = () => resolve(request.result) - request.onerror = () => reject(request.error) - }) - } - - private async putData(key: string, value: any): Promise { - const db = await this.openDB() - return new Promise((resolve, reject) => { - const tx = db.transaction(this.tableName, 'readwrite') - const store = tx.objectStore(this.tableName) - const request = store.put(value, key) - request.onsuccess = () => resolve() - request.onerror = () => reject(request.error) - }) - } - - private async getData(key: string): Promise { - const db = await this.openDB() - return new Promise((resolve, reject) => { - const tx = db.transaction(this.tableName, 'readonly') - const store = tx.objectStore(this.tableName) - const request = store.get(key) - request.onsuccess = () => resolve(request.result) - request.onerror = () => reject(request.error) - }) - } - - private async getAllData(): Promise { - const db = await this.openDB() - return new Promise((resolve, reject) => { - const tx = db.transaction(this.tableName, 'readonly') - const store = tx.objectStore(this.tableName) - const request = store.getAll() - request.onsuccess = () => resolve(request.result) - request.onerror = () => reject(request.error) - }) - } - - async generateAndStore(): Promise { - const crypto = this.getCrypto() - const storage = this.getStorage() - const TextEncoderCtor = this.getTextEncoderCtor() - - const encryptionKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [ - 'encrypt', - 'decrypt', - ]) - - const privateKey = Hex.random(32) - - const publicKey = Secp256k1.getPublicKey({ privateKey }) - const address = Address.fromPublicKey(publicKey) - const keyPointer = this.localStorageKeyPrefix + address - - const exportedKey = await crypto.subtle.exportKey('jwk', encryptionKey) - storage.setItem(keyPointer, JSON.stringify(exportedKey)) - - const encoder = new TextEncoderCtor() - const encodedPk = encoder.encode(privateKey) - const iv = crypto.getRandomValues(new Uint8Array(12)) - const encryptedBuffer = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk) - - const encrypted: EncryptedData = { - iv, - data: encryptedBuffer, - keyPointer, - address, - publicKey, - } - - const dbKey = this.computeDbKey(address) - await this.putData(dbKey, encrypted) - return encrypted - } - - async getEncryptedEntry(address: Address.Address): Promise { - const dbKey = this.computeDbKey(address) - return this.getData(dbKey) - } - - async getEncryptedPkStore(address: Address.Address): Promise { - const entry = await this.getEncryptedEntry(address) - if (!entry) return - return new EncryptedPkStore(entry, this.env) - } - - async listAddresses(): Promise { - const allEntries = await this.getAllData() - return allEntries.map((entry) => entry.address) - } - - async remove(address: Address.Address) { - const dbKey = this.computeDbKey(address) - await this.putData(dbKey, undefined) - const keyPointer = this.localStorageKeyPrefix + address - this.getStorage().removeItem(keyPointer) - } -} - -export class EncryptedPkStore implements PkStore { - constructor( - private readonly encrypted: EncryptedData, - private readonly env?: CoreEnv, - ) {} - - private getStorage(): StorageLike { - const storage = resolveCoreEnv(this.env).storage - if (!storage) { - throw new Error('storage is not available') - } - return storage - } - - private getCrypto(): CryptoLike { - const globalObj = globalThis as any - const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto - if (!crypto?.subtle) { - throw new Error('crypto.subtle is not available') - } - return crypto - } - - private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] { - const globalObj = globalThis as any - const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder - if (!decoderCtor) { - throw new Error('TextDecoder is not available') - } - return decoderCtor - } - - address(): Address.Address { - return this.encrypted.address - } - - publicKey(): PublicKey.PublicKey { - return this.encrypted.publicKey - } - - async signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> { - const storage = this.getStorage() - const crypto = this.getCrypto() - const TextDecoderCtor = this.getTextDecoderCtor() - - const keyJson = storage.getItem(this.encrypted.keyPointer) - if (!keyJson) throw new Error('Encryption key not found in localStorage') - const jwk = JSON.parse(keyJson) - const encryptionKey = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt']) - const decryptedBuffer = await crypto.subtle.decrypt( - { name: 'AES-GCM', iv: this.encrypted.iv }, - encryptionKey, - this.encrypted.data, - ) - const decoder = new TextDecoderCtor() - const privateKey = decoder.decode(decryptedBuffer) as Hex.Hex - return Secp256k1.sign({ payload: digest, privateKey }) - } -} diff --git a/packages/wallet/core/src/signers/pk/index.ts b/packages/wallet/core/src/signers/pk/index.ts deleted file mode 100644 index b15fb3ecbd..0000000000 --- a/packages/wallet/core/src/signers/pk/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Payload as PayloadTypes, Signature as SignatureTypes } from '@0xsequence/wallet-primitives' -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Bytes, Hex, PublicKey, Secp256k1 } from 'ox' -import { Signer as SignerInterface, Witnessable } from '../index.js' -import { State } from '../../index.js' - -export interface PkStore { - address(): Address.Address - publicKey(): PublicKey.PublicKey - signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> -} - -export class MemoryPkStore implements PkStore { - constructor(private readonly privateKey: Hex.Hex) {} - - address(): Address.Address { - return Address.fromPublicKey(this.publicKey()) - } - - publicKey(): PublicKey.PublicKey { - return Secp256k1.getPublicKey({ privateKey: this.privateKey }) - } - - signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> { - return Promise.resolve(Secp256k1.sign({ payload: digest, privateKey: this.privateKey })) - } -} - -export class Pk implements SignerInterface, Witnessable { - private readonly privateKey: PkStore - - public readonly address: Address.Address - public readonly pubKey: PublicKey.PublicKey - - constructor(privateKey: Hex.Hex | PkStore) { - this.privateKey = typeof privateKey === 'string' ? new MemoryPkStore(privateKey) : privateKey - this.pubKey = this.privateKey.publicKey() - this.address = this.privateKey.address() - } - - async sign( - wallet: Address.Address, - chainId: number, - payload: PayloadTypes.Parented, - ): Promise { - const hash = Payload.hash(wallet, chainId, payload) - return this.signDigest(hash) - } - - async signDigest(digest: Bytes.Bytes): Promise { - const signature = await this.privateKey.signDigest(digest) - return { ...signature, type: 'hash' } - } - - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { - const payload = Payload.fromMessage( - Hex.fromString( - JSON.stringify({ - action: 'consent-to-be-part-of-wallet', - wallet, - signer: this.address, - timestamp: Date.now(), - ...extra, - }), - ), - ) - - const signature = await this.sign(wallet, 0, payload) - await stateWriter.saveWitnesses(wallet, 0, payload, { - type: 'unrecovered-signer', - weight: 1n, - signature, - }) - } -} - -export * as Encrypted from './encrypted.js' diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts deleted file mode 100644 index 6b23cb7a6c..0000000000 --- a/packages/wallet/core/src/signers/session-manager.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { - Config, - Constants, - Extensions, - Payload, - SessionConfig, - SessionSignature, - Signature as SignatureTypes, -} from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Hex, Provider } from 'ox' -import * as State from '../state/index.js' -import { Wallet } from '../wallet.js' -import { SapientSigner } from './index.js' -import { - Explicit, - Implicit, - isExplicitSessionSigner, - SessionSigner, - SessionSignerInvalidReason, - isImplicitSessionSigner, - isIncrementCall, - UsageLimit, -} from './session/index.js' - -export type SessionManagerOptions = { - sessionManagerAddress: Address.Address - stateProvider?: State.Provider - implicitSigners?: Implicit[] - explicitSigners?: Explicit[] - provider?: Provider.Provider -} - -const MAX_SPACE = 2n ** 80n - 1n - -export class SessionManager implements SapientSigner { - public readonly stateProvider: State.Provider - public readonly address: Address.Address - - private readonly _implicitSigners: Implicit[] - private readonly _explicitSigners: Explicit[] - private readonly _provider?: Provider.Provider - - constructor( - readonly wallet: Wallet, - options: SessionManagerOptions, - ) { - this.stateProvider = options.stateProvider ?? wallet.stateProvider - this.address = options.sessionManagerAddress - this._implicitSigners = options.implicitSigners ?? [] - this._explicitSigners = options.explicitSigners ?? [] - this._provider = options.provider - } - - get imageHash(): Promise { - return this.getImageHash() - } - - async getImageHash(): Promise { - const { configuration } = await this.wallet.getStatus() - const sessionConfigLeaf = Config.findSignerLeaf(configuration, this.address) - if (!sessionConfigLeaf || !Config.isSapientSignerLeaf(sessionConfigLeaf)) { - return undefined - } - return sessionConfigLeaf.imageHash - } - - get topology(): Promise { - return this.getTopology() - } - - async getTopology(): Promise { - const imageHash = await this.imageHash - if (!imageHash) { - throw new Error(`Session configuration not found for image hash ${imageHash}`) - } - const tree = await this.stateProvider.getTree(imageHash) - if (!tree) { - throw new Error(`Session configuration not found for image hash ${imageHash}`) - } - return SessionConfig.configurationTreeToSessionsTopology(tree) - } - - withProvider(provider: Provider.Provider): SessionManager { - return new SessionManager(this.wallet, { - sessionManagerAddress: this.address, - stateProvider: this.stateProvider, - implicitSigners: this._implicitSigners, - explicitSigners: this._explicitSigners, - provider, - }) - } - - withImplicitSigner(signer: Implicit): SessionManager { - const implicitSigners = [...this._implicitSigners, signer] - return new SessionManager(this.wallet, { - sessionManagerAddress: this.address, - stateProvider: this.stateProvider, - implicitSigners, - explicitSigners: this._explicitSigners, - provider: this._provider, - }) - } - - withExplicitSigner(signer: Explicit): SessionManager { - const explicitSigners = [...this._explicitSigners, signer] - - return new SessionManager(this.wallet, { - sessionManagerAddress: this.address, - stateProvider: this.stateProvider, - implicitSigners: this._implicitSigners, - explicitSigners, - provider: this._provider, - }) - } - - async listSignerValidity( - chainId: number, - ): Promise<{ signer: Address.Address; isValid: boolean; invalidReason?: SessionSignerInvalidReason }[]> { - const topology = await this.topology - const signerStatus = new Map() - for (const signer of this._implicitSigners) { - signerStatus.set(signer.address, signer.isValid(topology, chainId)) - } - for (const signer of this._explicitSigners) { - signerStatus.set(signer.address, signer.isValid(topology, chainId)) - } - return Array.from(signerStatus.entries()).map(([signer, { isValid, invalidReason }]) => ({ - signer, - isValid, - invalidReason, - })) - } - - /** - * Find one signer per call from the given candidate list (first that supports each call). - */ - private async findSignersForCallsWithCandidates( - wallet: Address.Address, - chainId: number, - calls: Payload.Call[], - topology: SessionConfig.SessionsTopology, - availableSigners: SessionSigner[], - ): Promise { - const signers: SessionSigner[] = [] - for (const call of calls) { - let supported = false - let expiredSupportedSigner: SessionSigner | undefined - for (const signer of availableSigners) { - try { - supported = await signer.supportedCall(wallet, chainId, call, this.address, this._provider) - if (supported) { - // Check signer validity - const signerValidity = signer.isValid(topology, chainId) - if (signerValidity.invalidReason === 'Expired') { - expiredSupportedSigner = signer - } - supported = signerValidity.isValid - } - } catch (error) { - console.error('findSignersForCalls error', error) - continue - } - if (supported) { - signers.push(signer) - break - } - } - if (!supported) { - if (expiredSupportedSigner) { - throw new Error(`Signer supporting call is expired: ${expiredSupportedSigner.address}`) - } - throw new Error(`No signer supported for call. Call: to=${call.to}, data=${call.data}, value=${call.value}, `) - } - } - return signers - } - - async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { - const topology = await this.topology - const identitySigners = SessionConfig.getIdentitySigners(topology) - if (identitySigners.length === 0) { - throw new Error('Identity signers not found') - } - - const availableSigners = [...this._implicitSigners, ...this._explicitSigners] - if (availableSigners.length === 0) { - throw new Error('No signers match the topology') - } - - const nonIncrementCalls: Payload.Call[] = [] - const incrementCalls: Payload.Call[] = [] - for (const call of calls) { - if (isIncrementCall(call, this.address)) { - incrementCalls.push(call) - } else { - nonIncrementCalls.push(call) - } - } - - // Find signers for non-increment calls - const nonIncrementSigners = - nonIncrementCalls.length > 0 - ? await this.findSignersForCallsWithCandidates(wallet, chainId, nonIncrementCalls, topology, availableSigners) - : [] - - let incrementSigners: SessionSigner[] = [] - if (incrementCalls.length > 0) { - // Find signers for increment calls, preferring signers that signed non-increment calls - const incrementCandidates = [ - ...nonIncrementSigners, - ...availableSigners.filter((s) => !nonIncrementSigners.includes(s)), - ] - incrementSigners = await this.findSignersForCallsWithCandidates( - wallet, - chainId, - incrementCalls, - topology, - incrementCandidates, - ) - } - - // Merge back in original call order - const signers: SessionSigner[] = [] - let nonIncrementIndex = 0 - let incrementIndex = 0 - for (const call of calls) { - if (isIncrementCall(call, this.address)) { - signers.push(incrementSigners[incrementIndex]!) - incrementIndex++ - } else { - signers.push(nonIncrementSigners[nonIncrementIndex]!) - nonIncrementIndex++ - } - } - return signers - } - - async prepareIncrement( - wallet: Address.Address, - chainId: number, - calls: Payload.Call[], - ): Promise { - if (calls.length === 0) { - throw new Error('No calls provided') - } - const signers = await this.findSignersForCalls(wallet, chainId, calls) - - // Map each signer to only their non-increment calls - const signerToNonIncrementCalls = new Map() - signers.forEach((signer, index) => { - const call = calls[index]! - if (isIncrementCall(call, this.address)) { - return - } - const existing = signerToNonIncrementCalls.get(signer) || [] - signerToNonIncrementCalls.set(signer, [...existing, call]) - }) - - // Prepare increments for each explicit signer from their non-increment calls only - const increments: UsageLimit[] = ( - await Promise.all( - Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => { - if (isExplicitSessionSigner(signer)) { - return signer.prepareIncrements(wallet, chainId, nonIncrementCalls, this.address, this._provider!) - } - return [] - }), - ) - ).flat() - - if (increments.length === 0) { - return null - } - - // Error if there are repeated usage hashes - const uniqueIncrements = increments.filter( - (increment, index, self) => index === self.findIndex((t) => t.usageHash === increment.usageHash), - ) - if (uniqueIncrements.length !== increments.length) { - throw new Error('Repeated usage hashes') - } - - const data = AbiFunction.encodeData(Constants.INCREMENT_USAGE_LIMIT, [increments]) - - return { - to: this.address, - data, - value: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - gasLimit: 0n, - } - } - - async signSapient( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - imageHash: Hex.Hex, - ): Promise { - if (!Address.isEqual(wallet, this.wallet.address)) { - throw new Error('Wallet address mismatch') - } - if ((await this.imageHash) !== imageHash) { - throw new Error('Unexpected image hash') - } - //FIXME Test chain id - // if (this._provider) { - // const providerChainId = await this._provider.request({ - // method: 'eth_chainId', - // }) - // if (providerChainId !== Hex.fromNumber(chainId)) { - // throw new Error(`Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`) - // } - // } - if (!Payload.isCalls(payload) || payload.calls.length === 0) { - throw new Error('Only calls are supported') - } - - // Check space - if (payload.space > MAX_SPACE) { - throw new Error(`Space ${payload.space} is too large`) - } - - const signers = await this.findSignersForCalls(wallet, chainId, payload.calls) - if (signers.length !== payload.calls.length) { - // Unreachable. Throw in findSignersForCalls - throw new Error('No signer supported for call') - } - const signatures = await Promise.all( - signers.map(async (signer, i) => { - try { - return signer.signCall(wallet, chainId, payload, i, this.address, this._provider) - } catch (error) { - console.error('signSapient error', error) - throw error - } - }), - ) - - // Check if the last call is an increment usage call - const expectedIncrement = await this.prepareIncrement(wallet, chainId, payload.calls) - if (expectedIncrement) { - let actualIncrement: Payload.Call - if ( - Address.isEqual(this.address, Extensions.Dev1.sessions) || - Address.isEqual(this.address, Extensions.Dev2.sessions) - ) { - // Last call - actualIncrement = payload.calls[payload.calls.length - 1]! - //FIXME Maybe this should throw since it's exploitable..? - } else { - // First call - actualIncrement = payload.calls[0]! - } - if ( - !Address.isEqual(expectedIncrement.to, actualIncrement.to) || - !Hex.isEqual(expectedIncrement.data, actualIncrement.data) - ) { - throw new Error('Actual increment call does not match expected increment call') - } - } - - // Prepare encoding params - const explicitSigners: Address.Address[] = [] - const implicitSigners: Address.Address[] = [] - let identitySigner: Address.Address | undefined - await Promise.all( - signers.map(async (signer) => { - const address = await signer.address - if (isExplicitSessionSigner(signer)) { - if (!explicitSigners.find((a) => Address.isEqual(a, address))) { - explicitSigners.push(address) - } - } else if (isImplicitSessionSigner(signer)) { - if (!implicitSigners.find((a) => Address.isEqual(a, address))) { - implicitSigners.push(address) - if (!identitySigner) { - identitySigner = signer.identitySigner - } else if (!Address.isEqual(identitySigner, signer.identitySigner)) { - throw new Error('Multiple implicit signers with different identity signers') - } - } - } - }), - ) - if (!identitySigner) { - // Explicit signers only. Use any identity signer - const identitySigners = SessionConfig.getIdentitySigners(await this.topology) - if (identitySigners.length === 0) { - throw new Error('No identity signers found') - } - identitySigner = identitySigners[0]! - } - - // Perform encoding - const encodedSignature = SessionSignature.encodeSessionSignature( - signatures, - await this.topology, - identitySigner, - explicitSigners, - implicitSigners, - ) - - return { - type: 'sapient', - address: this.address, - data: Hex.from(encodedSignature), - } - } - - async isValidSapientSignature( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signature: SignatureTypes.SignatureOfSapientSignerLeaf, - ): Promise { - if (!Payload.isCalls(payload)) { - // Only calls are supported - return false - } - - if (!this._provider) { - throw new Error('Provider not set') - } - //FIXME Test chain id - // const providerChainId = await this._provider.request({ - // method: 'eth_chainId', - // }) - // if (providerChainId !== Hex.fromNumber(chainId)) { - // throw new Error( - // `Provider chain id mismatch, expected ${Hex.fromNumber(chainId)} but got ${providerChainId}`, - // ) - // } - - const encodedPayload = Payload.encodeSapient(chainId, payload) - const encodedCallData = AbiFunction.encodeData(Constants.RECOVER_SAPIENT_SIGNATURE, [ - encodedPayload, - signature.data, - ]) - try { - const recoverSapientSignatureResult = await this._provider.request({ - method: 'eth_call', - params: [{ from: wallet, to: this.address, data: encodedCallData }, 'pending'], - }) - const resultImageHash = Hex.from( - AbiFunction.decodeResult(Constants.RECOVER_SAPIENT_SIGNATURE, recoverSapientSignatureResult), - ) - return resultImageHash === (await this.imageHash) - } catch (error) { - console.error('recoverSapientSignature error', error) - return false - } - } -} diff --git a/packages/wallet/core/src/signers/session/explicit.ts b/packages/wallet/core/src/signers/session/explicit.ts deleted file mode 100644 index ef78c554a6..0000000000 --- a/packages/wallet/core/src/signers/session/explicit.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' -import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox' -import { MemoryPkStore, PkStore } from '../pk/index.js' -import { ExplicitSessionSigner, isIncrementCall, SessionSignerValidity, UsageLimit } from './session.js' - -export type ExplicitParams = Omit - -const VALUE_TRACKING_ADDRESS: Address.Address = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' - -export class Explicit implements ExplicitSessionSigner { - private readonly _privateKey: PkStore - - public readonly address: Address.Address - public readonly sessionPermissions: Permission.SessionPermissions - - constructor(privateKey: Hex.Hex | PkStore, sessionPermissions: ExplicitParams) { - this._privateKey = typeof privateKey === 'string' ? new MemoryPkStore(privateKey) : privateKey - this.address = this._privateKey.address() - this.sessionPermissions = { - ...sessionPermissions, - signer: this.address, - } - } - - isValid(sessionTopology: SessionConfig.SessionsTopology, chainId: number): SessionSignerValidity { - // Equality is considered expired - if (this.sessionPermissions.deadline <= BigInt(Math.floor(Date.now() / 1000))) { - return { isValid: false, invalidReason: 'Expired' } - } - if (this.sessionPermissions.chainId !== 0 && this.sessionPermissions.chainId !== chainId) { - return { isValid: false, invalidReason: 'Chain ID mismatch' } - } - const explicitPermission = SessionConfig.getSessionPermissions(sessionTopology, this.address) - if (!explicitPermission) { - return { isValid: false, invalidReason: 'Permission not found' } - } - - // Validate permission in configuration matches permission in signer - if ( - explicitPermission.deadline !== this.sessionPermissions.deadline || - explicitPermission.chainId !== this.sessionPermissions.chainId || - explicitPermission.valueLimit !== this.sessionPermissions.valueLimit || - explicitPermission.permissions.length !== this.sessionPermissions.permissions.length - ) { - return { isValid: false, invalidReason: 'Permission mismatch' } - } - // Validate permission rules - for (const [index, permission] of explicitPermission.permissions.entries()) { - const signerPermission = this.sessionPermissions.permissions[index]! - if ( - !Address.isEqual(permission.target, signerPermission.target) || - permission.rules.length !== signerPermission.rules.length - ) { - return { isValid: false, invalidReason: 'Permission rule mismatch' } - } - for (const [ruleIndex, rule] of permission.rules.entries()) { - const signerRule = signerPermission.rules[ruleIndex]! - if ( - rule.cumulative !== signerRule.cumulative || - rule.operation !== signerRule.operation || - !Bytes.isEqual(rule.value, signerRule.value) || - rule.offset !== signerRule.offset || - !Bytes.isEqual(rule.mask, signerRule.mask) - ) { - return { isValid: false, invalidReason: 'Permission rule mismatch' } - } - } - } - return { isValid: true } - } - - async findSupportedPermission( - wallet: Address.Address, - chainId: number, - call: Payload.Call, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - if (this.sessionPermissions.chainId !== 0 && this.sessionPermissions.chainId !== chainId) { - return undefined - } - - if (call.value !== 0n) { - // Validate the value - if (!provider) { - throw new Error('Value transaction validation requires a provider') - } - const usageHash = Hash.keccak256( - AbiParameters.encode( - [ - { type: 'address', name: 'signer' }, - { type: 'address', name: 'valueTrackingAddress' }, - ], - [this.address, VALUE_TRACKING_ADDRESS], - ), - ) - const { usageAmount } = await this.readCurrentUsageLimit(wallet, sessionManagerAddress, usageHash, provider) - const value = Bytes.fromNumber(usageAmount + call.value, { size: 32 }) - if (Bytes.toBigInt(value) > this.sessionPermissions.valueLimit) { - return undefined - } - } - - for (const permission of this.sessionPermissions.permissions) { - // Validate the permission - if (await this.validatePermission(permission, call, wallet, sessionManagerAddress, provider)) { - return permission - } - } - return undefined - } - - private getPermissionUsageHash(permission: Permission.Permission, ruleIndex: number): Hex.Hex { - const encodedPermission = { - target: permission.target, - rules: permission.rules.map((rule) => ({ - cumulative: rule.cumulative, - operation: rule.operation, - value: Bytes.toHex(rule.value), - offset: rule.offset, - mask: Bytes.toHex(rule.mask), - })), - } - return Hash.keccak256( - AbiParameters.encode( - [{ type: 'address', name: 'signer' }, Permission.permissionStructAbi, { type: 'uint256', name: 'ruleIndex' }], - [this.address, encodedPermission, BigInt(ruleIndex)], - ), - ) - } - - private getValueUsageHash(): Hex.Hex { - return Hash.keccak256( - AbiParameters.encode( - [ - { type: 'address', name: 'signer' }, - { type: 'address', name: 'valueTrackingAddress' }, - ], - [this.address, VALUE_TRACKING_ADDRESS], - ), - ) - } - - async validatePermission( - permission: Permission.Permission, - call: Payload.Call, - wallet: Address.Address, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - if (!Address.isEqual(permission.target, call.to)) { - return false - } - - for (const [ruleIndex, rule] of permission.rules.entries()) { - // Extract value from calldata at offset - const callDataValue = Bytes.padRight( - Bytes.fromHex(call.data).slice(Number(rule.offset), Number(rule.offset) + 32), - 32, - ) - // Apply mask - let value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!) - if (rule.cumulative) { - if (provider) { - const { usageAmount } = await this.readCurrentUsageLimit( - wallet, - sessionManagerAddress, - this.getPermissionUsageHash(permission, ruleIndex), - provider, - ) - // Increment the value - value = Bytes.fromNumber(usageAmount + Bytes.toBigInt(value), { size: 32 }) - } else { - throw new Error('Cumulative rules require a provider') - } - } - - // Compare based on operation - if (rule.operation === Permission.ParameterOperation.EQUAL) { - if (!Bytes.isEqual(value, rule.value)) { - return false - } - } - if (rule.operation === Permission.ParameterOperation.LESS_THAN_OR_EQUAL) { - if (Bytes.toBigInt(value) > Bytes.toBigInt(rule.value)) { - return false - } - } - if (rule.operation === Permission.ParameterOperation.NOT_EQUAL) { - if (Bytes.isEqual(value, rule.value)) { - return false - } - } - if (rule.operation === Permission.ParameterOperation.GREATER_THAN_OR_EQUAL) { - if (Bytes.toBigInt(value) < Bytes.toBigInt(rule.value)) { - return false - } - } - } - - return true - } - - async supportedCall( - wallet: Address.Address, - chainId: number, - call: Payload.Call, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - if (isIncrementCall(call, sessionManagerAddress)) { - // Can sign increment usage calls - return true - } - - const permission = await this.findSupportedPermission(wallet, chainId, call, sessionManagerAddress, provider) - if (!permission) { - return false - } - return true - } - - async signCall( - wallet: Address.Address, - chainId: number, - payload: Payload.Calls, - callIdx: number, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - const call = payload.calls[callIdx]! - let permissionIndex: number - if (isIncrementCall(call, sessionManagerAddress)) { - // Permission check not required. Use the first permission - permissionIndex = 0 - } else { - // Find the valid permission for this call - const permission = await this.findSupportedPermission(wallet, chainId, call, sessionManagerAddress, provider) - if (!permission) { - // This covers the support check - throw new Error('Invalid permission') - } - permissionIndex = this.sessionPermissions.permissions.indexOf(permission) - if (permissionIndex === -1) { - // Unreachable - throw new Error('Invalid permission') - } - } - - // Sign it - const callHash = SessionSignature.hashPayloadWithCallIdx(wallet, payload, callIdx, chainId, sessionManagerAddress) - const sessionSignature = await this._privateKey.signDigest(Bytes.fromHex(callHash)) - return { - permissionIndex: BigInt(permissionIndex), - sessionSignature, - } - } - - private async readCurrentUsageLimit( - wallet: Address.Address, - sessionManagerAddress: Address.Address, - usageHash: Hex.Hex, - provider: Provider.Provider, - ): Promise { - const readData = AbiFunction.encodeData(Constants.GET_LIMIT_USAGE, [wallet, usageHash]) - const getUsageLimitResult = await provider.request({ - method: 'eth_call', - params: [ - { - to: sessionManagerAddress, - data: readData, - }, - 'latest', - ], - }) - const usageAmount = AbiFunction.decodeResult(Constants.GET_LIMIT_USAGE, getUsageLimitResult) - return { - usageHash, - usageAmount, - } - } - - async prepareIncrements( - wallet: Address.Address, - chainId: number, - calls: Payload.Call[], - sessionManagerAddress: Address.Address, - provider: Provider.Provider, - ): Promise { - const increments: { usageHash: Hex.Hex; increment: bigint }[] = [] - const usageValueHash = this.getValueUsageHash() - - // Always read the current value usage - const currentUsage = await this.readCurrentUsageLimit(wallet, sessionManagerAddress, usageValueHash, provider) - let valueUsed = currentUsage.usageAmount - - for (const call of calls) { - // Find matching permission - const perm = await this.findSupportedPermission(wallet, chainId, call, sessionManagerAddress, provider) - if (!perm) continue - - for (const [ruleIndex, rule] of perm.rules.entries()) { - if (!rule.cumulative) { - continue - } - // Extract the masked value - const callDataValue = Bytes.padRight( - Bytes.fromHex(call.data).slice(Number(rule.offset), Number(rule.offset) + 32), - 32, - ) - const value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!) - if (Bytes.toBigInt(value) === 0n) continue - - // Add to list - const usageHash = this.getPermissionUsageHash(perm, ruleIndex) - const existingIncrement = increments.find((i) => Hex.isEqual(i.usageHash, usageHash)) - if (existingIncrement) { - existingIncrement.increment += Bytes.toBigInt(value) - } else { - increments.push({ - usageHash, - increment: Bytes.toBigInt(value), - }) - } - } - - valueUsed += call.value - } - - // If no increments, return early - if (increments.length === 0 && valueUsed === 0n) { - return [] - } - - // Apply current usage limit to each increment - const updatedIncrements = await Promise.all( - increments.map(async ({ usageHash, increment }) => { - if (increment === 0n) return null - - const currentUsage = await this.readCurrentUsageLimit(wallet, sessionManagerAddress, usageHash, provider) - - // For value usage hash, validate against the limit - if (Hex.isEqual(usageHash, usageValueHash)) { - const totalValue = currentUsage.usageAmount + increment - if (totalValue > this.sessionPermissions.valueLimit) { - throw new Error('Value transaction validation failed') - } - } - - return { - usageHash, - usageAmount: currentUsage.usageAmount + increment, - } - }), - ).then((results) => results.filter((r): r is UsageLimit => r !== null)) - - // Finally, add the value usage if it's non-zero - if (valueUsed > 0n) { - updatedIncrements.push({ - usageHash: usageValueHash, - usageAmount: valueUsed, - }) - } - - return updatedIncrements - } -} diff --git a/packages/wallet/core/src/signers/session/implicit.ts b/packages/wallet/core/src/signers/session/implicit.ts deleted file mode 100644 index 8eb765808a..0000000000 --- a/packages/wallet/core/src/signers/session/implicit.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - Attestation, - Payload, - Signature as SequenceSignature, - SessionConfig, - SessionSignature, -} from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Bytes, Hex, Provider, Secp256k1, Signature } from 'ox' -import { MemoryPkStore, PkStore } from '../pk/index.js' -import { ImplicitSessionSigner, SessionSignerValidity } from './session.js' - -export type AttestationParams = Omit - -export class Implicit implements ImplicitSessionSigner { - private readonly _privateKey: PkStore - private readonly _identitySignature: SequenceSignature.RSY - public readonly address: Address.Address - - constructor( - privateKey: Hex.Hex | PkStore, - private readonly _attestation: Attestation.Attestation, - identitySignature: SequenceSignature.RSY | Hex.Hex, - private readonly _sessionManager: Address.Address, - ) { - this._privateKey = typeof privateKey === 'string' ? new MemoryPkStore(privateKey) : privateKey - this.address = this._privateKey.address() - if (this._attestation.approvedSigner !== this.address) { - throw new Error('Invalid attestation') - } - if (this._attestation.authData.issuedAt > BigInt(Math.floor(Date.now() / 1000))) { - throw new Error('Attestation issued in the future') - } - this._identitySignature = - typeof identitySignature === 'string' ? Signature.fromHex(identitySignature) : identitySignature - } - - get identitySigner(): Address.Address { - // Recover identity signer from attestions and identity signature - const attestationHash = Attestation.hash(this._attestation) - const identityPubKey = Secp256k1.recoverPublicKey({ payload: attestationHash, signature: this._identitySignature }) - return Address.fromPublicKey(identityPubKey) - } - - isValid(sessionTopology: SessionConfig.SessionsTopology, _chainId: number): SessionSignerValidity { - const implicitSigners = SessionConfig.getIdentitySigners(sessionTopology) - const thisIdentitySigner = this.identitySigner - if (!implicitSigners.some((s) => Address.isEqual(s, thisIdentitySigner))) { - return { isValid: false, invalidReason: 'Identity signer not found' } - } - const blacklist = SessionConfig.getImplicitBlacklist(sessionTopology) - if (blacklist?.some((b) => Address.isEqual(b, this.address))) { - return { isValid: false, invalidReason: 'Blacklisted' } - } - return { isValid: true } - } - - async supportedCall( - wallet: Address.Address, - _chainId: number, - call: Payload.Call, - _sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - if (!provider) { - throw new Error('Provider is required') - } - try { - // Call the acceptImplicitRequest function on the called contract - const encodedCallData = AbiFunction.encodeData(acceptImplicitRequestFunctionAbi, [ - wallet, - { - approvedSigner: this._attestation.approvedSigner, - identityType: Bytes.toHex(this._attestation.identityType), - issuerHash: Bytes.toHex(this._attestation.issuerHash), - audienceHash: Bytes.toHex(this._attestation.audienceHash), - applicationData: Bytes.toHex(this._attestation.applicationData), - authData: this._attestation.authData, - }, - { - to: call.to, - value: call.value, - data: call.data, - gasLimit: call.gasLimit, - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: BigInt(Payload.encodeBehaviorOnError(call.behaviorOnError)), - }, - ]) - const acceptImplicitRequestResult = await provider.request({ - method: 'eth_call', - params: [{ from: this._sessionManager, to: call.to, data: encodedCallData }, 'latest'], - }) - const acceptImplicitRequest = Hex.from( - AbiFunction.decodeResult(acceptImplicitRequestFunctionAbi, acceptImplicitRequestResult), - ) - const expectedResult = Bytes.toHex(Attestation.generateImplicitRequestMagic(this._attestation, wallet)) - return acceptImplicitRequest === expectedResult - } catch { - // console.log('implicit signer unsupported call', call, error) - return false - } - } - - async signCall( - wallet: Address.Address, - chainId: number, - payload: Payload.Calls, - callIdx: number, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ): Promise { - const call = payload.calls[callIdx]! - const isSupported = await this.supportedCall(wallet, chainId, call, sessionManagerAddress, provider) - if (!isSupported) { - throw new Error('Unsupported call') - } - const callHash = SessionSignature.hashPayloadWithCallIdx(wallet, payload, callIdx, chainId, sessionManagerAddress) - const sessionSignature = await this._privateKey.signDigest(Bytes.fromHex(callHash)) - return { - attestation: this._attestation, - identitySignature: this._identitySignature, - sessionSignature, - } - } -} - -const acceptImplicitRequestFunctionAbi = { - type: 'function', - name: 'acceptImplicitRequest', - inputs: [ - { name: 'wallet', type: 'address', internalType: 'address' }, - { - name: 'attestation', - type: 'tuple', - internalType: 'struct Attestation', - components: [ - { name: 'approvedSigner', type: 'address', internalType: 'address' }, - { name: 'identityType', type: 'bytes4', internalType: 'bytes4' }, - { name: 'issuerHash', type: 'bytes32', internalType: 'bytes32' }, - { name: 'audienceHash', type: 'bytes32', internalType: 'bytes32' }, - { name: 'applicationData', type: 'bytes', internalType: 'bytes' }, - { - internalType: 'struct AuthData', - name: 'authData', - type: 'tuple', - components: [ - { internalType: 'string', name: 'redirectUrl', type: 'string' }, - { internalType: 'uint64', name: 'issuedAt', type: 'uint64' }, - ], - }, - ], - }, - { - name: 'call', - type: 'tuple', - internalType: 'struct Payload.Call', - components: [ - { name: 'to', type: 'address', internalType: 'address' }, - { name: 'value', type: 'uint256', internalType: 'uint256' }, - { name: 'data', type: 'bytes', internalType: 'bytes' }, - { name: 'gasLimit', type: 'uint256', internalType: 'uint256' }, - { name: 'delegateCall', type: 'bool', internalType: 'bool' }, - { name: 'onlyFallback', type: 'bool', internalType: 'bool' }, - { name: 'behaviorOnError', type: 'uint256', internalType: 'uint256' }, - ], - }, - ], - outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], - stateMutability: 'view', -} as const diff --git a/packages/wallet/core/src/signers/session/index.ts b/packages/wallet/core/src/signers/session/index.ts deleted file mode 100644 index 87ef5e89ca..0000000000 --- a/packages/wallet/core/src/signers/session/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './explicit.js' -export * from './implicit.js' -export * from './session.js' diff --git a/packages/wallet/core/src/signers/session/session.ts b/packages/wallet/core/src/signers/session/session.ts deleted file mode 100644 index 08b2ade814..0000000000 --- a/packages/wallet/core/src/signers/session/session.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Constants, Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Hex, Provider } from 'ox' - -export type SessionSignerInvalidReason = - | 'Expired' - | 'Chain ID mismatch' - | 'Permission not found' - | 'Permission mismatch' - | 'Permission rule mismatch' - | 'Identity signer not found' - | 'Identity signer mismatch' - | 'Blacklisted' - -export type SessionSignerValidity = { - isValid: boolean - invalidReason?: SessionSignerInvalidReason -} - -export interface SessionSigner { - address: Address.Address | Promise - - /// Check if the signer is valid for the given topology and chainId - isValid: (sessionTopology: SessionConfig.SessionsTopology, chainId: number) => SessionSignerValidity - - /// Check if the signer supports the call - supportedCall: ( - wallet: Address.Address, - chainId: number, - call: Payload.Call, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ) => Promise - - /// Sign the call. Will throw if the call is not supported. - signCall: ( - wallet: Address.Address, - chainId: number, - payload: Payload.Calls, - callIdx: number, - sessionManagerAddress: Address.Address, - provider?: Provider.Provider, - ) => Promise -} - -export type UsageLimit = { - usageHash: Hex.Hex - usageAmount: bigint -} - -export interface ExplicitSessionSigner extends SessionSigner { - prepareIncrements: ( - wallet: Address.Address, - chainId: number, - calls: Payload.Call[], - sessionManagerAddress: Address.Address, - provider: Provider.Provider, - ) => Promise -} - -export interface ImplicitSessionSigner extends SessionSigner { - identitySigner: Address.Address -} - -export function isExplicitSessionSigner(signer: SessionSigner): signer is ExplicitSessionSigner { - return 'prepareIncrements' in signer -} - -export function isImplicitSessionSigner(signer: SessionSigner): signer is ImplicitSessionSigner { - return 'identitySigner' in signer -} - -export function isIncrementCall(call: Payload.Call, sessionManagerAddress: Address.Address): boolean { - return ( - Address.isEqual(call.to, sessionManagerAddress) && - Hex.size(call.data) >= 4 && - Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) - ) -} diff --git a/packages/wallet/core/src/state/arweave/arweave.ts b/packages/wallet/core/src/state/arweave/arweave.ts deleted file mode 100644 index b3cadb7340..0000000000 --- a/packages/wallet/core/src/state/arweave/arweave.ts +++ /dev/null @@ -1,115 +0,0 @@ -export interface Options { - readonly namespace?: string - readonly owners?: string[] - readonly arweaveUrl?: string - readonly graphqlUrl?: string - readonly rateLimitRetryDelayMs?: number -} - -export const defaults = { - namespace: 'Sequence-Sessions', - owners: ['AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM'], - arweaveUrl: 'https://arweave.net', - graphqlUrl: 'https://arweave.net/graphql', - rateLimitRetryDelayMs: 5 * 60 * 1000, -} - -export async function findItems( - filter: { [name: string]: undefined | string | string[] }, - options?: Options & { pageSize?: number; maxResults?: number }, -): Promise<{ [id: string]: { [tag: string]: string } }> { - const namespace = options?.namespace ?? defaults.namespace - const owners = options?.owners ?? defaults.owners - const graphqlUrl = options?.graphqlUrl ?? defaults.graphqlUrl - const rateLimitRetryDelayMs = options?.rateLimitRetryDelayMs ?? defaults.rateLimitRetryDelayMs - const pageSize = options?.pageSize ?? 100 - const maxResults = options?.maxResults - - const tags = Object.entries(filter).flatMap(([name, values]) => - values === undefined - ? [] - : [ - `{ name: "${namespace ? `${namespace}-${name}` : name}", values: [${typeof values === 'string' ? `"${values}"` : values.map((value) => `"${value}"`).join(', ')}] }`, - ], - ) - - const edges: Array<{ cursor: string; node: { id: string; tags: Array<{ name: string; value: string }> } }> = [] - - for (let hasNextPage = true; hasNextPage && (maxResults === undefined || edges.length < maxResults); ) { - const results = maxResults === undefined ? pageSize : Math.min(pageSize, maxResults - edges.length) - - const query = ` - query { - transactions(sort: HEIGHT_DESC, ${edges.length ? `first: ${results}, after: "${edges[edges.length - 1]!.cursor}"` : `first: ${results}`}, tags: [${tags.join(', ')}]${owners.length ? `, owners: [${owners.map((owner) => `"${owner}"`).join(', ')}]` : ''}) { - pageInfo { - hasNextPage - } - edges { - cursor - node { - id - tags { - name - value - } - } - } - } - } - ` - - let response: Response - while (true) { - response = await fetch(graphqlUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query }), - redirect: 'follow', - }) - if (response.status !== 429) { - break - } - console.warn( - `rate limited by ${graphqlUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`, - ) - await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs)) - } - - const { - data: { transactions }, - } = await response.json() - - edges.push(...transactions.edges.slice(0, results)) - - hasNextPage = transactions.pageInfo.hasNextPage - } - - return Object.fromEntries( - edges.map(({ node: { id, tags } }) => [ - id, - Object.fromEntries( - tags.map(({ name, value }) => [ - namespace && name.startsWith(`${namespace}-`) ? name.slice(namespace.length + 1) : name, - value, - ]), - ), - ]), - ) -} - -export async function fetchItem( - id: string, - rateLimitRetryDelayMs = defaults.rateLimitRetryDelayMs, - arweaveUrl = defaults.arweaveUrl, -): Promise { - while (true) { - const response = await fetch(`${arweaveUrl}/${id}`, { redirect: 'follow' }) - if (response.status !== 429) { - return response - } - console.warn( - `rate limited by ${arweaveUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`, - ) - await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs)) - } -} diff --git a/packages/wallet/core/src/state/arweave/index.ts b/packages/wallet/core/src/state/arweave/index.ts deleted file mode 100644 index b5f3400e68..0000000000 --- a/packages/wallet/core/src/state/arweave/index.ts +++ /dev/null @@ -1,1006 +0,0 @@ -import { - Address as SequenceAddress, - Config, - Context, - GenericTree, - Payload, - Signature, -} from '@0xsequence/wallet-primitives' -import { Address, Bytes, Hex, Signature as OxSignature } from 'ox' -import { Reader as ReaderInterface, normalizeAddressKeys } from '../index.js' -import { defaults, fetchItem, findItems, Options } from './arweave.js' -import type { - ArweaveConfigRecord, - ArweaveObject, - ArweavePayloadRecord, - ArweaveSapientSignatureRecord, - ArweaveSignatureRecord, - ArweaveTreeRecord, - ArweaveV1ConfigUpdatePayloadRecord, - ArweaveV2ConfigUpdatePayloadRecord, - ArweaveV3ConfigUpdatePayloadRecord, - ArweaveWalletRecord, - CallData, - ConfigData, - SignatureType, - TreeData, - V1ConfigData, - V2ConfigTreeData, - V2ConfigData, - V3ConfigTreeData, - V3ConfigData, -} from './schema.js' - -type ArweaveConfigUpdatePayloadRecord = - | ArweaveV1ConfigUpdatePayloadRecord - | ArweaveV2ConfigUpdatePayloadRecord - | ArweaveV3ConfigUpdatePayloadRecord - -type ItemTags = { [tag: string]: string } -type ItemEntry = { id: string; tags: ItemTags } - -type Witness = { - chainId: number - payload: Payload.Parented - signature: TSignature -} - -type WitnessMap = { - [wallet: Address.Address]: Witness -} - -type Candidate = { - nextImageHash: Hex.Hex - checkpoint: bigint - noChainId: boolean - signatureEntries: Map -} - -type TopologyChoice = { - topology: Config.Topology - weight: bigint - signatures: number - size: number - signatureMask: string -} - -type TopologyChoiceSet = { - slotCount: number - choices: Map -} - -const PLAIN_SIGNATURE_TYPES = ['eip-712', 'eth_sign', 'erc-1271'] satisfies SignatureType[] -const SAPIENT_SIGNATURE_TYPES = ['sapient', 'sapient-compact'] satisfies SignatureType[] -const PAYLOAD_VERSION_FILTER = { - 'Major-Version': '1', - 'Minor-Version': '2', -} as const - -function isSapientSignatureType(type: SignatureType): type is (typeof SAPIENT_SIGNATURE_TYPES)[number] { - return (SAPIENT_SIGNATURE_TYPES as readonly SignatureType[]).includes(type) -} - -function normalizeHex(value: Hex.Hex): Hex.Hex { - return Hex.fromBytes(Hex.toBytes(value)) -} - -function normalizeAddress(value: Address.Address): Address.Address { - return Address.checksum(value) -} - -function signerKey(address: Address.Address): string { - return normalizeAddress(address).toLowerCase() -} - -function sapientSignerKey(address: Address.Address, imageHash: Hex.Hex): string { - return `${signerKey(address)}:${normalizeHex(imageHash).toLowerCase()}` -} - -function sameAddress(left: Address.Address | undefined, right: Address.Address | undefined): boolean { - return left === undefined && right === undefined - ? true - : left !== undefined && right !== undefined && Address.isEqual(left, right) -} - -function mergeConfigurations(base: Config.Config | undefined, next: Config.Config): Config.Config { - if (!base) { - return next - } - - if ( - base.threshold !== next.threshold || - base.checkpoint !== next.checkpoint || - !sameAddress(base.checkpointer, next.checkpointer) - ) { - throw new Error('conflicting configuration metadata for the same image hash') - } - - return { - ...base, - topology: Config.mergeTopology(base.topology, next.topology), - } -} - -function fromCallData(call: CallData): Payload.Call { - return { - to: normalizeAddress(call.to), - value: BigInt(call.value), - data: normalizeHex(call.data), - gasLimit: BigInt(call.gasLimit), - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: call.behaviorOnError, - } -} - -function fromPayloadRecord(record: ArweavePayloadRecord): Payload.Parented { - switch (record['Payload-Type']) { - case 'calls': - return { - type: 'call', - space: BigInt(record.Space), - nonce: BigInt(record.Nonce), - calls: record.data.map(fromCallData), - } - - case 'message': - return { - type: 'message', - message: normalizeHex(record.data), - } - - case 'config update': - return { - type: 'config-update', - imageHash: normalizeHex(record['To-Config']), - } - - case 'digest': - return { - type: 'digest', - digest: normalizeHex(record.Digest), - } - } -} - -function fromTreeData(tree: TreeData): GenericTree.Tree { - if (typeof tree === 'string') { - return normalizeHex(tree) - } - - if (Array.isArray(tree)) { - return tree.map(fromTreeData) as GenericTree.Branch - } - - return { - type: 'leaf', - value: Bytes.fromHex(tree.data), - } -} - -function fromV2ConfigTree(tree: V2ConfigTreeData): Config.Topology { - if (typeof tree === 'string') { - return normalizeHex(tree) - } - - if (Array.isArray(tree)) { - return [fromV2ConfigTree(tree[0]), fromV2ConfigTree(tree[1])] - } - - if ('address' in tree) { - return { - type: 'signer', - address: normalizeAddress(tree.address), - weight: BigInt(tree.weight), - } - } - - if ('tree' in tree) { - return { - type: 'nested', - weight: BigInt(tree.weight), - threshold: BigInt(tree.threshold), - tree: fromV2ConfigTree(tree.tree), - } - } - - return { - type: 'subdigest', - digest: normalizeHex(tree.subdigest), - } -} - -function fromV3ConfigTree(tree: V3ConfigTreeData): Config.Topology { - if (typeof tree === 'string') { - return normalizeHex(tree) - } - - if (Array.isArray(tree)) { - return [fromV3ConfigTree(tree[0]), fromV3ConfigTree(tree[1])] - } - - if ('address' in tree) { - if ('imageHash' in tree) { - return { - type: 'sapient-signer', - address: normalizeAddress(tree.address), - weight: BigInt(tree.weight), - imageHash: normalizeHex(tree.imageHash), - } - } - - return { - type: 'signer', - address: normalizeAddress(tree.address), - weight: BigInt(tree.weight), - } - } - - if ('tree' in tree) { - return { - type: 'nested', - weight: BigInt(tree.weight), - threshold: BigInt(tree.threshold), - tree: fromV3ConfigTree(tree.tree), - } - } - - return { - type: 'isAnyAddress' in tree && tree.isAnyAddress ? 'any-address-subdigest' : 'subdigest', - digest: normalizeHex(tree.subdigest), - } -} - -function fromConfigData(version: '1' | '2' | '3', data: ConfigData): Config.Config { - switch (version) { - case '1': { - const v1Data = data as V1ConfigData - if (v1Data.signers.length === 0) { - throw new Error('legacy configuration cannot be empty') - } - - return { - threshold: BigInt(v1Data.threshold), - checkpoint: 0n, - topology: Config.flatLeavesToTopology( - v1Data.signers.map((signer) => ({ - type: 'signer' as const, - address: normalizeAddress(signer.address), - weight: BigInt(signer.weight), - })), - ), - } - } - - case '2': { - const v2Data = data as V2ConfigData - return { - threshold: BigInt(v2Data.threshold), - checkpoint: BigInt(v2Data.checkpoint), - topology: fromV2ConfigTree(v2Data.tree), - } - } - - case '3': { - const v3Data = data as V3ConfigData - return { - threshold: BigInt(v3Data.threshold), - checkpoint: BigInt(v3Data.checkpoint), - checkpointer: v3Data.checkpointer ? normalizeAddress(v3Data.checkpointer) : undefined, - topology: fromV3ConfigTree(v3Data.tree), - } - } - } -} - -function fromConfigCarrier( - record: - | ArweaveConfigRecord - | ArweaveConfigUpdatePayloadRecord - | Extract, -): Config.Config { - switch (record.Type) { - case 'config': - return fromConfigData(record.Version, record.data) - - case 'payload': - return fromConfigData(record['To-Version'], record.data) - - case 'wallet': - return fromConfigData(record['Deploy-Version'], record.data) - } -} - -function fromSignatureRecord(record: ArweaveSignatureRecord): Signature.SignatureOfSignerLeaf { - switch (record['Signature-Type']) { - case 'eip-712': - return { type: 'hash', ...OxSignature.from(record.data) } - - case 'eth_sign': - return { type: 'eth_sign', ...OxSignature.from(record.data) } - - case 'erc-1271': - return { - type: 'erc1271', - address: normalizeAddress(record.Signer), - data: normalizeHex(record.data), - } - - case 'sapient': - case 'sapient-compact': - throw new Error(`unexpected sapient signature type ${record['Signature-Type']}`) - } -} - -function fromSapientSignatureRecord(record: ArweaveSapientSignatureRecord): Signature.SignatureOfSapientSignerLeaf { - switch (record['Signature-Type']) { - case 'sapient': - return { - type: 'sapient', - address: normalizeAddress(record.Signer), - data: normalizeHex(record.data), - } - - case 'sapient-compact': - return { - type: 'sapient_compact', - address: normalizeAddress(record.Signer), - data: normalizeHex(record.data), - } - - case 'eip-712': - case 'eth_sign': - case 'erc-1271': - throw new Error(`unexpected plain signature type ${record['Signature-Type']}`) - } -} - -function inferContext(record: ArweaveWalletRecord): Context.Context | undefined { - if ('Context-Factory' in record) { - return { - factory: normalizeAddress(record['Context-Factory']), - stage1: normalizeAddress(record['Context-Stage-1']), - stage2: normalizeAddress(record['Context-Stage-2']), - creationCode: normalizeHex(record['Context-Creation-Code']), - } - } - - const wallet = normalizeAddress(record.Wallet) - const imageHashBytes = Bytes.fromHex(normalizeHex(record['Deploy-Config'])) - - const knownContext = Context.KnownContexts.find((context) => - Address.isEqual(SequenceAddress.from(imageHashBytes, context), wallet), - ) - - if (!knownContext) { - return undefined - } - - return { - factory: knownContext.factory, - stage1: knownContext.stage1, - stage2: knownContext.stage2, - creationCode: knownContext.creationCode, - } -} - -function toRecoveredLikeTopology(topology: Config.Topology): Config.Topology { - if (Config.isNode(topology)) { - return [toRecoveredLikeTopology(topology[0]), toRecoveredLikeTopology(topology[1])] - } - - if (Config.isSignerLeaf(topology)) { - return topology.signature ? { ...topology, signed: true } : topology - } - - if (Config.isSapientSignerLeaf(topology)) { - if (topology.signature) { - return { ...topology, signed: true } - } - - return Hex.fromBytes(Config.hashConfiguration(topology)) - } - - if (Config.isNestedLeaf(topology)) { - return { - ...topology, - tree: toRecoveredLikeTopology(topology.tree), - } - } - - return topology -} - -function fillTopologyWithSignatures( - configuration: Config.Config, - signatures: Map, -): Config.Topology { - return Signature.fillLeaves(configuration.topology, (leaf) => { - if (Config.isSapientSignerLeaf(leaf)) { - const signature = signatures.get(sapientSignerKey(leaf.address, leaf.imageHash)) - return signature && Signature.isSignatureOfSapientSignerLeaf(signature) ? signature : undefined - } - - const signature = signatures.get(signerKey(leaf.address)) - return signature && !Signature.isSignatureOfSapientSignerLeaf(signature) ? signature : undefined - }) -} - -function clampWeight(weight: bigint, cap: bigint): bigint { - return weight > cap ? cap : weight -} - -function zeroMask(length: number): string { - return '0'.repeat(length) -} - -function compareChoices(left: TopologyChoice, right: TopologyChoice): number { - if (left.signatures !== right.signatures) { - return left.signatures - right.signatures - } - - if (left.size !== right.size) { - return left.size - right.size - } - - if (left.signatureMask !== right.signatureMask) { - return left.signatureMask > right.signatureMask ? -1 : 1 - } - - return 0 -} - -function dominatesChoice(left: TopologyChoice, right: TopologyChoice): boolean { - return ( - left.weight >= right.weight && - left.signatures <= right.signatures && - left.size <= right.size && - left.signatureMask >= right.signatureMask - ) -} - -function makeChoice( - topology: Config.Topology, - weight: bigint, - signatures: number, - signatureMask: string, -): TopologyChoice { - return { - topology, - weight, - signatures, - size: Signature.encodeTopology(topology).length, - signatureMask, - } -} - -function addChoice(choiceSet: TopologyChoiceSet, choice: TopologyChoice): void { - const key = choice.weight.toString() - const existing = choiceSet.choices.get(key) - - if (!existing || compareChoices(choice, existing) < 0) { - choiceSet.choices.set(key, choice) - } -} - -function pruneChoiceSet(choiceSet: TopologyChoiceSet): TopologyChoiceSet { - const choices = [...choiceSet.choices.values()] - const pruned = new Map() - - for (const candidate of choices) { - const dominated = choices.some((other) => other !== candidate && dominatesChoice(other, candidate)) - if (!dominated) { - pruned.set(candidate.weight.toString(), candidate) - } - } - - return { ...choiceSet, choices: pruned } -} - -function buildTopologyChoiceSet(topology: Config.Topology, cap: bigint): TopologyChoiceSet { - if (Signature.isSignedSignerLeaf(topology)) { - const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() } - addChoice(choices, makeChoice({ type: 'signer', address: topology.address, weight: topology.weight }, 0n, 0, '0')) - - if (topology.weight > 0n) { - addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1')) - } - - return choices - } - - if (Signature.isSignedSapientSignerLeaf(topology)) { - const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() } - addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '0')) - - if (topology.weight > 0n) { - addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1')) - } - - return choices - } - - if (Config.isSignerLeaf(topology)) { - return { - slotCount: 0, - choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]), - } - } - - if (Config.isSapientSignerLeaf(topology)) { - return { - slotCount: 0, - choices: new Map([[0n.toString(), makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '')]]), - } - } - - if (Config.isSubdigestLeaf(topology) || Config.isAnyAddressSubdigestLeaf(topology) || Config.isNodeLeaf(topology)) { - return { - slotCount: 0, - choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]), - } - } - - if (Config.isNestedLeaf(topology)) { - const treeChoices = buildTopologyChoiceSet(topology.tree, topology.threshold) - const choices: TopologyChoiceSet = { slotCount: treeChoices.slotCount, choices: new Map() } - addChoice( - choices, - makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(treeChoices.slotCount)), - ) - - const satisfied = treeChoices.choices.get(topology.threshold.toString()) - if (satisfied && topology.weight > 0n) { - addChoice( - choices, - makeChoice( - { ...topology, tree: satisfied.topology }, - clampWeight(topology.weight, cap), - satisfied.signatures, - satisfied.signatureMask, - ), - ) - } - - return pruneChoiceSet(choices) - } - - const leftChoices = buildTopologyChoiceSet(topology[0], cap) - const rightChoices = buildTopologyChoiceSet(topology[1], cap) - const choices: TopologyChoiceSet = { - slotCount: leftChoices.slotCount + rightChoices.slotCount, - choices: new Map(), - } - - addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(choices.slotCount))) - - for (const leftChoice of leftChoices.choices.values()) { - for (const rightChoice of rightChoices.choices.values()) { - addChoice( - choices, - makeChoice( - [leftChoice.topology, rightChoice.topology], - clampWeight(leftChoice.weight + rightChoice.weight, cap), - leftChoice.signatures + rightChoice.signatures, - `${leftChoice.signatureMask}${rightChoice.signatureMask}`, - ), - ) - } - } - - return pruneChoiceSet(choices) -} - -function minimizeTopologyForThreshold(topology: Config.Topology, threshold: bigint): Config.Topology | undefined { - return buildTopologyChoiceSet(topology, threshold).choices.get(threshold.toString())?.topology -} - -export class Reader implements ReaderInterface { - constructor(private readonly options: Options = defaults) {} - - private async findEntries( - filter: { [name: string]: undefined | string | string[] }, - options?: { maxResults?: number }, - ): Promise { - const items = await findItems(filter, { ...this.options, maxResults: options?.maxResults }) - return Object.entries(items).map(([id, tags]) => ({ id, tags })) - } - - private async loadRecord(entry: ItemEntry): Promise { - const response = await fetchItem(entry.id, this.options.rateLimitRetryDelayMs, this.options.arweaveUrl) - if (!response.ok) { - throw new Error(`failed to fetch arweave item ${entry.id}: ${response.status}`) - } - - const data = - entry.tags['Content-Type'] === 'application/json' ? await response.json() : (await response.text()).trim() - return { ...entry.tags, data } as T - } - - private async findFirstRecord(filter: { - [name: string]: undefined | string | string[] - }): Promise { - const [entry] = await this.findEntries(filter, { maxResults: 1 }) - return entry ? this.loadRecord(entry) : undefined - } - - async getConfiguration(imageHash: Hex.Hex): Promise { - const normalizedImageHash = normalizeHex(imageHash) - const configEntries = await this.findEntries({ Type: 'config', Config: normalizedImageHash }) - - let configuration: Config.Config | undefined - - for (const record of await Promise.all(configEntries.map((entry) => this.loadRecord(entry)))) { - configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) - } - - if (configuration) { - return configuration - } - - const [walletEntries, payloadEntries] = await Promise.all([ - this.findEntries({ - Type: 'wallet', - 'Deploy-Config': normalizedImageHash, - 'Deploy-Config-Attached': 'true', - }), - this.findEntries({ - Type: 'payload', - ...PAYLOAD_VERSION_FILTER, - 'Payload-Type': 'config update', - 'To-Config': normalizedImageHash, - }), - ]) - - for (const record of await Promise.all(walletEntries.map((entry) => this.loadRecord(entry)))) { - if (record['Deploy-Config-Attached'] === 'true') { - configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) - } - } - - for (const record of await Promise.all( - payloadEntries.map((entry) => this.loadRecord(entry)), - )) { - configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) - } - - return configuration - } - - async getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - const record = await this.findFirstRecord({ - Type: 'wallet', - Wallet: normalizeAddress(wallet), - }) - - if (!record) { - return undefined - } - - const context = inferContext(record) - if (!context) { - return undefined - } - - return { - imageHash: normalizeHex(record['Deploy-Config']), - context, - } - } - - private async getWalletsGeneric( - filter: { [name: string]: undefined | string | string[] }, - signatureFrom: (record: TRecord) => TSignature, - ): Promise> { - const payloads = new Map< - Hex.Hex, - Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> - >() - const response: WitnessMap = {} - - for (const entry of await this.findEntries(filter)) { - const wallet = normalizeAddress(entry.tags.Wallet as Address.Address) - if (response[wallet]) { - continue - } - - const record = await this.loadRecord(entry) - const subdigest = normalizeHex(record.Subdigest) - const payloadPromise = payloads.get(subdigest) ?? this.getPayload(subdigest) - payloads.set(subdigest, payloadPromise) - const payload = await payloadPromise - - if (!payload) { - continue - } - - response[wallet] = { - chainId: payload.chainId, - payload: payload.payload, - signature: signatureFrom(record), - } - } - - return normalizeAddressKeys(response) - } - - async getWallets(signer: Address.Address): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - }> { - return this.getWalletsGeneric( - { - Type: 'signature', - Signer: normalizeAddress(signer), - Witness: 'true', - 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], - }, - fromSignatureRecord, - ) - } - - async getWalletsForSapient( - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - }> { - return this.getWalletsGeneric( - { - Type: 'signature', - Signer: normalizeAddress(signer), - 'Image-Hash': normalizeHex(imageHash), - Witness: 'true', - 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], - }, - fromSapientSignatureRecord, - ) - } - - private async getWitnessGeneric( - filter: { [name: string]: undefined | string | string[] }, - signatureFrom: (record: TRecord) => TSignature, - ): Promise | undefined> { - const entries = await this.findEntries(filter) - - for (const entry of entries) { - const record = await this.loadRecord(entry) - const payload = await this.getPayload(record.Subdigest) - if (!payload) { - continue - } - - return { - chainId: payload.chainId, - payload: payload.payload, - signature: signatureFrom(record), - } - } - } - - getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> { - return this.getWitnessGeneric( - { - Type: 'signature', - Wallet: normalizeAddress(wallet), - Signer: normalizeAddress(signer), - Witness: 'true', - 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], - }, - fromSignatureRecord, - ) - } - - getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined - > { - return this.getWitnessGeneric( - { - Type: 'signature', - Wallet: normalizeAddress(wallet), - Signer: normalizeAddress(signer), - 'Image-Hash': normalizeHex(imageHash), - Witness: 'true', - 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], - }, - fromSapientSignatureRecord, - ) - } - - async getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): Promise> { - const normalizedWallet = normalizeAddress(wallet) - const signatureRecords = new Map>() - const loadSignatureRecord = (entry: ItemEntry): Promise => { - const cached = signatureRecords.get(entry.id) - if (cached) { - return cached - } - - const promise = isSapientSignatureType(entry.tags['Signature-Type'] as SignatureType) - ? this.loadRecord(entry) - : this.loadRecord(entry) - - signatureRecords.set(entry.id, promise) - return promise - } - - const updates: Array<{ imageHash: Hex.Hex; signature: Signature.RawSignature }> = [] - let currentImageHash = normalizeHex(fromImageHash) - - top: while (true) { - const currentConfig = await this.getConfiguration(currentImageHash) - if (!currentConfig) { - return updates - } - - const { signers, sapientSigners } = Config.getSigners(currentConfig) - const [plainEntries, sapientEntries] = await Promise.all([ - signers.length - ? this.findEntries({ - Type: 'config update', - Wallet: normalizedWallet, - Signer: signers.map(normalizeAddress), - 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], - }) - : Promise.resolve([]), - Promise.all( - sapientSigners.map(({ address, imageHash }) => - this.findEntries({ - Type: 'config update', - Wallet: normalizedWallet, - Signer: normalizeAddress(address), - 'Image-Hash': normalizeHex(imageHash), - 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], - }), - ), - ), - ]) - - const candidates = new Map() - const addCandidate = (entry: ItemEntry, key: string) => { - const checkpoint = BigInt(entry.tags['To-Checkpoint']!) - if (checkpoint <= currentConfig.checkpoint) { - return - } - - const nextImageHash = normalizeHex(entry.tags['To-Config'] as Hex.Hex) - const candidateKey = `${checkpoint}:${nextImageHash.toLowerCase()}` - const candidate = candidates.get(candidateKey) - - if (candidate) { - if (!candidate.signatureEntries.has(key)) { - candidate.signatureEntries.set(key, entry) - } - - return - } - - candidates.set(candidateKey, { - nextImageHash, - checkpoint, - noChainId: entry.tags['Major-Version'] !== '1', - signatureEntries: new Map([[key, entry]]), - }) - } - - for (const entry of plainEntries) { - addCandidate(entry, signerKey(entry.tags.Signer as Address.Address)) - } - - for (const entries of sapientEntries) { - for (const entry of entries) { - addCandidate( - entry, - sapientSignerKey(entry.tags.Signer as Address.Address, entry.tags['Image-Hash'] as Hex.Hex), - ) - } - } - - const sortedCandidates = [...candidates.values()].sort((left, right) => { - if (left.checkpoint === right.checkpoint) { - return 0 - } - - if (options?.allUpdates) { - return left.checkpoint < right.checkpoint ? -1 : 1 - } - - return left.checkpoint > right.checkpoint ? -1 : 1 - }) - - for (const candidate of sortedCandidates) { - const signatures = new Map() - const records = await Promise.all([...candidate.signatureEntries.values()].map(loadSignatureRecord)) - - for (const record of records) { - if (isSapientSignatureType(record['Signature-Type'])) { - const sapientRecord = record as ArweaveSapientSignatureRecord - signatures.set( - sapientSignerKey(sapientRecord.Signer, sapientRecord['Image-Hash']), - fromSapientSignatureRecord(sapientRecord), - ) - } else { - signatures.set(signerKey(record.Signer), fromSignatureRecord(record as ArweaveSignatureRecord)) - } - } - - const filledTopology = fillTopologyWithSignatures(currentConfig, signatures) - const minimalTopology = minimizeTopologyForThreshold(filledTopology, currentConfig.threshold) - if (!minimalTopology) { - continue - } - - const topology = toRecoveredLikeTopology(minimalTopology) - const { weight } = Config.getWeight(topology, () => false) - if (weight < currentConfig.threshold) { - continue - } - - updates.push({ - imageHash: candidate.nextImageHash, - signature: { - noChainId: candidate.noChainId, - configuration: { - threshold: currentConfig.threshold, - checkpoint: currentConfig.checkpoint, - checkpointer: currentConfig.checkpointer, - topology, - }, - }, - }) - - currentImageHash = candidate.nextImageHash - continue top - } - - return updates - } - } - - async getTree(imageHash: Hex.Hex): Promise { - const record = await this.findFirstRecord({ - Type: 'tree', - Tree: normalizeHex(imageHash), - }) - - return record ? fromTreeData(record.data) : undefined - } - - async getPayload( - digest: Hex.Hex, - ): Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> { - const record = await this.findFirstRecord({ - Type: 'payload', - ...PAYLOAD_VERSION_FILTER, - Payload: normalizeHex(digest), - }) - - if (!record) { - return undefined - } - - return { - chainId: Number(record['Chain-ID']), - payload: fromPayloadRecord(record), - wallet: normalizeAddress(record.Address), - } - } -} diff --git a/packages/wallet/core/src/state/arweave/schema.ts b/packages/wallet/core/src/state/arweave/schema.ts deleted file mode 100644 index 059579cb08..0000000000 --- a/packages/wallet/core/src/state/arweave/schema.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { Address, Hex } from 'ox' - -export type BooleanString = 'true' | 'false' -export type IntegerString = `${number}` -export type ConfigVersion = '1' | '2' | '3' -export type WalletMajorVersion = '1' | '2' -export type PayloadType = 'calls' | 'message' | 'config update' | 'digest' -export type SignatureType = 'eip-712' | 'eth_sign' | 'erc-1271' | 'sapient' | 'sapient-compact' -export type BehaviorOnError = 'ignore' | 'revert' | 'abort' - -export interface V1ConfigSignerData { - weight: number - address: Address.Address -} - -export interface V1ConfigData { - threshold: number - signers: Array -} - -export interface V2ConfigAddressLeafData { - weight: number - address: Address.Address -} - -export interface V2ConfigNestedLeafData { - weight: number - threshold: number - tree: V2ConfigTreeData -} - -export interface V2ConfigSubdigestLeafData { - subdigest: Hex.Hex -} - -export type V2ConfigTreeData = - | Hex.Hex - | [V2ConfigTreeData, V2ConfigTreeData] - | V2ConfigAddressLeafData - | V2ConfigNestedLeafData - | V2ConfigSubdigestLeafData - -export interface V2ConfigData { - threshold: number - checkpoint: number - tree: V2ConfigTreeData -} - -export interface V3ConfigAddressLeafData { - weight: number - address: Address.Address -} - -export interface V3ConfigSapientSignerLeafData { - weight: number - address: Address.Address - imageHash: Hex.Hex -} - -export interface V3ConfigNestedLeafData { - weight: number - threshold: number - tree: V3ConfigTreeData -} - -export interface V3ConfigSubdigestLeafData { - subdigest: Hex.Hex -} - -export interface V3ConfigAnyAddressSubdigestLeafData { - subdigest: Hex.Hex - isAnyAddress: true -} - -export type V3ConfigTreeData = - | Hex.Hex - | [V3ConfigTreeData, V3ConfigTreeData] - | V3ConfigAddressLeafData - | V3ConfigSapientSignerLeafData - | V3ConfigNestedLeafData - | V3ConfigSubdigestLeafData - | V3ConfigAnyAddressSubdigestLeafData - -export interface V3ConfigData { - threshold: number - checkpoint: IntegerString - tree: V3ConfigTreeData - checkpointer?: Address.Address -} - -export type ConfigData = V1ConfigData | V2ConfigData | V3ConfigData - -export interface TreeLeafData { - data: Hex.Hex -} - -export type TreeData = Hex.Hex | TreeLeafData | Array - -export interface CallData { - to: Address.Address - value: IntegerString - data: Hex.Hex - gasLimit: IntegerString - delegateCall: boolean - onlyFallback: boolean - behaviorOnError: BehaviorOnError -} - -export interface ArweaveRecordBase< - TType extends string, - TMajorVersion extends string, - TMinorVersion extends string, - TContentType extends string, -> { - Type: TType - 'Major-Version': TMajorVersion - 'Minor-Version': TMinorVersion - 'Content-Type': TContentType -} - -export interface ArweaveConfigRecordBase extends ArweaveRecordBase<'config', '1', '0', 'application/json'> { - Config: Hex.Hex - Complete: BooleanString - 'Signers-Count': IntegerString - 'Signers-Bloom': Hex.Hex -} - -export interface ArweaveV1ConfigRecord extends ArweaveConfigRecordBase { - Version: '1' - data: V1ConfigData -} - -export interface ArweaveV2ConfigRecord extends ArweaveConfigRecordBase { - Version: '2' - data: V2ConfigData -} - -export interface ArweaveV3ConfigRecord extends ArweaveConfigRecordBase { - Version: '3' - data: V3ConfigData -} - -export type ArweaveConfigRecord = ArweaveV1ConfigRecord | ArweaveV2ConfigRecord | ArweaveV3ConfigRecord - -export interface ArweaveTreeRecord extends ArweaveRecordBase<'tree', '1', '0', 'application/json'> { - Tree: Hex.Hex - Complete: BooleanString - data: TreeData -} - -export interface ArweaveWalletRecordBase extends ArweaveRecordBase< - 'wallet', - WalletMajorVersion, - '0', - 'application/json' -> { - Wallet: Address.Address - 'Deploy-Config': Hex.Hex - 'Deploy-Version': ConfigVersion - 'Deploy-Config-Attached': BooleanString - 'Deploy-Config-Complete': BooleanString - 'Deploy-Signers-Count': IntegerString - 'Deploy-Signers-Bloom': Hex.Hex -} - -export interface ArweaveWalletDefaultContext { - 'Major-Version': '1' -} - -export interface ArweaveWalletCustomContext { - 'Major-Version': '2' - 'Context-Factory': Address.Address - 'Context-Stage-1': Address.Address - 'Context-Stage-2': Address.Address - 'Context-Guest': Address.Address - 'Context-Creation-Code': Hex.Hex -} - -export interface ArweaveWalletDetachedData { - 'Deploy-Config-Attached': 'false' - 'Deploy-Config-Complete': 'false' - data: null -} - -export interface ArweaveWalletWithV1DeployConfig { - 'Deploy-Config-Attached': 'true' - 'Deploy-Version': '1' - data: V1ConfigData -} - -export interface ArweaveWalletWithV2DeployConfig { - 'Deploy-Config-Attached': 'true' - 'Deploy-Version': '2' - data: V2ConfigData -} - -export interface ArweaveWalletWithV3DeployConfig { - 'Deploy-Config-Attached': 'true' - 'Deploy-Version': '3' - data: V3ConfigData -} - -export type ArweaveWalletRecord = ArweaveWalletRecordBase & - (ArweaveWalletDefaultContext | ArweaveWalletCustomContext) & - ( - | ArweaveWalletDetachedData - | ArweaveWalletWithV1DeployConfig - | ArweaveWalletWithV2DeployConfig - | ArweaveWalletWithV3DeployConfig - ) - -export interface ArweavePayloadRecordBase extends ArweaveRecordBase<'payload', '1', '2', 'application/json'> { - Payload: Hex.Hex - Address: Address.Address - 'Chain-ID': IntegerString - 'Payload-Type': PayloadType -} - -export interface ArweaveCallsPayloadRecord extends ArweavePayloadRecordBase { - 'Payload-Type': 'calls' - Space: IntegerString - Nonce: IntegerString - data: Array -} - -export interface ArweaveMessagePayloadRecord extends ArweavePayloadRecordBase { - 'Payload-Type': 'message' - data: Hex.Hex -} - -export interface ArweaveConfigUpdatePayloadRecordBase extends ArweavePayloadRecordBase { - 'Payload-Type': 'config update' - 'To-Config': Hex.Hex - 'To-Checkpoint': IntegerString - 'To-Config-Complete': BooleanString - 'To-Signers-Count': IntegerString - 'To-Signers-Bloom': Hex.Hex -} - -export interface ArweaveV1ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { - 'To-Version': '1' - data: V1ConfigData -} - -export interface ArweaveV2ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { - 'To-Version': '2' - data: V2ConfigData -} - -export interface ArweaveV3ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { - 'To-Version': '3' - data: V3ConfigData -} - -export interface ArweaveDigestPayloadRecord extends ArweavePayloadRecordBase { - 'Payload-Type': 'digest' - Digest: Hex.Hex - data: null -} - -export type ArweavePayloadRecord = - | ArweaveCallsPayloadRecord - | ArweaveMessagePayloadRecord - | ArweaveV1ConfigUpdatePayloadRecord - | ArweaveV2ConfigUpdatePayloadRecord - | ArweaveV3ConfigUpdatePayloadRecord - | ArweaveDigestPayloadRecord - -export interface ArweaveConfigUpdateTags { - Type: 'config update' - 'To-Config': Hex.Hex - 'To-Checkpoint': IntegerString - 'To-Config-Complete': BooleanString - 'To-Signers-Count': IntegerString - 'To-Signers-Bloom': Hex.Hex -} - -export interface ArweavePlainSignatureTags { - Type: 'signature' -} - -export interface ArweaveSignatureDigestTags { - 'Major-Version': '1' - Digest: Hex.Hex -} - -export interface ArweaveSignatureSubdigestTags { - 'Major-Version': '2' -} - -export interface ArweaveSignatureBlockTags { - 'Block-Number': IntegerString - 'Block-Hash': Hex.Hex -} - -export interface ArweaveSignatureRecordBase extends ArweaveRecordBase< - 'signature' | 'config update', - '1' | '2', - '0', - 'text/plain' -> { - 'Signature-Type': SignatureType - Signer: Address.Address - Subdigest: Hex.Hex - Wallet: Address.Address - 'Chain-ID': IntegerString - Witness: BooleanString - data: Hex.Hex -} - -export type ArweaveSignatureRecord = - | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureDigestTags) - | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureSubdigestTags) - | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureDigestTags & ArweaveSignatureBlockTags) - | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureSubdigestTags & ArweaveSignatureBlockTags) - | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureDigestTags) - | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureSubdigestTags) - | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureDigestTags & ArweaveSignatureBlockTags) - | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureSubdigestTags & ArweaveSignatureBlockTags) - -export interface ArweaveSapientSignatureRecordBase extends ArweaveRecordBase< - 'signature' | 'config update', - '2', - '0', - 'text/plain' -> { - 'Signature-Type': SignatureType - Signer: Address.Address - 'Image-Hash': Hex.Hex - Subdigest: Hex.Hex - Wallet: Address.Address - 'Chain-ID': IntegerString - 'Block-Number': IntegerString - 'Block-Hash': Hex.Hex - Witness: BooleanString - data: Hex.Hex -} - -export type ArweaveSapientSignatureRecord = - | (ArweaveSapientSignatureRecordBase & ArweavePlainSignatureTags) - | (ArweaveSapientSignatureRecordBase & ArweaveConfigUpdateTags) - -export interface ArweaveMigrationRecord extends ArweaveRecordBase<'migration', '1', '0', 'text/plain'> { - Migration: Address.Address - 'Chain-ID': IntegerString - 'From-Version': ConfigVersion - 'From-Config': Hex.Hex - 'From-Config-Complete': BooleanString - 'From-Signers-Count': IntegerString - 'From-Signers-Bloom': Hex.Hex - 'To-Version': ConfigVersion - 'To-Config': Hex.Hex - 'To-Config-Complete': BooleanString - 'To-Signers-Count': IntegerString - 'To-Signers-Bloom': Hex.Hex - Executor: Address.Address - data: Hex.Hex -} - -export type ArweaveRecord = - | ArweaveConfigRecord - | ArweaveTreeRecord - | ArweaveWalletRecord - | ArweavePayloadRecord - | ArweaveSignatureRecord - | ArweaveSapientSignatureRecord - | ArweaveMigrationRecord - -export type ArweaveObject = ArweaveRecord diff --git a/packages/wallet/core/src/state/cached.ts b/packages/wallet/core/src/state/cached.ts deleted file mode 100644 index 401611eb22..0000000000 --- a/packages/wallet/core/src/state/cached.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { Address, Hex } from 'ox' -import { MaybePromise, Provider } from './index.js' -import { Config, Context, GenericTree, Payload, Signature } from '@0xsequence/wallet-primitives' -import { normalizeAddressKeys } from './utils.js' - -export class Cached implements Provider { - constructor( - private readonly args: { - readonly source: Provider - readonly cache: Provider - }, - ) {} - - async getConfiguration(imageHash: Hex.Hex): Promise { - const cached = await this.args.cache.getConfiguration(imageHash) - if (cached) { - return cached - } - const config = await this.args.source.getConfiguration(imageHash) - - if (config) { - await this.args.cache.saveConfiguration(config) - } - - return config - } - - async getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - const cached = await this.args.cache.getDeploy(wallet) - if (cached) { - return cached - } - const deploy = await this.args.source.getDeploy(wallet) - if (deploy) { - await this.args.cache.saveDeploy(deploy.imageHash, deploy.context) - } - return deploy - } - - async getWallets(signer: Address.Address): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - }> { - // Get both from cache and source - const cached = normalizeAddressKeys(await this.args.cache.getWallets(signer)) - const source = normalizeAddressKeys(await this.args.source.getWallets(signer)) - - // Merge and deduplicate - const deduplicated = { ...cached, ...source } - - // Sync values to source that are not in cache, and vice versa - for (const [walletAddress, data] of Object.entries(deduplicated)) { - Address.assert(walletAddress) - - if (!source[walletAddress]) { - await this.args.source.saveWitnesses(walletAddress, data.chainId, data.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: data.signature, - }) - } - if (!cached[walletAddress]) { - await this.args.cache.saveWitnesses(walletAddress, data.chainId, data.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: data.signature, - }) - } - } - - return deduplicated - } - - async getWalletsForSapient( - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - }> { - const cached = await this.args.cache.getWalletsForSapient(signer, imageHash) - const source = await this.args.source.getWalletsForSapient(signer, imageHash) - - const deduplicated = { ...cached, ...source } - - // Sync values to source that are not in cache, and vice versa - for (const [wallet, data] of Object.entries(deduplicated)) { - const walletAddress = Address.from(wallet) - if (!source[walletAddress]) { - await this.args.source.saveWitnesses(walletAddress, data.chainId, data.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: data.signature, - }) - } - if (!cached[walletAddress]) { - await this.args.cache.saveWitnesses(walletAddress, data.chainId, data.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: data.signature, - }) - } - } - - return deduplicated - } - - async getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> { - const cached = await this.args.cache.getWitnessFor(wallet, signer) - if (cached) { - return cached - } - - const source = await this.args.source.getWitnessFor(wallet, signer) - if (source) { - await this.args.cache.saveWitnesses(wallet, source.chainId, source.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: source.signature, - }) - } - - return source - } - - async getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined - > { - const cached = await this.args.cache.getWitnessForSapient(wallet, signer, imageHash) - if (cached) { - return cached - } - const source = await this.args.source.getWitnessForSapient(wallet, signer, imageHash) - if (source) { - await this.args.cache.saveWitnesses(wallet, source.chainId, source.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: source.signature, - }) - } - return source - } - - async getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): Promise> { - // TODO: Cache this - return this.args.source.getConfigurationUpdates(wallet, fromImageHash, options) - } - - async getTree(rootHash: Hex.Hex): Promise { - const cached = await this.args.cache.getTree(rootHash) - if (cached) { - return cached - } - const source = await this.args.source.getTree(rootHash) - if (source) { - await this.args.cache.saveTree(source) - } - return source - } - - // Write methods are not cached, they are directly forwarded to the source - saveWallet(deployConfiguration: Config.Config, context: Context.Context): MaybePromise { - return this.args.source.saveWallet(deployConfiguration, context) - } - - saveWitnesses( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signatures: Signature.RawTopology, - ): MaybePromise { - return this.args.source.saveWitnesses(wallet, chainId, payload, signatures) - } - - saveUpdate( - wallet: Address.Address, - configuration: Config.Config, - signature: Signature.RawSignature, - ): MaybePromise { - return this.args.source.saveUpdate(wallet, configuration, signature) - } - - saveTree(tree: GenericTree.Tree): MaybePromise { - return this.args.source.saveTree(tree) - } - - saveConfiguration(config: Config.Config): MaybePromise { - return this.args.source.saveConfiguration(config) - } - - saveDeploy(imageHash: Hex.Hex, context: Context.Context): MaybePromise { - return this.args.source.saveDeploy(imageHash, context) - } - - async getPayload(opHash: Hex.Hex): Promise< - | { - chainId: number - payload: Payload.Parented - wallet: Address.Address - } - | undefined - > { - const cached = await this.args.cache.getPayload(opHash) - if (cached) { - return cached - } - - const source = await this.args.source.getPayload(opHash) - if (source) { - await this.args.cache.savePayload(source.wallet, source.payload, source.chainId) - } - return source - } - - savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: number): MaybePromise { - return this.args.source.savePayload(wallet, payload, chainId) - } -} diff --git a/packages/wallet/core/src/state/debug.ts b/packages/wallet/core/src/state/debug.ts deleted file mode 100644 index 05302a1991..0000000000 --- a/packages/wallet/core/src/state/debug.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Hex } from 'ox' - -// JSON.stringify replacer for args/results -function stringifyReplacer(_key: string, value: any): any { - if (typeof value === 'bigint') { - return value.toString() - } - if (value instanceof Uint8Array) { - return Hex.fromBytes(value) - } - return value -} - -function stringify(value: any): string { - return JSON.stringify(value, stringifyReplacer, 2) -} - -// Normalize for deep comparison -function normalize(value: any): any { - if (typeof value === 'bigint') { - return value.toString() - } - if (value instanceof Uint8Array) { - return Hex.fromBytes(value) - } - if (typeof value === 'string') { - return value.toLowerCase() - } - if (Array.isArray(value)) { - return value.map(normalize) - } - if (value && typeof value === 'object') { - const out: [string, any][] = [] - // ignore undefined, sort keys - for (const key of Object.keys(value) - .filter((k) => value[k] !== undefined) - .sort()) { - out.push([key.toLowerCase(), normalize(value[key])]) - } - return out - } - return value -} - -function deepEqual(a: any, b: any): boolean { - return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b)) -} - -export function multiplex(reference: T, candidates: Record): T { - const handler: ProxyHandler = { - get(_target, prop, _receiver) { - const orig = (reference as any)[prop] - if (typeof orig !== 'function') { - // non-method properties passthrough - return Reflect.get(reference, prop) - } - - return async (...args: any[]): Promise => { - const methodName = String(prop) - const argsStr = stringify(args) - - let refResult: any - try { - refResult = await orig.apply(reference, args) - } catch (err) { - const id = Math.floor(1000000 * Math.random()) - .toString() - .padStart(6, '0') - console.trace( - `[${id}] calling ${methodName}: ${argsStr}\n[${id}] warning: reference ${methodName} threw:`, - err, - ) - throw err - } - - const refResultStr = stringify(refResult) - - // invoke all candidates in parallel - await Promise.all( - Object.entries(candidates).map(async ([name, cand]) => { - const method = (cand as any)[prop] - if (typeof method !== 'function') { - const id = Math.floor(1000000 * Math.random()) - .toString() - .padStart(6, '0') - console.trace( - `[${id}] calling ${methodName}: ${argsStr}\n[${id}] reference returned: ${refResultStr}\n[${id}] warning: ${name} has no ${methodName}`, - ) - return - } - let candRes: any - try { - candRes = method.apply(cand, args) - candRes = await Promise.resolve(candRes) - } catch (err) { - const id = Math.floor(1000000 * Math.random()) - .toString() - .padStart(6, '0') - console.trace( - `[${id}] calling ${methodName}: ${argsStr}\n[${id}] reference returned: ${refResultStr}\n[${id}] warning: ${name} ${methodName} threw:`, - err, - ) - return - } - const id = Math.floor(1000000 * Math.random()) - .toString() - .padStart(6, '0') - if (deepEqual(refResult, candRes)) { - console.trace( - `[${id}] calling ${methodName}: ${argsStr}\n[${id}] reference returned: ${refResultStr}\n[${id}] ${name} returned: ${stringify(candRes)}`, - ) - } else { - console.trace( - `[${id}] calling ${methodName}: ${argsStr}\n[${id}] reference returned: ${refResultStr}\n[${id}] ${name} returned: ${stringify(candRes)}\n[${id}] warning: ${name} ${methodName} does not match reference`, - ) - } - }), - ) - - return refResult - } - }, - } - - return new Proxy(reference, handler) -} diff --git a/packages/wallet/core/src/state/index.ts b/packages/wallet/core/src/state/index.ts deleted file mode 100644 index 94faee061b..0000000000 --- a/packages/wallet/core/src/state/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Address, Hex } from 'ox' -import { Context, Config, Payload, Signature, GenericTree } from '@0xsequence/wallet-primitives' - -export type Provider = Reader & Writer - -export interface Reader { - getConfiguration(imageHash: Hex.Hex): MaybePromise - - getDeploy(wallet: Address.Address): MaybePromise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> - - getWallets(signer: Address.Address): MaybePromise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - }> - - getWalletsForSapient( - signer: Address.Address, - imageHash: Hex.Hex, - ): MaybePromise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - }> - - getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): MaybePromise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined - > - - getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): MaybePromise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined - > - - getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): MaybePromise> - - getTree(rootHash: Hex.Hex): MaybePromise - getPayload( - opHash: Hex.Hex, - ): MaybePromise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> -} - -export interface Writer { - saveWallet(deployConfiguration: Config.Config, context: Context.Context): MaybePromise - - saveWitnesses( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signatures: Signature.RawTopology, - ): MaybePromise - - saveUpdate( - wallet: Address.Address, - configuration: Config.Config, - signature: Signature.RawSignature, - ): MaybePromise - - saveTree(tree: GenericTree.Tree): MaybePromise - - saveConfiguration(config: Config.Config): MaybePromise - saveDeploy(imageHash: Hex.Hex, context: Context.Context): MaybePromise - savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: number): MaybePromise -} - -export type MaybePromise = T | Promise - -export * from './cached.js' -export * from './debug.js' -export * from './utils.js' -export * as Arweave from './arweave/index.js' -export * as Local from './local/index.js' -export * as Remote from './remote/index.js' -export * as Sequence from './sequence/index.js' diff --git a/packages/wallet/core/src/state/local/index.ts b/packages/wallet/core/src/state/local/index.ts deleted file mode 100644 index 282de20e0a..0000000000 --- a/packages/wallet/core/src/state/local/index.ts +++ /dev/null @@ -1,443 +0,0 @@ -import { - Context, - Payload, - Signature, - Config, - Address as SequenceAddress, - Extensions, - GenericTree, -} from '@0xsequence/wallet-primitives' -import { Address, Bytes, Hex, PersonalMessage, Secp256k1 } from 'ox' -import { Provider as ProviderInterface } from '../index.js' -import { MemoryStore } from './memory.js' -import { normalizeAddressKeys } from '../utils.js' - -export interface Store { - // top level configurations store - loadConfig: (imageHash: Hex.Hex) => Promise - saveConfig: (imageHash: Hex.Hex, config: Config.Config) => Promise - - // counterfactual wallets - loadCounterfactualWallet: ( - wallet: Address.Address, - ) => Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> - saveCounterfactualWallet: (wallet: Address.Address, imageHash: Hex.Hex, context: Context.Context) => Promise - - // payloads - loadPayloadOfSubdigest: ( - subdigest: Hex.Hex, - ) => Promise<{ content: Payload.Parented; chainId: number; wallet: Address.Address } | undefined> - savePayloadOfSubdigest: ( - subdigest: Hex.Hex, - payload: { content: Payload.Parented; chainId: number; wallet: Address.Address }, - ) => Promise - - // signatures - loadSubdigestsOfSigner: (signer: Address.Address) => Promise - loadSignatureOfSubdigest: ( - signer: Address.Address, - subdigest: Hex.Hex, - ) => Promise - saveSignatureOfSubdigest: ( - signer: Address.Address, - subdigest: Hex.Hex, - signature: Signature.SignatureOfSignerLeaf, - ) => Promise - - // sapient signatures - loadSubdigestsOfSapientSigner: (signer: Address.Address, imageHash: Hex.Hex) => Promise - loadSapientSignatureOfSubdigest: ( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - ) => Promise - saveSapientSignatureOfSubdigest: ( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - signature: Signature.SignatureOfSapientSignerLeaf, - ) => Promise - - // generic trees - loadTree: (rootHash: Hex.Hex) => Promise - saveTree: (rootHash: Hex.Hex, tree: GenericTree.Tree) => Promise -} - -export class Provider implements ProviderInterface { - constructor( - private readonly store: Store = new MemoryStore(), - public readonly extensions: Extensions.Extensions = Extensions.Rc5, - ) {} - - getConfiguration(imageHash: Hex.Hex): Promise { - return this.store.loadConfig(imageHash) - } - - async saveWallet(deployConfiguration: Config.Config, context: Context.Context): Promise { - // Save both the configuration and the deploy hash - await this.saveConfig(deployConfiguration) - const imageHash = Config.hashConfiguration(deployConfiguration) - await this.saveCounterfactualWallet(SequenceAddress.from(imageHash, context), Hex.fromBytes(imageHash), context) - } - - async saveConfig(config: Config.Config): Promise { - const imageHash = Bytes.toHex(Config.hashConfiguration(config)) - const previous = await this.store.loadConfig(imageHash) - if (previous) { - const combined = Config.mergeTopology(previous.topology, config.topology) - return this.store.saveConfig(imageHash, { ...previous, topology: combined }) - } else { - return this.store.saveConfig(imageHash, config) - } - } - - saveCounterfactualWallet( - wallet: Address.Address, - imageHash: Hex.Hex, - context: Context.Context, - ): void | Promise { - this.store.saveCounterfactualWallet(wallet, imageHash, context) - } - - getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - return this.store.loadCounterfactualWallet(wallet) - } - - private async getWalletsGeneric( - subdigests: Hex.Hex[], - loadSignatureFn: (subdigest: Hex.Hex) => Promise, - ): Promise> { - const payloads = await Promise.all(subdigests.map((sd) => this.store.loadPayloadOfSubdigest(sd))) - const response: Record = {} - - for (const payload of payloads) { - if (!payload) { - continue - } - - const walletAddress = Address.checksum(payload.wallet) - - // If we already have a witness for this wallet, skip it - if (response[walletAddress]) { - continue - } - - const subdigest = Hex.fromBytes(Payload.hash(walletAddress, payload.chainId, payload.content)) - const signature = await loadSignatureFn(subdigest) - - if (!signature) { - continue - } - - response[walletAddress] = { - chainId: payload.chainId, - payload: payload.content, - signature, - } - } - - return response - } - - async getWallets(signer: Address.Address) { - return normalizeAddressKeys( - await this.getWalletsGeneric( - await this.store.loadSubdigestsOfSigner(signer), - (subdigest) => this.store.loadSignatureOfSubdigest(signer, subdigest), - ), - ) - } - - async getWalletsForSapient(signer: Address.Address, imageHash: Hex.Hex) { - return normalizeAddressKeys( - await this.getWalletsGeneric( - await this.store.loadSubdigestsOfSapientSigner(signer, imageHash), - (subdigest) => this.store.loadSapientSignatureOfSubdigest(signer, subdigest, imageHash), - ), - ) - } - - getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): - | { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } - | Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> - | undefined { - const checksumAddress = Address.checksum(wallet) - return this.getWallets(signer).then((wallets) => wallets[checksumAddress]) - } - - getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): - | { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } - | Promise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined - > - | undefined { - const checksumAddress = Address.checksum(wallet) - return this.getWalletsForSapient(signer, imageHash).then((wallets) => wallets[checksumAddress]) - } - - async saveWitnesses( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signatures: Signature.RawTopology, - ): Promise { - const subdigest = Hex.fromBytes(Payload.hash(wallet, chainId, payload)) - - await Promise.all([ - this.saveSignature(subdigest, signatures), - this.store.savePayloadOfSubdigest(subdigest, { content: payload, chainId, wallet }), - ]) - - return - } - - async getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): Promise<{ imageHash: Hex.Hex; signature: Signature.RawSignature }[]> { - const fromConfig = await this.store.loadConfig(fromImageHash) - if (!fromConfig) { - return [] - } - - const { signers, sapientSigners } = Config.getSigners(fromConfig) - const subdigestsOfSigner = await Promise.all([ - ...signers.map((s) => this.store.loadSubdigestsOfSigner(s)), - ...sapientSigners.map((s) => this.store.loadSubdigestsOfSapientSigner(s.address, s.imageHash)), - ]) - - const subdigests = [...new Set(subdigestsOfSigner.flat())] - const payloads = await Promise.all(subdigests.map((subdigest) => this.store.loadPayloadOfSubdigest(subdigest))) - - const nextCandidates = await Promise.all( - payloads - .filter((p) => p?.content && Payload.isConfigUpdate(p.content)) - .map(async (p) => ({ - payload: p!, - nextImageHash: (p!.content as Payload.ConfigUpdate).imageHash, - config: await this.store.loadConfig((p!.content as Payload.ConfigUpdate).imageHash), - })), - ) - - let best: - | { - nextImageHash: Hex.Hex - checkpoint: bigint - signature: Signature.RawSignature - } - | undefined - - const nextCandidatesSorted = nextCandidates - .filter((c) => c!.config && c!.config.checkpoint > fromConfig.checkpoint) - .sort((a, b) => - // If we are looking for the longest path, sort by ascending checkpoint - // because we want to find the smalles jump, and we should start with the - // closest one. If we are not looking for the longest path, sort by - // descending checkpoint, because we want to find the largest jump. - // - // We don't have a guarantee that all "next configs" will be valid - // so worst case scenario we will need to try all of them. - // But we can try to optimize for the most common case. - a.config!.checkpoint > b.config!.checkpoint ? (options?.allUpdates ? 1 : -1) : options?.allUpdates ? -1 : 1, - ) - - for (const candidate of nextCandidatesSorted) { - if (best) { - if (options?.allUpdates) { - // Only consider candidates earlier than our current best - if (candidate.config!.checkpoint <= best.checkpoint) { - continue - } - } else { - // Only consider candidates later than our current best - if (candidate.config!.checkpoint <= best.checkpoint) { - continue - } - } - } - - // Get all signatures (for all signers) for this subdigest - const expectedSubdigest = Hex.fromBytes( - Payload.hash(wallet, candidate.payload.chainId, candidate.payload.content), - ) - const signaturesOfSigners = await Promise.all([ - ...signers.map(async (signer) => { - return { signer, signature: await this.store.loadSignatureOfSubdigest(signer, expectedSubdigest) } - }), - ...sapientSigners.map(async (signer) => { - return { - signer: signer.address, - imageHash: signer.imageHash, - signature: await this.store.loadSapientSignatureOfSubdigest( - signer.address, - expectedSubdigest, - signer.imageHash, - ), - } - }), - ]) - - let totalWeight = 0n - const encoded = Signature.fillLeaves(fromConfig.topology, (leaf) => { - if (Config.isSapientSignerLeaf(leaf)) { - const sapientSignature = signaturesOfSigners.find( - ({ signer, imageHash }: { signer: Address.Address; imageHash?: Hex.Hex }) => { - return imageHash && Address.isEqual(signer, leaf.address) && imageHash === leaf.imageHash - }, - )?.signature - - if (sapientSignature) { - totalWeight += leaf.weight - return sapientSignature - } - } - - const signature = signaturesOfSigners.find(({ signer }) => Address.isEqual(signer, leaf.address))?.signature - if (!signature) { - return undefined - } - - totalWeight += leaf.weight - return signature - }) - - if (totalWeight < fromConfig.threshold) { - continue - } - - best = { - nextImageHash: candidate.nextImageHash, - checkpoint: candidate.config!.checkpoint, - signature: { - noChainId: true, - configuration: { - threshold: fromConfig.threshold, - checkpoint: fromConfig.checkpoint, - topology: encoded, - }, - }, - } - } - - if (!best) { - return [] - } - - const nextStep = await this.getConfigurationUpdates(wallet, best.nextImageHash, { allUpdates: true }) - - return [ - { - imageHash: best.nextImageHash, - signature: best.signature, - }, - ...nextStep, - ] - } - - async saveUpdate( - wallet: Address.Address, - configuration: Config.Config, - signature: Signature.RawSignature, - ): Promise { - const nextImageHash = Bytes.toHex(Config.hashConfiguration(configuration)) - const payload: Payload.ConfigUpdate = { - type: 'config-update', - imageHash: nextImageHash, - } - - const subdigest = Payload.hash(wallet, 0, payload) - - await this.store.savePayloadOfSubdigest(Hex.fromBytes(subdigest), { content: payload, chainId: 0, wallet }) - await this.saveConfig(configuration) - - await this.saveSignature(Hex.fromBytes(subdigest), signature.configuration.topology) - } - - async saveSignature(subdigest: Hex.Hex, topology: Signature.RawTopology): Promise { - if (Signature.isRawNode(topology)) { - await Promise.all([this.saveSignature(subdigest, topology[0]), this.saveSignature(subdigest, topology[1])]) - return - } - - if (Signature.isRawNestedLeaf(topology)) { - return this.saveSignature(subdigest, topology.tree) - } - - if (Signature.isRawSignerLeaf(topology)) { - const type = topology.signature.type - if (type === 'eth_sign' || type === 'hash') { - const address = Secp256k1.recoverAddress({ - payload: type === 'eth_sign' ? PersonalMessage.getSignPayload(subdigest) : subdigest, - signature: topology.signature, - }) - - return this.store.saveSignatureOfSubdigest(address, subdigest, topology.signature) - } - - if (Signature.isSignatureOfSapientSignerLeaf(topology.signature)) { - switch (topology.signature.address.toLowerCase()) { - case this.extensions.passkeys.toLowerCase(): { - const decoded = Extensions.Passkeys.decode(Bytes.fromHex(topology.signature.data)) - - if (!Extensions.Passkeys.isValidSignature(subdigest, decoded)) { - throw new Error('Invalid passkey signature') - } - - return this.store.saveSapientSignatureOfSubdigest( - topology.signature.address, - subdigest, - Extensions.Passkeys.rootFor(decoded.publicKey), - topology.signature, - ) - } - - default: - throw new Error(`Unsupported sapient signer: ${topology.signature.address}`) - } - } - } - } - - getTree(rootHash: Hex.Hex): GenericTree.Tree | Promise | undefined { - return this.store.loadTree(rootHash) - } - - saveTree(tree: GenericTree.Tree): void | Promise { - return this.store.saveTree(GenericTree.hash(tree), tree) - } - - saveConfiguration(config: Config.Config): Promise { - return this.store.saveConfig(Bytes.toHex(Config.hashConfiguration(config)), config) - } - - saveDeploy(imageHash: Hex.Hex, context: Context.Context): Promise { - return this.store.saveCounterfactualWallet( - SequenceAddress.from(Bytes.fromHex(imageHash), context), - imageHash, - context, - ) - } - - async getPayload( - opHash: Hex.Hex, - ): Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> { - const data = await this.store.loadPayloadOfSubdigest(opHash) - return data ? { chainId: data.chainId, payload: data.content, wallet: data.wallet } : undefined - } - - savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: number): Promise { - const subdigest = Hex.fromBytes(Payload.hash(wallet, chainId, payload)) - return this.store.savePayloadOfSubdigest(subdigest, { content: payload, chainId, wallet }) - } -} - -export * from './memory.js' -export * from './indexed-db.js' diff --git a/packages/wallet/core/src/state/local/indexed-db.ts b/packages/wallet/core/src/state/local/indexed-db.ts deleted file mode 100644 index eeb5cd346f..0000000000 --- a/packages/wallet/core/src/state/local/indexed-db.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { Context, Payload, Signature, Config, GenericTree } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import type { CoreEnv } from '../../env.js' -import { Store } from './index.js' - -const DB_VERSION = 1 -const STORE_CONFIGS = 'configs' -const STORE_WALLETS = 'counterfactualWallets' -const STORE_PAYLOADS = 'payloads' -const STORE_SIGNER_SUBDIGESTS = 'signerSubdigests' -const STORE_SIGNATURES = 'signatures' -const STORE_SAPIENT_SIGNER_SUBDIGESTS = 'sapientSignerSubdigests' -const STORE_SAPIENT_SIGNATURES = 'sapientSignatures' -const STORE_TREES = 'trees' - -export class IndexedDbStore implements Store { - private _db: IDBDatabase | null = null - private dbName: string - - constructor( - dbName: string = 'sequence-indexeddb', - private readonly env?: CoreEnv, - ) { - this.dbName = dbName - } - - private getIndexedDB(): IDBFactory { - const globalObj = globalThis as any - const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB - if (!indexedDb) { - throw new Error('indexedDB is not available') - } - return indexedDb - } - - private async openDB(): Promise { - if (this._db) return this._db - - return new Promise((resolve, reject) => { - const request = this.getIndexedDB().open(this.dbName, DB_VERSION) - - request.onupgradeneeded = () => { - const db = request.result - if (!db.objectStoreNames.contains(STORE_CONFIGS)) { - db.createObjectStore(STORE_CONFIGS) - } - if (!db.objectStoreNames.contains(STORE_WALLETS)) { - db.createObjectStore(STORE_WALLETS) - } - if (!db.objectStoreNames.contains(STORE_PAYLOADS)) { - db.createObjectStore(STORE_PAYLOADS) - } - if (!db.objectStoreNames.contains(STORE_SIGNER_SUBDIGESTS)) { - db.createObjectStore(STORE_SIGNER_SUBDIGESTS) - } - if (!db.objectStoreNames.contains(STORE_SIGNATURES)) { - db.createObjectStore(STORE_SIGNATURES) - } - if (!db.objectStoreNames.contains(STORE_SAPIENT_SIGNER_SUBDIGESTS)) { - db.createObjectStore(STORE_SAPIENT_SIGNER_SUBDIGESTS) - } - if (!db.objectStoreNames.contains(STORE_SAPIENT_SIGNATURES)) { - db.createObjectStore(STORE_SAPIENT_SIGNATURES) - } - if (!db.objectStoreNames.contains(STORE_TREES)) { - db.createObjectStore(STORE_TREES) - } - } - - request.onsuccess = () => { - this._db = request.result - resolve(this._db!) - } - - request.onerror = () => { - reject(request.error) - } - }) - } - - private async get(storeName: string, key: string): Promise { - const db = await this.openDB() - return new Promise((resolve, reject) => { - const tx = db.transaction(storeName, 'readonly') - const store = tx.objectStore(storeName) - const req = store.get(key) - req.onsuccess = () => resolve(req.result) - req.onerror = () => reject(req.error) - }) - } - - private async put(storeName: string, key: string, value: T): Promise { - const db = await this.openDB() - return new Promise((resolve, reject) => { - const tx = db.transaction(storeName, 'readwrite') - const store = tx.objectStore(storeName) - const req = store.put(value, key) - req.onsuccess = () => resolve() - req.onerror = () => reject(req.error) - }) - } - - private async getSet(storeName: string, key: string): Promise> { - const data = (await this.get>(storeName, key)) || new Set() - return Array.isArray(data) ? new Set(data) : data - } - - private async putSet(storeName: string, key: string, setData: Set): Promise { - await this.put(storeName, key, Array.from(setData)) - } - - private getSignatureKey(signer: Address.Address, subdigest: Hex.Hex): string { - return `${signer.toLowerCase()}-${subdigest.toLowerCase()}` - } - - private getSapientSignatureKey(signer: Address.Address, subdigest: Hex.Hex, imageHash: Hex.Hex): string { - return `${signer.toLowerCase()}-${imageHash.toLowerCase()}-${subdigest.toLowerCase()}` - } - - async loadConfig(imageHash: Hex.Hex): Promise { - return this.get(STORE_CONFIGS, imageHash.toLowerCase()) - } - - async saveConfig(imageHash: Hex.Hex, config: Config.Config): Promise { - await this.put(STORE_CONFIGS, imageHash.toLowerCase(), config) - } - - async loadCounterfactualWallet( - wallet: Address.Address, - ): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - return this.get(STORE_WALLETS, wallet.toLowerCase()) - } - - async saveCounterfactualWallet(wallet: Address.Address, imageHash: Hex.Hex, context: Context.Context): Promise { - await this.put(STORE_WALLETS, wallet.toLowerCase(), { imageHash, context }) - } - - async loadPayloadOfSubdigest( - subdigest: Hex.Hex, - ): Promise<{ content: Payload.Parented; chainId: number; wallet: Address.Address } | undefined> { - return this.get(STORE_PAYLOADS, subdigest.toLowerCase()) - } - - async savePayloadOfSubdigest( - subdigest: Hex.Hex, - payload: { content: Payload.Parented; chainId: number; wallet: Address.Address }, - ): Promise { - await this.put(STORE_PAYLOADS, subdigest.toLowerCase(), payload) - } - - async loadSubdigestsOfSigner(signer: Address.Address): Promise { - const dataSet = await this.getSet(STORE_SIGNER_SUBDIGESTS, signer.toLowerCase()) - return Array.from(dataSet) as Hex.Hex[] - } - - async loadSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - ): Promise { - const key = this.getSignatureKey(signer, subdigest) - return this.get(STORE_SIGNATURES, key.toLowerCase()) - } - - async saveSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - signature: Signature.SignatureOfSignerLeaf, - ): Promise { - const key = this.getSignatureKey(signer, subdigest) - await this.put(STORE_SIGNATURES, key.toLowerCase(), signature) - - const signerKey = signer.toLowerCase() - const subdigestKey = subdigest.toLowerCase() - const dataSet = await this.getSet(STORE_SIGNER_SUBDIGESTS, signerKey) - dataSet.add(subdigestKey) - await this.putSet(STORE_SIGNER_SUBDIGESTS, signerKey, dataSet) - } - - async loadSubdigestsOfSapientSigner(signer: Address.Address, imageHash: Hex.Hex): Promise { - const key = `${signer.toLowerCase()}-${imageHash.toLowerCase()}` - const dataSet = await this.getSet(STORE_SAPIENT_SIGNER_SUBDIGESTS, key) - return Array.from(dataSet) as Hex.Hex[] - } - - async loadSapientSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - ): Promise { - const key = this.getSapientSignatureKey(signer, subdigest, imageHash) - return this.get(STORE_SAPIENT_SIGNATURES, key.toLowerCase()) - } - - async saveSapientSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - signature: Signature.SignatureOfSapientSignerLeaf, - ): Promise { - const fullKey = this.getSapientSignatureKey(signer, subdigest, imageHash).toLowerCase() - await this.put(STORE_SAPIENT_SIGNATURES, fullKey, signature) - - const signerKey = `${signer.toLowerCase()}-${imageHash.toLowerCase()}` - const subdigestKey = subdigest.toLowerCase() - const dataSet = await this.getSet(STORE_SAPIENT_SIGNER_SUBDIGESTS, signerKey) - dataSet.add(subdigestKey) - await this.putSet(STORE_SAPIENT_SIGNER_SUBDIGESTS, signerKey, dataSet) - } - - async loadTree(rootHash: Hex.Hex): Promise { - return this.get(STORE_TREES, rootHash.toLowerCase()) - } - - async saveTree(rootHash: Hex.Hex, tree: GenericTree.Tree): Promise { - await this.put(STORE_TREES, rootHash.toLowerCase(), tree) - } -} diff --git a/packages/wallet/core/src/state/local/memory.ts b/packages/wallet/core/src/state/local/memory.ts deleted file mode 100644 index 5d3ad3e2be..0000000000 --- a/packages/wallet/core/src/state/local/memory.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Context, Payload, Signature, Config, GenericTree } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { Store } from './index.js' - -export class MemoryStore implements Store { - private configs = new Map<`0x${string}`, Config.Config>() - private counterfactualWallets = new Map<`0x${string}`, { imageHash: Hex.Hex; context: Context.Context }>() - private payloads = new Map<`0x${string}`, { content: Payload.Parented; chainId: number; wallet: Address.Address }>() - private signerSubdigests = new Map>() - private signatures = new Map<`0x${string}`, Signature.SignatureOfSignerLeaf>() - - private sapientSignerSubdigests = new Map>() - private sapientSignatures = new Map<`0x${string}`, Signature.SignatureOfSapientSignerLeaf>() - - private trees = new Map<`0x${string}`, GenericTree.Tree>() - - private deepCopy(value: T): T { - // modern runtime → fast native path - if (typeof structuredClone === 'function') { - return structuredClone(value) - } - - // very small poly-fill for old environments - if (value === null || typeof value !== 'object') return value - if (value instanceof Date) return new Date(value.getTime()) as unknown as T - if (Array.isArray(value)) return value.map((v) => this.deepCopy(v)) as unknown as T - if (value instanceof Map) { - return new Map(Array.from(value, ([k, v]) => [this.deepCopy(k), this.deepCopy(v)])) as unknown as T - } - if (value instanceof Set) { - return new Set(Array.from(value, (v) => this.deepCopy(v))) as unknown as T - } - - const out: Record = {} - for (const [k, v] of Object.entries(value as Record)) { - out[k] = this.deepCopy(v) - } - return out as T - } - - private getSignatureKey(signer: Address.Address, subdigest: Hex.Hex): string { - return `${signer.toLowerCase()}-${subdigest.toLowerCase()}` - } - - private getSapientSignatureKey(signer: Address.Address, subdigest: Hex.Hex, imageHash: Hex.Hex): string { - return `${signer.toLowerCase()}-${imageHash.toLowerCase()}-${subdigest.toLowerCase()}` - } - - async loadConfig(imageHash: Hex.Hex): Promise { - const config = this.configs.get(imageHash.toLowerCase() as `0x${string}`) - return config ? this.deepCopy(config) : undefined - } - - async saveConfig(imageHash: Hex.Hex, config: Config.Config): Promise { - this.configs.set(imageHash.toLowerCase() as `0x${string}`, this.deepCopy(config)) - } - - async loadCounterfactualWallet( - wallet: Address.Address, - ): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - const counterfactualWallet = this.counterfactualWallets.get(wallet.toLowerCase() as `0x${string}`) - return counterfactualWallet ? this.deepCopy(counterfactualWallet) : undefined - } - - async saveCounterfactualWallet(wallet: Address.Address, imageHash: Hex.Hex, context: Context.Context): Promise { - this.counterfactualWallets.set(wallet.toLowerCase() as `0x${string}`, this.deepCopy({ imageHash, context })) - } - - async loadPayloadOfSubdigest( - subdigest: Hex.Hex, - ): Promise<{ content: Payload.Parented; chainId: number; wallet: Address.Address } | undefined> { - const payload = this.payloads.get(subdigest.toLowerCase() as `0x${string}`) - return payload ? this.deepCopy(payload) : undefined - } - - async savePayloadOfSubdigest( - subdigest: Hex.Hex, - payload: { content: Payload.Parented; chainId: number; wallet: Address.Address }, - ): Promise { - this.payloads.set(subdigest.toLowerCase() as `0x${string}`, this.deepCopy(payload)) - } - - async loadSubdigestsOfSigner(signer: Address.Address): Promise { - const subdigests = this.signerSubdigests.get(signer.toLowerCase() as `0x${string}`) - return subdigests ? Array.from(subdigests).map((s) => s as Hex.Hex) : [] - } - - async loadSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - ): Promise { - const key = this.getSignatureKey(signer, subdigest) - const signature = this.signatures.get(key as `0x${string}`) - return signature ? this.deepCopy(signature) : undefined - } - - async saveSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - signature: Signature.SignatureOfSignerLeaf, - ): Promise { - const key = this.getSignatureKey(signer, subdigest) - this.signatures.set(key as `0x${string}`, this.deepCopy(signature)) - - const signerKey = signer.toLowerCase() - const subdigestKey = subdigest.toLowerCase() - - if (!this.signerSubdigests.has(signerKey)) { - this.signerSubdigests.set(signerKey, new Set()) - } - this.signerSubdigests.get(signerKey)!.add(subdigestKey) - } - - async loadSubdigestsOfSapientSigner(signer: Address.Address, imageHash: Hex.Hex): Promise { - const key = `${signer.toLowerCase()}-${imageHash.toLowerCase()}` - const subdigests = this.sapientSignerSubdigests.get(key) - return subdigests ? Array.from(subdigests).map((s) => s as Hex.Hex) : [] - } - - async loadSapientSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - ): Promise { - const key = this.getSapientSignatureKey(signer, subdigest, imageHash) - const signature = this.sapientSignatures.get(key as `0x${string}`) - return signature ? this.deepCopy(signature) : undefined - } - - async saveSapientSignatureOfSubdigest( - signer: Address.Address, - subdigest: Hex.Hex, - imageHash: Hex.Hex, - signature: Signature.SignatureOfSapientSignerLeaf, - ): Promise { - const key = this.getSapientSignatureKey(signer, subdigest, imageHash) - this.sapientSignatures.set(key as `0x${string}`, this.deepCopy(signature)) - - const signerKey = `${signer.toLowerCase()}-${imageHash.toLowerCase()}` - const subdigestKey = subdigest.toLowerCase() - - if (!this.sapientSignerSubdigests.has(signerKey)) { - this.sapientSignerSubdigests.set(signerKey, new Set()) - } - this.sapientSignerSubdigests.get(signerKey)!.add(subdigestKey) - } - - async loadTree(rootHash: Hex.Hex): Promise { - const tree = this.trees.get(rootHash.toLowerCase() as `0x${string}`) - return tree ? this.deepCopy(tree) : undefined - } - - async saveTree(rootHash: Hex.Hex, tree: GenericTree.Tree): Promise { - this.trees.set(rootHash.toLowerCase() as `0x${string}`, this.deepCopy(tree)) - } -} diff --git a/packages/wallet/core/src/state/remote/dev-http.ts b/packages/wallet/core/src/state/remote/dev-http.ts deleted file mode 100644 index 7c288e050a..0000000000 --- a/packages/wallet/core/src/state/remote/dev-http.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Address, Hex } from 'ox' -import { Config, Context, GenericTree, Payload, Signature, Utils } from '@0xsequence/wallet-primitives' -import { Provider } from '../index.js' - -export class DevHttpProvider implements Provider { - private readonly baseUrl: string - private readonly fetcher: typeof fetch - - constructor(baseUrl: string, fetcher?: typeof fetch) { - // Remove trailing slash if present - this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl - const resolvedFetch = fetcher ?? (globalThis as any).fetch - if (!resolvedFetch) { - throw new Error('fetch is not available') - } - this.fetcher = resolvedFetch - } - - private async request(method: 'GET' | 'POST', path: string, body?: any): Promise { - const url = `${this.baseUrl}${path}` - const options: RequestInit = { - method, - headers: {}, - } - - if (body && method === 'POST') { - options.headers = { 'Content-Type': 'application/json' } - options.body = Utils.toJSON(body) - } - - let response: Response - try { - response = await this.fetcher(url, options) - } catch (networkError) { - // Handle immediate network errors (e.g., DNS resolution failure, refused connection) - console.error(`Network error during ${method} request to ${url}:`, networkError) - throw networkError // Re-throw network errors - } - - // --- Error Handling for HTTP Status --- - if (!response.ok) { - let errorPayload: any = { message: `HTTP error! Status: ${response.status}` } - try { - const errorText = await response.text() - const errorJson = await Utils.fromJSON(errorText) - errorPayload = { ...errorPayload, ...errorJson } - } catch { - try { - // If JSON parsing fails, try getting text for better error message - const errorText = await response.text() - errorPayload.body = errorText - } catch { - // Ignore if reading text also fails - } - } - console.error('HTTP Request Failed:', errorPayload) - throw new Error(errorPayload.message || `Request failed for ${method} ${path} with status ${response.status}`) - } - - // --- Response Body Handling (with fix for empty body) --- - try { - // Handle cases where POST might return 201/204 No Content - // 204 should definitely have no body. 201 might or might not. - if (response.status === 204) { - return undefined as T // No content expected - } - if (response.status === 201 && method === 'POST') { - // Attempt to parse JSON (e.g., for { success: true }), but handle empty body gracefully - const text = await response.clone().text() // Clone and check text first - if (text.trim() === '') { - return undefined as T // Treat empty 201 as success with no specific return data - } - // If not empty, try parsing JSON - const responseText = await response.text() - return (await Utils.fromJSON(responseText)) as T - } - - // For 200 OK or other success statuses expecting a body - // Clone the response before attempting to read the body, - // so we can potentially read it again (as text) if json() fails. - const clonedResponse = response.clone() - const textContent = await clonedResponse.text() // Read as text first - - if (textContent.trim() === '') { - // If the body is empty (or only whitespace) and status was OK (checked above), - // treat this as the server sending 'undefined' or 'null'. - // Return `undefined` to match the expected optional types in the Provider interface. - return undefined as T - } else { - // If there is content, attempt to parse it as JSON. - // We use the original response here, which hasn't had its body consumed yet. - const responseText = await response.text() - const data = await Utils.fromJSON(responseText) - - // BigInt Deserialization note remains the same: manual conversion may be needed by consumer. - return data as T - } - } catch (error) { - // This catch block now primarily handles errors from response.json() - // if the non-empty textContent wasn't valid JSON. - console.error(`Error processing response body for ${method} ${url}:`, error) - // Also include the raw text in the error if possible - try { - const text = await response.text() // Try reading original response if not already done - throw new Error( - `Failed to parse JSON response from server. Status: ${response.status}. Body: "${text}". Original error: ${error instanceof Error ? error.message : String(error)}`, - ) - } catch { - throw new Error( - `Failed to parse JSON response from server and could not read response body as text. Status: ${response.status}. Original error: ${error instanceof Error ? error.message : String(error)}`, - ) - } - } - } - - // --- Reader Methods --- - - async getConfiguration(imageHash: Hex.Hex): Promise { - // The response needs careful handling if BigInts are involved (threshold, checkpoint) - const config = await this.request('GET', `/configuration/${imageHash}`) - // Manual conversion example (if needed by consumer): - // if (config?.threshold) config.threshold = BigInt(config.threshold); - // if (config?.checkpoint) config.checkpoint = BigInt(config.checkpoint); - return config - } - - async getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - return this.request('GET', `/deploy/${wallet}`) - } - - async getWallets(signer: Address.Address): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - }> { - // Response `chainId` will be a string/number, needs conversion if BigInt is strictly required upstream - return this.request('GET', `/wallets/signer/${signer}`) - } - - async getWalletsForSapient( - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - }> { - // Response `chainId` will be a string/number, needs conversion - return this.request('GET', `/wallets/sapient/${signer}/${imageHash}`) - } - - async getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): Promise< - | { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - | undefined - > { - // Response `chainId` will be a string/number, needs conversion - return this.request('GET', `/witness/${wallet}/signer/${signer}`) - } - - async getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise< - | { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - | undefined - > { - // Response `chainId` will be a string/number, needs conversion - return this.request('GET', `/witness/sapient/${wallet}/${signer}/${imageHash}`) - } - - async getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): Promise> { - const query = options?.allUpdates ? '?allUpdates=true' : '' - // Response signature object might contain BigInts (threshold, checkpoint) as strings - return this.request('GET', `/configuration-updates/${wallet}/from/${fromImageHash}${query}`) - } - - async getTree(rootHash: Hex.Hex): Promise { - return this.request('GET', `/tree/${rootHash}`) - } - - // --- Writer Methods --- - - async saveWallet(deployConfiguration: Config.Config, context: Context.Context): Promise { - await this.request('POST', '/wallet', { deployConfiguration, context }) - } - - async saveWitnesses( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signatures: Signature.RawTopology, - ): Promise { - // chainId will be correctly stringified by the jsonReplacer - await this.request('POST', '/witnesses', { wallet, chainId, payload, signatures }) - } - - async saveUpdate( - wallet: Address.Address, - configuration: Config.Config, - signature: Signature.RawSignature, - ): Promise { - // configuration and signature might contain BigInts, handled by replacer - await this.request('POST', '/update', { wallet, configuration, signature }) - } - - async saveTree(tree: GenericTree.Tree): Promise { - await this.request('POST', '/tree', { tree }) - } - - saveConfiguration(config: Config.Config): Promise { - return this.request('POST', '/configuration', { config }) - } - - saveDeploy(imageHash: Hex.Hex, context: Context.Context): Promise { - return this.request('POST', '/deploy', { imageHash, context }) - } - - async getPayload(opHash: Hex.Hex): Promise< - | { - chainId: number - payload: Payload.Parented - wallet: Address.Address - } - | undefined - > { - return this.request< - | { - chainId: number - payload: Payload.Parented - wallet: Address.Address - } - | undefined - >('GET', `/payload/${opHash}`) - } - - async savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: number): Promise { - return this.request('POST', '/payload', { wallet, payload, chainId }) - } -} diff --git a/packages/wallet/core/src/state/remote/index.ts b/packages/wallet/core/src/state/remote/index.ts deleted file mode 100644 index 893f1ca19c..0000000000 --- a/packages/wallet/core/src/state/remote/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dev-http.js' diff --git a/packages/wallet/core/src/state/sequence/index.ts b/packages/wallet/core/src/state/sequence/index.ts deleted file mode 100644 index 0842111530..0000000000 --- a/packages/wallet/core/src/state/sequence/index.ts +++ /dev/null @@ -1,690 +0,0 @@ -import { Config, Constants, Context, Extensions, GenericTree, Payload, Signature } from '@0xsequence/wallet-primitives' -import { - AbiFunction, - Address, - Bytes, - Hex, - Provider as oxProvider, - Signature as oxSignature, - TransactionRequest, -} from 'ox' -import { normalizeAddressKeys, Provider as ProviderInterface } from '../index.js' -import { Sessions, SignatureType, type Fetch } from './sessions.gen.js' - -export class Provider implements ProviderInterface { - private readonly service: Sessions - - constructor(host = 'https://keymachine.sequence.app', fetcher?: Fetch) { - const resolvedFetch = fetcher ?? (globalThis as any).fetch - if (!resolvedFetch) { - throw new Error('fetch is not available') - } - this.service = new Sessions(host, resolvedFetch) - } - - async getConfiguration(imageHash: Hex.Hex): Promise { - const { version, config } = await this.service.config({ imageHash }) - - if (version !== 3) { - throw new Error(`invalid configuration version ${version}, expected version 3`) - } - - return fromServiceConfig(config) - } - - async getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { - const { deployHash, context } = await this.service.deployHash({ wallet }) - - Hex.assert(deployHash) - Address.assert(context.factory) - Address.assert(context.mainModule) - Address.assert(context.mainModuleUpgradable) - Hex.assert(context.walletCreationCode) - - return { - imageHash: deployHash, - context: { - factory: context.factory, - stage1: context.mainModule, - stage2: context.mainModuleUpgradable, - creationCode: context.walletCreationCode, - }, - } - } - - async getWallets(signer: Address.Address): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSignerLeaf - } - }> { - const result = await this.service.wallets({ signer }) - const wallets = normalizeAddressKeys(result.wallets) - - return Object.fromEntries( - Object.entries(wallets).map(([wallet, signature]) => { - Address.assert(wallet) - Hex.assert(signature.signature) - - switch (signature.type) { - case SignatureType.EIP712: - return [ - wallet, - { - chainId: Number(signature.chainID), - payload: fromServicePayload(signature.payload), - signature: { type: 'hash', ...oxSignature.from(signature.signature) }, - }, - ] - case SignatureType.EthSign: - return [ - wallet, - { - chainId: Number(signature.chainID), - payload: fromServicePayload(signature.payload), - signature: { type: 'eth_sign', ...oxSignature.from(signature.signature) }, - }, - ] - case SignatureType.EIP1271: - return [ - wallet, - { - chainId: Number(signature.chainID), - payload: fromServicePayload(signature.payload), - signature: { type: 'erc1271', address: signer, data: signature.signature }, - }, - ] - case SignatureType.Sapient: - throw new Error(`unexpected sapient signature by ${signer}`) - case SignatureType.SapientCompact: - throw new Error(`unexpected compact sapient signature by ${signer}`) - } - }), - ) - } - - async getWalletsForSapient( - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise<{ - [wallet: Address.Address]: { - chainId: number - payload: Payload.Parented - signature: Signature.SignatureOfSapientSignerLeaf - } - }> { - const result = await this.service.wallets({ signer, sapientHash: imageHash }) - const wallets = normalizeAddressKeys(result.wallets) - - return Object.fromEntries( - Object.entries(wallets).map( - ([wallet, signature]): [ - Address.Address, - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf }, - ] => { - Address.assert(wallet) - Hex.assert(signature.signature) - - switch (signature.type) { - case SignatureType.EIP712: - throw new Error(`unexpected eip-712 signature by ${signer}`) - case SignatureType.EthSign: - throw new Error(`unexpected eth_sign signature by ${signer}`) - case SignatureType.EIP1271: - throw new Error(`unexpected erc-1271 signature by ${signer}`) - case SignatureType.Sapient: - return [ - wallet, - { - chainId: Number(signature.chainID), - payload: fromServicePayload(signature.payload), - signature: { type: 'sapient', address: signer, data: signature.signature }, - }, - ] - case SignatureType.SapientCompact: - return [ - wallet, - { - chainId: Number(signature.chainID), - payload: fromServicePayload(signature.payload), - signature: { type: 'sapient_compact', address: signer, data: signature.signature }, - }, - ] - } - }, - ), - ) - } - - async getWitnessFor( - wallet: Address.Address, - signer: Address.Address, - ): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> { - try { - const { witness } = await this.service.witness({ signer, wallet }) - - Hex.assert(witness.signature) - - switch (witness.type) { - case SignatureType.EIP712: - return { - chainId: Number(witness.chainID), - payload: fromServicePayload(witness.payload), - signature: { type: 'hash', ...oxSignature.from(witness.signature) }, - } - case SignatureType.EthSign: - return { - chainId: Number(witness.chainID), - payload: fromServicePayload(witness.payload), - signature: { type: 'eth_sign', ...oxSignature.from(witness.signature) }, - } - case SignatureType.EIP1271: - return { - chainId: Number(witness.chainID), - payload: fromServicePayload(witness.payload), - signature: { type: 'erc1271', address: signer, data: witness.signature }, - } - case SignatureType.Sapient: - throw new Error(`unexpected sapient signature by ${signer}`) - case SignatureType.SapientCompact: - throw new Error(`unexpected compact sapient signature by ${signer}`) - } - } catch { - // ignore - } - } - - async getWitnessForSapient( - wallet: Address.Address, - signer: Address.Address, - imageHash: Hex.Hex, - ): Promise< - { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined - > { - try { - const { witness } = await this.service.witness({ signer, wallet, sapientHash: imageHash }) - - Hex.assert(witness.signature) - - switch (witness.type) { - case SignatureType.EIP712: - throw new Error(`unexpected eip-712 signature by ${signer}`) - case SignatureType.EthSign: - throw new Error(`unexpected eth_sign signature by ${signer}`) - case SignatureType.EIP1271: - throw new Error(`unexpected erc-1271 signature by ${signer}`) - case SignatureType.Sapient: - return { - chainId: Number(witness.chainID), - payload: fromServicePayload(witness.payload), - signature: { type: 'sapient', address: signer, data: witness.signature }, - } - case SignatureType.SapientCompact: - return { - chainId: Number(witness.chainID), - payload: fromServicePayload(witness.payload), - signature: { type: 'sapient_compact', address: signer, data: witness.signature }, - } - } - } catch { - // ignore - } - } - - async getConfigurationUpdates( - wallet: Address.Address, - fromImageHash: Hex.Hex, - options?: { allUpdates?: boolean }, - ): Promise> { - const { updates } = await this.service.configUpdates({ wallet, fromImageHash, allUpdates: options?.allUpdates }) - - return Promise.all( - updates.map(async ({ toImageHash, signature }) => { - Hex.assert(toImageHash) - Hex.assert(signature) - - const decoded = Signature.decodeSignature(Hex.toBytes(signature)) - - const { configuration } = await Signature.recover(decoded, wallet, 0, Payload.fromConfigUpdate(toImageHash), { - provider: passkeySignatureValidator, - }) - - return { imageHash: toImageHash, signature: { ...decoded, configuration } } - }), - ) - } - - async getTree(rootHash: Hex.Hex): Promise { - const { version, tree } = await this.service.tree({ imageHash: rootHash }) - - if (version !== 3) { - throw new Error(`invalid tree version ${version}, expected version 3`) - } - - return fromServiceTree(tree) - } - - async getPayload( - opHash: Hex.Hex, - ): Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> { - const { version, payload, wallet, chainID } = await this.service.payload({ digest: opHash }) - - if (version !== 3) { - throw new Error(`invalid payload version ${version}, expected version 3`) - } - - Address.assert(wallet) - - return { payload: fromServicePayload(payload), wallet, chainId: Number(chainID) } - } - - async saveWallet(deployConfiguration: Config.Config, context: Context.Context): Promise { - await this.service.saveWallet({ - version: 3, - deployConfig: getServiceConfig(deployConfiguration), - context: { - version: 3, - factory: context.factory, - mainModule: context.stage1, - mainModuleUpgradable: context.stage2, - guestModule: Constants.DefaultGuestAddress, - walletCreationCode: context.creationCode, - }, - }) - } - - async saveWitnesses( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - signatures: Signature.RawTopology, - ): Promise { - await this.service.saveSignerSignatures3({ - wallet, - payload: getServicePayload(payload), - chainID: chainId.toString(), - signatures: getSignerSignatures(signatures).map((signature) => { - switch (signature.type) { - case 'hash': - return { type: SignatureType.EIP712, signature: oxSignature.toHex(oxSignature.from(signature)) } - case 'eth_sign': - return { type: SignatureType.EthSign, signature: oxSignature.toHex(oxSignature.from(signature)) } - case 'erc1271': - return { - type: SignatureType.EIP1271, - signer: signature.address, - signature: signature.data, - referenceChainID: chainId.toString(), - } - case 'sapient': - return { - type: SignatureType.Sapient, - signer: signature.address, - signature: signature.data, - referenceChainID: chainId.toString(), - } - case 'sapient_compact': - return { - type: SignatureType.SapientCompact, - signer: signature.address, - signature: signature.data, - referenceChainID: chainId.toString(), - } - } - }), - }) - } - - async saveUpdate( - wallet: Address.Address, - configuration: Config.Config, - signature: Signature.RawSignature, - ): Promise { - await this.service.saveSignature2({ - wallet, - payload: getServicePayload(Payload.fromConfigUpdate(Bytes.toHex(Config.hashConfiguration(configuration)))), - chainID: '0', - signature: Bytes.toHex(Signature.encodeSignature(signature)), - toConfig: getServiceConfig(configuration), - }) - } - - async saveTree(tree: GenericTree.Tree): Promise { - await this.service.saveTree({ version: 3, tree: getServiceTree(tree) }) - } - - async saveConfiguration(config: Config.Config): Promise { - await this.service.saveConfig({ version: 3, config: getServiceConfig(config) }) - } - - async saveDeploy(_imageHash: Hex.Hex, _context: Context.Context): Promise { - // TODO: save deploy hash even if we don't have its configuration - } - - async savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: number): Promise { - await this.service.savePayload({ - version: 3, - payload: getServicePayload(payload), - wallet, - chainID: chainId.toString(), - }) - } -} - -const passkeySigners = [ - Extensions.Dev1.passkeys, - Extensions.Dev2.passkeys, - Extensions.Rc3.passkeys, - Extensions.Rc4.passkeys, - Extensions.Rc5.passkeys, -].map(Address.checksum) - -const recoverSapientSignatureCompactSignature = - 'function recoverSapientSignatureCompact(bytes32 _digest, bytes _signature) view returns (bytes32)' - -const recoverSapientSignatureCompactFunction = AbiFunction.from(recoverSapientSignatureCompactSignature) - -class PasskeySignatureValidator implements oxProvider.Provider { - request: oxProvider.Provider['request'] = (async (request) => { - switch (request.method) { - case 'eth_call': { - if (!request.params || !Array.isArray(request.params) || request.params.length === 0) { - throw new Error('eth_call requires transaction parameters') - } - - const transaction: TransactionRequest.Rpc = request.params[0] - - if (!transaction.data?.startsWith(AbiFunction.getSelector(recoverSapientSignatureCompactFunction))) { - throw new Error( - `unknown selector ${transaction.data?.slice(0, 10)}, expected selector ${AbiFunction.getSelector(recoverSapientSignatureCompactFunction)} for ${recoverSapientSignatureCompactSignature}`, - ) - } - - if (!passkeySigners.includes(transaction.to ? Address.checksum(transaction.to) : '0x')) { - throw new Error(`unknown passkey signer ${transaction.to}`) - } - - const [digest, signature] = AbiFunction.decodeData(recoverSapientSignatureCompactFunction, transaction.data) - - const decoded = Extensions.Passkeys.decode(Hex.toBytes(signature)) - - if (Extensions.Passkeys.isValidSignature(digest, decoded)) { - return Extensions.Passkeys.rootFor(decoded.publicKey) - } else { - throw new Error(`invalid passkey signature ${signature} for digest ${digest}`) - } - } - - default: - throw new Error(`method ${request.method} not implemented`) - } - }) as oxProvider.Provider['request'] - - on: oxProvider.Provider['on'] = (event: string) => { - throw new Error(`unable to listen for ${event}: not implemented`) - } - - removeListener: oxProvider.Provider['removeListener'] = (event: string) => { - throw new Error(`unable to remove listener for ${event}: not implemented`) - } -} - -const passkeySignatureValidator = new PasskeySignatureValidator() - -type ServiceConfig = { - threshold: number | string - checkpoint: number | string - checkpointer?: string - tree: ServiceConfigTree -} - -type ServiceConfigTree = - | [ServiceConfigTree, ServiceConfigTree] - | string - | { weight: number | string; address: string; imageHash?: string } - | { weight: number | string; threshold: number | string; tree: ServiceConfigTree } - | { subdigest: string; isAnyAddress?: boolean } - -type ServicePayload = - | { type: 'call'; space: number | string; nonce: number | string; calls: ServicePayloadCall[] } - | { type: 'message'; message: string } - | { type: 'config-update'; imageHash: string } - | { type: 'digest'; digest: string } - -type ServicePayloadCall = { - to: string - value: number | string - data: string - gasLimit: number | string - delegateCall: boolean - onlyFallback: boolean - behaviorOnError: 'ignore' | 'revert' | 'abort' -} - -type ServiceTree = string | { data: string } | ServiceTree[] - -function getServiceConfig(config: Config.Config): ServiceConfig { - return { - threshold: encodeBigInt(config.threshold), - checkpoint: encodeBigInt(config.checkpoint), - checkpointer: config.checkpointer, - tree: getServiceConfigTree(config.topology), - } -} - -function fromServiceConfig(config: ServiceConfig): Config.Config { - if (config.checkpointer !== undefined) { - Address.assert(config.checkpointer) - } - - return { - threshold: BigInt(config.threshold), - checkpoint: BigInt(config.checkpoint), - checkpointer: config.checkpointer, - topology: fromServiceConfigTree(config.tree), - } -} - -function getServiceConfigTree(topology: Config.Topology): ServiceConfigTree { - if (Config.isNode(topology)) { - return [getServiceConfigTree(topology[0]), getServiceConfigTree(topology[1])] - } else if (Config.isSignerLeaf(topology)) { - return { weight: encodeBigInt(topology.weight), address: topology.address } - } else if (Config.isSapientSignerLeaf(topology)) { - return { weight: encodeBigInt(topology.weight), address: topology.address, imageHash: topology.imageHash } - } else if (Config.isSubdigestLeaf(topology)) { - return { subdigest: topology.digest } - } else if (Config.isAnyAddressSubdigestLeaf(topology)) { - return { subdigest: topology.digest, isAnyAddress: true } - } else if (Config.isNestedLeaf(topology)) { - return { - weight: encodeBigInt(topology.weight), - threshold: encodeBigInt(topology.threshold), - tree: getServiceConfigTree(topology.tree), - } - } else if (Config.isNodeLeaf(topology)) { - return topology - } else { - throw new Error(`unknown topology '${JSON.stringify(topology)}'`) - } -} - -function fromServiceConfigTree(tree: ServiceConfigTree): Config.Topology { - switch (typeof tree) { - case 'string': - Hex.assert(tree) - return tree - - case 'object': - if (tree instanceof Array) { - return [fromServiceConfigTree(tree[0]), fromServiceConfigTree(tree[1])] - } - - if ('weight' in tree) { - if ('address' in tree) { - Address.assert(tree.address) - - if (tree.imageHash) { - Hex.assert(tree.imageHash) - return { - type: 'sapient-signer', - address: tree.address, - weight: BigInt(tree.weight), - imageHash: tree.imageHash, - } - } else { - return { type: 'signer', address: tree.address, weight: BigInt(tree.weight) } - } - } - - if ('tree' in tree) { - return { - type: 'nested', - weight: BigInt(tree.weight), - threshold: BigInt(tree.threshold), - tree: fromServiceConfigTree(tree.tree), - } - } - } - - if ('subdigest' in tree) { - Hex.assert(tree.subdigest) - return { type: tree.isAnyAddress ? 'any-address-subdigest' : 'subdigest', digest: tree.subdigest } - } - } - - throw new Error(`unknown config tree '${JSON.stringify(tree)}'`) -} - -function getServicePayload(payload: Payload.Payload): ServicePayload { - if (Payload.isCalls(payload)) { - return { - type: 'call', - space: encodeBigInt(payload.space), - nonce: encodeBigInt(payload.nonce), - calls: payload.calls.map(getServicePayloadCall), - } - } else if (Payload.isMessage(payload)) { - return { type: 'message', message: payload.message } - } else if (Payload.isConfigUpdate(payload)) { - return { type: 'config-update', imageHash: payload.imageHash } - } else if (Payload.isDigest(payload)) { - return { type: 'digest', digest: payload.digest } - } else { - throw new Error(`unknown payload '${JSON.stringify(payload)}'`) - } -} - -function fromServicePayload(payload: ServicePayload): Payload.Payload { - switch (payload.type) { - case 'call': - return { - type: 'call', - space: BigInt(payload.space), - nonce: BigInt(payload.nonce), - calls: payload.calls.map(fromServicePayloadCall), - } - - case 'message': - Hex.assert(payload.message) - return { type: 'message', message: payload.message } - - case 'config-update': - Hex.assert(payload.imageHash) - return { type: 'config-update', imageHash: payload.imageHash } - - case 'digest': - Hex.assert(payload.digest) - return { type: 'digest', digest: payload.digest } - } -} - -function getServicePayloadCall(call: Payload.Call): ServicePayloadCall { - return { - to: call.to, - value: encodeBigInt(call.value), - data: call.data, - gasLimit: encodeBigInt(call.gasLimit), - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: call.behaviorOnError, - } -} - -function fromServicePayloadCall(call: ServicePayloadCall): Payload.Call { - Address.assert(call.to) - Hex.assert(call.data) - - return { - to: call.to, - value: BigInt(call.value), - data: call.data, - gasLimit: BigInt(call.gasLimit), - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: call.behaviorOnError, - } -} - -function getServiceTree(tree: GenericTree.Tree): ServiceTree { - if (GenericTree.isBranch(tree)) { - return tree.map(getServiceTree) - } else if (GenericTree.isLeaf(tree)) { - return { data: Bytes.toHex(tree.value) } - } else if (GenericTree.isNode(tree)) { - return tree - } else { - throw new Error(`unknown tree '${JSON.stringify(tree)}'`) - } -} - -function fromServiceTree(tree: ServiceTree): GenericTree.Tree { - switch (typeof tree) { - case 'string': - Hex.assert(tree) - return tree - - case 'object': - if (tree instanceof Array) { - return tree.map(fromServiceTree) as GenericTree.Branch - } - - if ('data' in tree) { - Hex.assert(tree.data) - return { type: 'leaf', value: Hex.toBytes(tree.data) } - } - } - - throw new Error(`unknown tree '${JSON.stringify(tree)}'`) -} - -function encodeBigInt(value: bigint): number | string { - return value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER ? value.toString() : Number(value) -} - -function getSignerSignatures( - topology: Signature.RawTopology, -): Array { - if (Signature.isRawNode(topology)) { - return [...getSignerSignatures(topology[0]), ...getSignerSignatures(topology[1])] - } else if (Signature.isRawSignerLeaf(topology)) { - return [topology.signature] - } else if (Config.isNestedLeaf(topology)) { - return getSignerSignatures(topology.tree) - } else if (Signature.isRawNestedLeaf(topology)) { - return getSignerSignatures(topology.tree) - } else if (Config.isSignerLeaf(topology)) { - return topology.signature ? [topology.signature] : [] - } else if (Config.isSapientSignerLeaf(topology)) { - return topology.signature ? [topology.signature] : [] - } else if (Config.isSubdigestLeaf(topology)) { - return [] - } else if (Config.isAnyAddressSubdigestLeaf(topology)) { - return [] - } else if (Config.isNodeLeaf(topology)) { - return [] - } else { - throw new Error(`unknown topology '${JSON.stringify(topology)}'`) - } -} diff --git a/packages/wallet/core/src/state/sequence/sessions.gen.ts b/packages/wallet/core/src/state/sequence/sessions.gen.ts deleted file mode 100644 index c071935fd1..0000000000 --- a/packages/wallet/core/src/state/sequence/sessions.gen.ts +++ /dev/null @@ -1,1021 +0,0 @@ -/* eslint-disable */ -// sessions v0.0.1 7f7ab1f70cc9f789cfe5317c9378f0c66895f141 -// -- -// Code generated by webrpc-gen@v0.22.1 with typescript generator. DO NOT EDIT. -// -// webrpc-gen -schema=sessions.ridl -target=typescript -client -out=./clients/sessions.gen.ts - -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' - -// Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.0.1' - -// Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '7f7ab1f70cc9f789cfe5317c9378f0c66895f141' - -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} - -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - return parseWebrpcGenVersions(headerValue) -} - -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } - - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') - - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} - -// -// Types -// - -export enum PayloadType { - Transactions = 'Transactions', - Message = 'Message', - ConfigUpdate = 'ConfigUpdate', - Digest = 'Digest', -} - -export enum SignatureType { - EIP712 = 'EIP712', - EthSign = 'EthSign', - EIP1271 = 'EIP1271', - Sapient = 'Sapient', - SapientCompact = 'SapientCompact', -} - -export interface RuntimeStatus { - healthy: boolean - started: string - uptime: number - version: string - branch: string - commit: string - arweave: ArweaveStatus -} - -export interface ArweaveStatus { - nodeURL: string - namespace: string - sender: string - signer: string - flushInterval: string - bundleDelay: string - bundleLimit: number - confirmations: number - lockTTL: string - healthy: boolean - lastFlush?: string - lastFlushSeconds?: number -} - -export interface Info { - wallets: { [key: string]: number } - configs: { [key: string]: number } - configTrees: number - trees: number - migrations: { [key: string]: number } - signatures: number - sapientSignatures: number - digests: number - payloads: number - recorder: RecorderInfo - arweave: ArweaveInfo -} - -export interface RecorderInfo { - requests: number - buffer: number - lastFlush?: string - lastFlushSeconds?: number - endpoints: { [key: string]: number } -} - -export interface ArweaveInfo { - nodeURL: string - namespace: string - sender: ArweaveSenderInfo - signer: string - flushInterval: string - bundleDelay: string - bundleLimit: number - confirmations: number - lockTTL: string - healthy: boolean - lastFlush?: string - lastFlushSeconds?: number - bundles: number - pending: ArweavePendingInfo -} - -export interface ArweaveSenderInfo { - address: string - balance: string -} - -export interface ArweavePendingInfo { - wallets: number - configs: number - trees: number - migrations: number - signatures: number - sapientSignatures: number - payloads: number - bundles: Array -} - -export interface ArweaveBundleInfo { - transaction: string - block: number - items: number - sentAt: string - confirmations: number -} - -export interface Context { - version: number - factory: string - mainModule: string - mainModuleUpgradable: string - guestModule: string - walletCreationCode: string -} - -export interface Signature { - digest?: string - payload?: any - toImageHash?: string - chainID: string - type: SignatureType - signature: string - sapientHash?: string - validOnChain?: string - validOnBlock?: string - validOnBlockHash?: string -} - -export interface SignerSignature { - signer?: string - signature: string - referenceChainID?: string -} - -export interface SignerSignature2 { - signer?: string - imageHash?: string - type: SignatureType - signature: string - referenceChainID?: string -} - -export interface ConfigUpdate { - toImageHash: string - signature: string -} - -export interface Transaction { - to: string - value?: string - data?: string - gasLimit?: string - delegateCall?: boolean - revertOnError?: boolean -} - -export interface TransactionBundle { - executor: string - transactions: Array - nonce: string - signature: string -} - -export interface Sessions { - ping(headers?: object, signal?: AbortSignal): Promise - config(args: ConfigArgs, headers?: object, signal?: AbortSignal): Promise - tree(args: TreeArgs, headers?: object, signal?: AbortSignal): Promise - payload(args: PayloadArgs, headers?: object, signal?: AbortSignal): Promise - wallets(args: WalletsArgs, headers?: object, signal?: AbortSignal): Promise - deployHash(args: DeployHashArgs, headers?: object, signal?: AbortSignal): Promise - witness(args: WitnessArgs, headers?: object, signal?: AbortSignal): Promise - configUpdates(args: ConfigUpdatesArgs, headers?: object, signal?: AbortSignal): Promise - migrations(args: MigrationsArgs, headers?: object, signal?: AbortSignal): Promise - saveConfig(args: SaveConfigArgs, headers?: object, signal?: AbortSignal): Promise - saveTree(args: SaveTreeArgs, headers?: object, signal?: AbortSignal): Promise - savePayload(args: SavePayloadArgs, headers?: object, signal?: AbortSignal): Promise - saveWallet(args: SaveWalletArgs, headers?: object, signal?: AbortSignal): Promise - saveSignature(args: SaveSignatureArgs, headers?: object, signal?: AbortSignal): Promise - saveSignature2(args: SaveSignature2Args, headers?: object, signal?: AbortSignal): Promise - saveSignerSignatures( - args: SaveSignerSignaturesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - saveSignerSignatures2( - args: SaveSignerSignatures2Args, - headers?: object, - signal?: AbortSignal, - ): Promise - saveSignerSignatures3( - args: SaveSignerSignatures3Args, - headers?: object, - signal?: AbortSignal, - ): Promise - saveMigration(args: SaveMigrationArgs, headers?: object, signal?: AbortSignal): Promise -} - -export interface PingArgs {} - -export interface PingReturn {} -export interface ConfigArgs { - imageHash: string -} - -export interface ConfigReturn { - version: number - config: any -} -export interface TreeArgs { - imageHash: string -} - -export interface TreeReturn { - version: number - tree: any -} -export interface PayloadArgs { - digest: string -} - -export interface PayloadReturn { - version: number - payload: any - wallet: string - chainID: string -} -export interface WalletsArgs { - signer: string - sapientHash?: string - cursor?: number - limit?: number -} - -export interface WalletsReturn { - wallets: { [key: string]: Signature } - cursor: number -} -export interface DeployHashArgs { - wallet: string -} - -export interface DeployHashReturn { - deployHash: string - context: Context -} -export interface WitnessArgs { - signer: string - wallet: string - sapientHash?: string -} - -export interface WitnessReturn { - witness: Signature -} -export interface ConfigUpdatesArgs { - wallet: string - fromImageHash: string - allUpdates?: boolean -} - -export interface ConfigUpdatesReturn { - updates: Array -} -export interface MigrationsArgs { - wallet: string - fromVersion: number - fromImageHash: string - chainID?: string -} - -export interface MigrationsReturn { - migrations: { [key: string]: { [key: number]: { [key: string]: TransactionBundle } } } -} -export interface SaveConfigArgs { - version: number - config: any -} - -export interface SaveConfigReturn {} -export interface SaveTreeArgs { - version: number - tree: any -} - -export interface SaveTreeReturn {} -export interface SavePayloadArgs { - version: number - payload: any - wallet: string - chainID: string -} - -export interface SavePayloadReturn {} -export interface SaveWalletArgs { - version: number - deployConfig: any - context?: Context -} - -export interface SaveWalletReturn {} -export interface SaveSignatureArgs { - wallet: string - digest: string - chainID: string - signature: string - toConfig?: any - referenceChainID?: string -} - -export interface SaveSignatureReturn {} -export interface SaveSignature2Args { - wallet: string - payload: any - chainID: string - signature: string - toConfig?: any - referenceChainID?: string -} - -export interface SaveSignature2Return {} -export interface SaveSignerSignaturesArgs { - wallet: string - digest: string - chainID: string - signatures: Array - toConfig?: any -} - -export interface SaveSignerSignaturesReturn {} -export interface SaveSignerSignatures2Args { - wallet: string - digest: string - chainID: string - signatures: Array - toConfig?: any -} - -export interface SaveSignerSignatures2Return {} -export interface SaveSignerSignatures3Args { - wallet: string - payload: any - chainID: string - signatures: Array - toConfig?: any -} - -export interface SaveSignerSignatures3Return {} -export interface SaveMigrationArgs { - wallet: string - fromVersion: number - toVersion: number - toConfig: any - executor: string - transactions: Array - nonce: string - signature: string - chainID?: string -} - -export interface SaveMigrationReturn {} - -// -// Client -// -export class Sessions implements Sessions { - protected hostname: string - protected fetch: Fetch - protected path = '/rpc/Sessions/' - - constructor(hostname: string, fetch: Fetch) { - this.hostname = hostname.replace(/\/*$/, '') - this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) - } - - private url(name: string): string { - return this.hostname + this.path + name - } - - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - config = (args: ConfigArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Config'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - version: _data.version, - config: _data.config, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - tree = (args: TreeArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Tree'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - version: _data.version, - tree: _data.tree, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - payload = (args: PayloadArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Payload'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - version: _data.version, - payload: _data.payload, - wallet: _data.wallet, - chainID: _data.chainID, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - wallets = (args: WalletsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Wallets'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - wallets: <{ [key: string]: Signature }>_data.wallets, - cursor: _data.cursor, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - deployHash = (args: DeployHashArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('DeployHash'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - deployHash: _data.deployHash, - context: _data.context, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - witness = (args: WitnessArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Witness'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - witness: _data.witness, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - configUpdates = (args: ConfigUpdatesArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ConfigUpdates'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - updates: >_data.updates, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - migrations = (args: MigrationsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Migrations'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - migrations: <{ [key: string]: { [key: number]: { [key: string]: TransactionBundle } } }>_data.migrations, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveConfig = (args: SaveConfigArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SaveConfig'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveTree = (args: SaveTreeArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SaveTree'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - savePayload = (args: SavePayloadArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SavePayload'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveWallet = (args: SaveWalletArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SaveWallet'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveSignature = (args: SaveSignatureArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SaveSignature'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveSignature2 = ( - args: SaveSignature2Args, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SaveSignature2'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveSignerSignatures = ( - args: SaveSignerSignaturesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SaveSignerSignatures'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveSignerSignatures2 = ( - args: SaveSignerSignatures2Args, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SaveSignerSignatures2'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveSignerSignatures3 = ( - args: SaveSignerSignatures3Args, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SaveSignerSignatures3'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - saveMigration = (args: SaveMigrationArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SaveMigration'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return {} - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } -} - -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, - } -} - -const buildResponse = (res: Response): Promise => { - return res.text().then((text) => { - let data - try { - data = JSON.parse(text) - } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } - throw WebrpcBadResponseError.new({ - status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, - }) - } - if (!res.ok) { - const code: number = typeof data.code === 'number' ? data.code : 0 - throw (webrpcErrorByCode[code] || WebrpcError).new(data) - } - return data - }) -} - -// -// Errors -// - -export class WebrpcError extends Error { - name: string - code: number - message: string - status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause - Object.setPrototypeOf(this, WebrpcError.prototype) - } - - static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) - } -} - -// Webrpc errors - -export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcEndpointError.prototype) - } -} - -export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) - } -} - -export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) - } -} - -export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) - } -} - -export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) - } -} - -export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) - } -} - -export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) - } -} - -export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) - } -} - -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) - } -} - -export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) - } -} - -export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) - } -} - -// Schema errors - -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 1, - message: string = `invalid argument`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) - } -} - -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 2, - message: string = `not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) - } -} - -export enum errors { - WebrpcEndpoint = 'WebrpcEndpoint', - WebrpcRequestFailed = 'WebrpcRequestFailed', - WebrpcBadRoute = 'WebrpcBadRoute', - WebrpcBadMethod = 'WebrpcBadMethod', - WebrpcBadRequest = 'WebrpcBadRequest', - WebrpcBadResponse = 'WebrpcBadResponse', - WebrpcServerPanic = 'WebrpcServerPanic', - WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', - WebrpcStreamLost = 'WebrpcStreamLost', - WebrpcStreamFinished = 'WebrpcStreamFinished', - InvalidArgument = 'InvalidArgument', - NotFound = 'NotFound', -} - -export enum WebrpcErrorCodes { - WebrpcEndpoint = 0, - WebrpcRequestFailed = -1, - WebrpcBadRoute = -2, - WebrpcBadMethod = -3, - WebrpcBadRequest = -4, - WebrpcBadResponse = -5, - WebrpcServerPanic = -6, - WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, - WebrpcStreamLost = -9, - WebrpcStreamFinished = -10, - InvalidArgument = 1, - NotFound = 2, -} - -export const webrpcErrorByCode: { [code: number]: any } = { - [0]: WebrpcEndpointError, - [-1]: WebrpcRequestFailedError, - [-2]: WebrpcBadRouteError, - [-3]: WebrpcBadMethodError, - [-4]: WebrpcBadRequestError, - [-5]: WebrpcBadResponseError, - [-6]: WebrpcServerPanicError, - [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, - [-9]: WebrpcStreamLostError, - [-10]: WebrpcStreamFinishedError, - [1]: InvalidArgumentError, - [2]: NotFoundError, -} - -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/wallet/core/src/state/utils.ts b/packages/wallet/core/src/state/utils.ts deleted file mode 100644 index f648e9abe3..0000000000 --- a/packages/wallet/core/src/state/utils.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Payload, Signature } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { Reader } from './index.js' -import { isSapientSigner, SapientSigner, Signer } from '../signers/index.js' - -export type WalletWithWitness = { - wallet: Address.Address - chainId: number - payload: Payload.Parented - signature: S extends SapientSigner ? Signature.SignatureOfSapientSignerLeaf : Signature.SignatureOfSignerLeaf -} - -export async function getWalletsFor( - stateReader: Reader, - signer: S, -): Promise>> { - const wallets = await retrieveWallets(stateReader, signer) - return Object.entries(wallets).map(([wallet, { chainId, payload, signature }]) => { - Hex.assert(wallet) - return { - wallet, - chainId, - payload, - signature, - } - }) -} - -async function retrieveWallets( - stateReader: Reader, - signer: S, -): Promise<{ - [wallet: `0x${string}`]: { - chainId: number - payload: Payload.Parented - signature: S extends SapientSigner ? Signature.SignatureOfSapientSignerLeaf : Signature.SignatureOfSignerLeaf - } -}> { - if (isSapientSigner(signer)) { - const [signerAddress, signerImageHash] = await Promise.all([signer.address, signer.imageHash]) - if (signerImageHash) { - return stateReader.getWalletsForSapient(signerAddress, signerImageHash) as unknown as any - } else { - console.warn('Sapient signer has no imageHash') - return {} as any - } - } else { - return stateReader.getWallets(await signer.address) as unknown as any - } -} - -export function normalizeAddressKeys>(obj: T): Record { - return Object.fromEntries( - Object.entries(obj).map(([wallet, signature]) => { - const checksumAddress = Address.checksum(wallet) - return [checksumAddress, signature] - }), - ) as Record -} diff --git a/packages/wallet/core/src/utils/index.ts b/packages/wallet/core/src/utils/index.ts deleted file mode 100644 index 7139676b3f..0000000000 --- a/packages/wallet/core/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './session/permission-builder.js' diff --git a/packages/wallet/core/src/utils/session/permission-builder.ts b/packages/wallet/core/src/utils/session/permission-builder.ts deleted file mode 100644 index c77580a188..0000000000 --- a/packages/wallet/core/src/utils/session/permission-builder.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { Permission } from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Bytes } from 'ox' - -/** - * Parses a human-readable signature like - * "function foo(uint256 x, address to, bytes data)" - * into parallel arrays of types and (optional) names. - */ -function parseSignature(sig: string): { types: string[]; names: (string | undefined)[] } { - const m = sig.match(/\(([^)]*)\)/) - if (!m) throw new Error(`Invalid function signature: ${sig}`) - const inner = m[1]?.trim() ?? '' - if (inner === '') return { types: [], names: [] } - - const parts = inner.split(',').map((p) => p.trim()) - const types = parts.map((p) => { - const t = p.split(/\s+/)[0] - if (!t) throw new Error(`Invalid parameter in signature: "${p}"`) - return t - }) - const names = parts.map((p) => { - const seg = p.split(/\s+/) - return seg.length > 1 ? seg[1] : undefined - }) - - return { types, names } -} - -function isDynamicType(type: string): boolean { - return type === 'bytes' || type === 'string' || type.endsWith('[]') || type.includes('(') -} - -export class PermissionBuilder { - private target: Address.Address - private rules: Permission.ParameterRule[] = [] - private fnTypes?: string[] - private fnNames?: (string | undefined)[] - private allowAllSet: boolean = false - private exactCalldataSet: boolean = false - - private constructor(target: Address.Address) { - this.target = target - } - - static for(target: Address.Address): PermissionBuilder { - return new PermissionBuilder(target) - } - - allowAll(): this { - if (this.rules.length > 0) { - throw new Error(`cannot call allowAll() after adding rules`) - } - this.allowAllSet = true - return this - } - - exactCalldata(calldata: Bytes.Bytes): this { - if (this.allowAllSet || this.rules.length > 0) { - throw new Error(`cannot call exactCalldata() after calling allowAll() or adding rules`) - } - for (let offset = 0; offset < calldata.length; offset += 32) { - let value: Bytes.Bytes = calldata.slice(offset, offset + 32) - let mask: Bytes.Bytes = Permission.MASK.BYTES32 - if (value.length < 32) { - mask = Bytes.fromHex(`0x${'ff'.repeat(value.length)}${'00'.repeat(32 - value.length)}`) - value = Bytes.padRight(value, 32) - } - this.rules.push({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value, - offset: BigInt(offset), - mask, - }) - } - this.exactCalldataSet = true - return this - } - - forFunction(sig: string | AbiFunction.AbiFunction): this { - if (this.allowAllSet || this.exactCalldataSet) { - throw new Error(`cannot call forFunction(...) after calling allowAll() or exactCalldata()`) - } - const selector = AbiFunction.getSelector(sig) - this.rules.push({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.from(selector), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - if (typeof sig === 'string') { - const { types, names } = parseSignature(sig) - this.fnTypes = types - this.fnNames = names - } else { - const fn = AbiFunction.from(sig) - this.fnTypes = fn.inputs.map((i) => i.type) - this.fnNames = fn.inputs.map((i) => i.name) - } - return this - } - - private findOffset(param: string | number, expectedType?: string): bigint { - if (!this.fnTypes || !this.fnNames) { - throw new Error(`must call forFunction(...) first`) - } - const idx = typeof param === 'number' ? param : this.fnNames.indexOf(param) - if (idx < 0 || idx >= this.fnTypes.length) { - throw new Error(`Unknown param "${param}" in function`) - } - if (expectedType && this.fnTypes[idx] !== expectedType) { - throw new Error(`type "${this.fnTypes[idx]}" is not ${expectedType}; cannot apply parameter rule`) - } - return 4n + 32n * BigInt(idx) - } - - private addRule( - param: string | number, - expectedType: string, - mask: Bytes.Bytes, - operation: Permission.ParameterOperation, - rawValue: bigint | Bytes.Bytes, - cumulative = false, - ): this { - const offset = this.findOffset(param, expectedType) - - // turn bigint → padded 32-byte, or Bytes → padded‐left 32-byte - const value = - typeof rawValue === 'bigint' ? Bytes.fromNumber(rawValue, { size: 32 }) : Bytes.padLeft(Bytes.from(rawValue), 32) - - this.rules.push({ cumulative, operation, value, offset, mask }) - return this - } - - withUintNParam( - param: string | number, - value: bigint, - bits: 8 | 16 | 32 | 64 | 128 | 256 = 256, - operation: Permission.ParameterOperation = Permission.ParameterOperation.EQUAL, - cumulative = false, - ): this { - const typeName = `uint${bits}` - const mask = Permission.MASK[`UINT${bits}` as keyof typeof Permission.MASK] - return this.addRule(param, typeName, mask, operation, value, cumulative) - } - - withIntNParam( - param: string | number, - value: bigint, - bits: 8 | 16 | 32 | 64 | 128 | 256 = 256, - operation: Permission.ParameterOperation = Permission.ParameterOperation.EQUAL, - cumulative = false, - ): this { - const typeName = `int${bits}` - const mask = Permission.MASK[`INT${bits}` as keyof typeof Permission.MASK] - return this.addRule(param, typeName, mask, operation, value, cumulative) - } - - withBytesNParam( - param: string | number, - value: Bytes.Bytes, - size: 1 | 2 | 4 | 8 | 16 | 32 = 32, - operation: Permission.ParameterOperation = Permission.ParameterOperation.EQUAL, - cumulative = false, - ): this { - const typeName = `bytes${size}` - const mask = Permission.MASK[`BYTES${size}` as keyof typeof Permission.MASK] - return this.addRule(param, typeName, mask, operation, value, cumulative) - } - - withAddressParam( - param: string | number, - value: Address.Address, - operation: Permission.ParameterOperation = Permission.ParameterOperation.EQUAL, - cumulative = false, - ): this { - return this.addRule( - param, - 'address', - Permission.MASK.ADDRESS, - operation, - Bytes.padLeft(Bytes.fromHex(value), 32), - cumulative, - ) - } - - withBoolParam( - param: string | number, - value: boolean, - operation: Permission.ParameterOperation = Permission.ParameterOperation.EQUAL, - cumulative = false, - ): this { - // solidity bool is encoded as 0 or 1, 32-bytes left-padded - return this.addRule(param, 'bool', Permission.MASK.BOOL, operation, value ? 1n : 0n, cumulative) - } - - private withDynamicAtOffset(pointerOffset: bigint, value: Bytes.Bytes): this { - // FIXME We can't predict the offset of the dynamic part if there are multiple dynamic params - if (this.fnTypes!.filter(isDynamicType).length !== 1) { - throw new Error(`multiple dynamic params are not supported`) - } - - // compute where this dynamic block will actually live - const dynStart = 32n * BigInt(this.fnTypes!.length) - - // Pointer rule - this.rules.push({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - mask: Permission.MASK.UINT256, - offset: pointerOffset, - value: Bytes.fromNumber(dynStart, { size: 32 }), - }) - - // Length rule - this.rules.push({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - mask: Permission.MASK.UINT256, - offset: 4n + dynStart, - value: Bytes.fromNumber(BigInt(value.length), { size: 32 }), - }) - - // Chunks - const chunks: Bytes.Bytes[] = [] - for (let i = 0; i < value.length; i += 32) { - const slice = value.slice(i, i + 32) - chunks.push(Bytes.padRight(slice, 32)) - } - chunks.forEach((chunk, i) => { - this.rules.push({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - mask: Permission.MASK.BYTES32, - offset: 4n + dynStart + 32n + 32n * BigInt(i), - value: chunk, - }) - }) - - return this - } - - withBytesParam(param: string | number, value: Bytes.Bytes): this { - const offset = this.findOffset(param, 'bytes') - return this.withDynamicAtOffset(offset, value) - } - - withStringParam(param: string | number, text: string): this { - const offset = this.findOffset(param, 'string') - return this.withDynamicAtOffset(offset, Bytes.fromString(text)) - } - - onlyOnce(): this { - if (this.rules.length === 0) { - throw new Error(`must call forFunction(...) before calling onlyOnce()`) - } - const selectorRule = this.rules.find((r) => r.offset === 0n && Bytes.isEqual(r.mask, Permission.MASK.SELECTOR)) - if (!selectorRule) { - throw new Error(`can call onlyOnce() after adding rules that match the selector`) - } - // Update the selector rule to be cumulative. This ensure the selector rule can only be matched once. - selectorRule.cumulative = true - return this - } - - build(): Permission.Permission { - if (this.rules.length === 0 && !this.allowAllSet && !this.exactCalldataSet) { - throw new Error(`must call forFunction(...) or allowAll() or exactCalldata() before calling build()`) - } - return { - target: this.target, - rules: this.rules, - } - } -} - -/** - * Builds permissions for an ERC20 token. - */ -export class ERC20PermissionBuilder { - static buildTransfer(target: Address.Address, limit: bigint): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function transfer(address to, uint256 value)') - .withUintNParam('value', limit, 256, Permission.ParameterOperation.LESS_THAN_OR_EQUAL, true) - .build() - } - - static buildApprove(target: Address.Address, spender: Address.Address, limit: bigint): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function approve(address spender, uint256 value)') - .withAddressParam('spender', spender) - .withUintNParam('value', limit, 256, Permission.ParameterOperation.LESS_THAN_OR_EQUAL, true) - .build() - } -} - -/** - * Builds permissions for an ERC721 token. - */ -export class ERC721PermissionBuilder { - static buildTransfer(target: Address.Address, tokenId: bigint): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function transferFrom(address from, address to, uint256 tokenId)') - .withUintNParam('tokenId', tokenId) - .build() - } - - static buildApprove(target: Address.Address, spender: Address.Address, tokenId: bigint): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function approve(address spender, uint256 tokenId)') - .withAddressParam('spender', spender) - .withUintNParam('tokenId', tokenId) - .build() - } -} - -/** - * Builds permissions for an ERC1155 token. - */ -export class ERC1155PermissionBuilder { - static buildTransfer(target: Address.Address, tokenId: bigint, limit: bigint): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)') - .withUintNParam('id', tokenId) - .withUintNParam('amount', limit, 256, Permission.ParameterOperation.LESS_THAN_OR_EQUAL, true) - .build() - } - - static buildApproveAll(target: Address.Address, operator: Address.Address): Permission.Permission { - return PermissionBuilder.for(target) - .forFunction('function setApprovalForAll(address operator, bool approved)') - .withAddressParam('operator', operator) - .build() - } -} diff --git a/packages/wallet/core/src/utils/session/types.ts b/packages/wallet/core/src/utils/session/types.ts deleted file mode 100644 index 6bf1086d4d..0000000000 --- a/packages/wallet/core/src/utils/session/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Permission } from '@0xsequence/wallet-primitives' -import { Address } from 'ox' - -export type ExplicitSessionConfig = { - valueLimit: bigint - deadline: bigint - permissions: Permission.Permission[] - chainId: number -} - -// Complete session types - what the SDK returns after session creation -export type ImplicitSession = { - sessionAddress: Address.Address - type: 'implicit' -} - -export type ExplicitSession = { - sessionAddress: Address.Address - valueLimit: bigint - deadline: bigint - permissions: Permission.Permission[] - chainId: number - type: 'explicit' -} - -export type Session = { - type: 'explicit' | 'implicit' - sessionAddress: Address.Address - valueLimit?: bigint - deadline?: bigint - permissions?: Permission.Permission[] - chainId?: number -} diff --git a/packages/wallet/core/src/wallet.ts b/packages/wallet/core/src/wallet.ts deleted file mode 100644 index d89d5b2b98..0000000000 --- a/packages/wallet/core/src/wallet.ts +++ /dev/null @@ -1,609 +0,0 @@ -import { - Config, - Constants, - Context, - Erc6492, - Payload, - Address as SequenceAddress, - Signature as SequenceSignature, -} from '@0xsequence/wallet-primitives' -import { AbiFunction, Address, Bytes, Hex, Provider, TypedData } from 'ox' -import * as Envelope from './envelope.js' -import * as State from './state/index.js' -import { UserOperation } from 'ox/erc4337' - -export type WalletOptions = { - knownContexts: Context.KnownContext[] - stateProvider: State.Provider - guest: Address.Address - unsafe?: boolean -} - -export const DefaultWalletOptions: WalletOptions = { - knownContexts: Context.KnownContexts, - stateProvider: new State.Sequence.Provider(), - guest: Constants.DefaultGuestAddress, -} - -export type WalletStatus = { - address: Address.Address - isDeployed: boolean - implementation?: Address.Address - configuration: Config.Config - imageHash: Hex.Hex - /** Pending updates in reverse chronological order (newest first) */ - pendingUpdates: Array<{ imageHash: Hex.Hex; signature: SequenceSignature.RawSignature }> - chainId?: number - counterFactual: { - context: Context.KnownContext | Context.Context - imageHash: Hex.Hex - } -} - -export type WalletStatusWithOnchain = WalletStatus & { - onChainImageHash: Hex.Hex - stage: 'stage1' | 'stage2' - context: Context.KnownContext | Context.Context -} - -export class Wallet { - public readonly guest: Address.Address - public readonly stateProvider: State.Provider - public readonly knownContexts: Context.KnownContext[] - - constructor( - readonly address: Address.Address, - options?: Partial, - ) { - const combinedContexts = [...DefaultWalletOptions.knownContexts, ...(options?.knownContexts ?? [])] - const combinedOptions = { ...DefaultWalletOptions, ...options, knownContexts: combinedContexts } - this.guest = combinedOptions.guest - this.stateProvider = combinedOptions.stateProvider - this.knownContexts = combinedOptions.knownContexts - } - - /** - * Creates a new counter-factual wallet using the provided configuration. - * Saves the wallet in the state provider, so you can get its imageHash from its address, - * and its configuration from its imageHash. - * - * @param configuration - The wallet configuration to use. - * @param options - Optional wallet options. - * @returns A Promise that resolves to the new Wallet instance. - */ - static async fromConfiguration( - configuration: Config.Config, - options?: Partial & { context?: Context.Context }, - ): Promise { - const context = options?.context ?? Context.Dev2 - const merged = { ...DefaultWalletOptions, ...options } - - if (!merged.unsafe) { - Config.evaluateConfigurationSafety(configuration) - } - - await merged.stateProvider.saveWallet(configuration, context) - return new Wallet(SequenceAddress.from(configuration, context), merged) - } - - async isDeployed(provider: Provider.Provider): Promise { - return (await provider.request({ method: 'eth_getCode', params: [this.address, 'pending'] })) !== '0x' - } - - async buildDeployTransaction(): Promise<{ to: Address.Address; data: Hex.Hex }> { - const deployInformation = await this.stateProvider.getDeploy(this.address) - if (!deployInformation) { - throw new Error(`cannot find deploy information for ${this.address}`) - } - return Erc6492.deploy(deployInformation.imageHash, deployInformation.context) - } - - /** - * Prepares an envelope for updating the wallet's configuration. - * - * This function creates the necessary envelope that must be signed in order to update - * the configuration of a wallet. If the `unsafe` option is set to true, no sanity checks - * will be performed on the provided configuration. Otherwise, the configuration will be - * validated for safety (e.g., weights, thresholds). - * - * Note: This function does not directly update the wallet's configuration. The returned - * envelope must be signed and then submitted using the `submitUpdate` method to apply - * the configuration change. - * - * @param configuration - The new wallet configuration to be proposed. - * @param options - Options for preparing the update. If `unsafe` is true, skips safety checks. - * @returns A promise that resolves to an unsigned envelope for the configuration update. - */ - async prepareUpdate( - configuration: Config.Config, - options?: { unsafe?: boolean }, - ): Promise> { - if (!options?.unsafe) { - Config.evaluateConfigurationSafety(configuration) - } - - const imageHash = Config.hashConfiguration(configuration) - const blankEnvelope = ( - await Promise.all([this.prepareBlankEnvelope(0), this.stateProvider.saveConfiguration(configuration)]) - )[0] - - return { - ...blankEnvelope, - payload: Payload.fromConfigUpdate(Bytes.toHex(imageHash)), - } - } - - async submitUpdate( - envelope: Envelope.Signed, - options?: { noValidateSave?: boolean }, - ): Promise { - const [status, newConfig] = await Promise.all([ - this.getStatus(), - this.stateProvider.getConfiguration(envelope.payload.imageHash), - ]) - - if (!newConfig) { - throw new Error(`cannot find configuration details for ${envelope.payload.imageHash}`) - } - - // Verify the new configuration is valid - const updatedEnvelope = { ...envelope, configuration: status.configuration } - const { weight, threshold } = Envelope.weightOf(updatedEnvelope) - if (weight < threshold) { - throw new Error('insufficient weight in envelope') - } - - const signature = Envelope.encodeSignature(updatedEnvelope) - await this.stateProvider.saveUpdate(this.address, newConfig, signature) - - if (!options?.noValidateSave) { - const status = await this.getStatus() - if (Hex.from(Config.hashConfiguration(status.configuration)) !== envelope.payload.imageHash) { - throw new Error('configuration not saved') - } - } - } - - async getStatus( - provider?: T, - ): Promise { - let isDeployed = false - let implementation: Address.Address | undefined - let chainId: number | undefined - let imageHash: Hex.Hex - let updates: Array<{ imageHash: Hex.Hex; signature: SequenceSignature.RawSignature }> = [] - let onChainImageHash: Hex.Hex | undefined - let stage: 'stage1' | 'stage2' | undefined - - const deployInformation = await this.stateProvider.getDeploy(this.address) - if (!deployInformation) { - throw new Error(`cannot find deploy information for ${this.address}`) - } - - // Try to use a context from the known contexts, so we populate - // the capabilities of the context - const counterFactualContext = - this.knownContexts.find( - (kc) => - Address.isEqual(deployInformation.context.factory, kc.factory) && - Address.isEqual(deployInformation.context.stage1, kc.stage1), - ) ?? deployInformation.context - - let context: Context.KnownContext | Context.Context | undefined - - if (provider) { - // Get chain ID, deployment status, and implementation - const requests = await Promise.all([ - provider.request({ method: 'eth_chainId' }), - this.isDeployed(provider), - provider - .request({ - method: 'eth_call', - params: [{ to: this.address, data: AbiFunction.encodeData(Constants.GET_IMPLEMENTATION) }, 'latest'], - }) - .then((res) => { - const address = `0x${res.slice(-40)}` - Address.assert(address, { strict: false }) - return address - }) - .catch(() => undefined), - ]) - - chainId = Number(requests[0]) - isDeployed = requests[1] - implementation = requests[2] - - // Try to find the context from the known contexts (or use the counterfactual context) - context = implementation - ? [...this.knownContexts, counterFactualContext].find( - (kc) => Address.isEqual(implementation!, kc.stage1) || Address.isEqual(implementation!, kc.stage2), - ) - : counterFactualContext - - if (!context) { - throw new Error(`cannot find context for ${this.address}`) - } - - // Determine stage based on implementation address - stage = implementation && Address.isEqual(implementation, context.stage2) ? 'stage2' : 'stage1' - - // Get image hash and updates - if (isDeployed && stage === 'stage2') { - // For deployed stage2 wallets, get the image hash from the contract - onChainImageHash = await provider.request({ - method: 'eth_call', - params: [{ to: this.address, data: AbiFunction.encodeData(Constants.IMAGE_HASH) }, 'latest'], - }) - } else { - // For non-deployed or stage1 wallets, get the deploy hash - const deployInformation = await this.stateProvider.getDeploy(this.address) - if (!deployInformation) { - throw new Error(`cannot find deploy information for ${this.address}`) - } - onChainImageHash = deployInformation.imageHash - } - - // Get configuration updates - updates = await this.stateProvider.getConfigurationUpdates(this.address, onChainImageHash) - imageHash = updates[updates.length - 1]?.imageHash ?? onChainImageHash - } else { - // Without a provider, we can only get information from the state provider - updates = await this.stateProvider.getConfigurationUpdates(this.address, deployInformation.imageHash) - imageHash = updates[updates.length - 1]?.imageHash ?? deployInformation.imageHash - } - - // Get the current configuration - const configuration = await this.stateProvider.getConfiguration(imageHash) - if (!configuration) { - throw new Error(`cannot find configuration details for ${this.address}`) - } - - if (provider) { - return { - address: this.address, - isDeployed, - implementation, - stage, - configuration, - imageHash, - pendingUpdates: [...updates].reverse(), - chainId, - onChainImageHash: onChainImageHash!, - context, - } as T extends Provider.Provider ? WalletStatusWithOnchain : WalletStatus - } else { - return { - address: this.address, - isDeployed, - implementation, - configuration, - imageHash, - pendingUpdates: [...updates].reverse(), - chainId, - counterFactual: { - context: counterFactualContext, - imageHash: deployInformation.imageHash, - }, - } as T extends Provider.Provider ? WalletStatusWithOnchain : WalletStatus - } - } - - async getNonce(provider: Provider.Provider, space: bigint): Promise { - const result = await provider.request({ - method: 'eth_call', - params: [{ to: this.address, data: AbiFunction.encodeData(Constants.READ_NONCE, [space]) }, 'latest'], - }) - - if (result === '0x' || result.length === 0) { - return 0n - } - - return BigInt(result) - } - - async get4337Nonce(provider: Provider.Provider, entrypoint: Address.Address, space: bigint): Promise { - const result = await provider.request({ - method: 'eth_call', - params: [ - { to: entrypoint, data: AbiFunction.encodeData(Constants.READ_NONCE_4337, [this.address, space]) }, - 'latest', - ], - }) - - if (result === '0x' || result.length === 0) { - return 0n - } - - // Mask lower 64 bits - return BigInt(result) & 0xffffffffffffffffn - } - - async get4337Entrypoint(provider: Provider.Provider): Promise { - const status = await this.getStatus(provider) - return status.context.capabilities?.erc4337?.entrypoint - } - - async prepare4337Transaction( - provider: Provider.Provider, - calls: Payload.Call[], - options: { - space?: bigint - noConfigUpdate?: boolean - unsafe?: boolean - }, - ): Promise> { - const space = options.space ?? 0n - - // If safe mode is set, then we check that the transaction - // is not "dangerous", aka it does not have any delegate calls - // or calls to the wallet contract itself - if (!options?.unsafe) { - for (const call of calls) { - if (call.delegateCall) { - throw new Error('delegate calls are not allowed in safe mode') - } - if (Address.isEqual(call.to, this.address) && call.data !== '0x') { - throw new Error('calls to the wallet contract itself are not allowed in safe mode') - } - } - } - - const [chainId, status] = await Promise.all([provider.request({ method: 'eth_chainId' }), this.getStatus(provider)]) - - // If entrypoint is address(0) then 4337 is not enabled in this wallet - if (!status.context.capabilities?.erc4337?.entrypoint) { - throw new Error('4337 is not enabled in this wallet') - } - - const noncePromise = this.get4337Nonce(provider, status.context.capabilities.erc4337.entrypoint, space) - - // If the wallet is not deployed, then we need to include the initCode on - // the 4337 transaction - let factory: Address.Address | undefined - let factoryData: Hex.Hex | undefined - - if (!status.isDeployed) { - const deploy = await this.buildDeployTransaction() - factory = deploy.to - factoryData = deploy.data - } - - // If the latest configuration does not match the onchain configuration - // then we bundle the update into the transaction envelope - if (!options?.noConfigUpdate) { - const status = await this.getStatus(provider) - if (status.imageHash !== status.onChainImageHash) { - calls.push({ - to: this.address, - value: 0n, - data: AbiFunction.encodeData(Constants.UPDATE_IMAGE_HASH, [status.imageHash]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }) - } - } - - return { - payload: { - type: 'call_4337_07', - nonce: await noncePromise, - space, - calls, - entrypoint: status.context.capabilities?.erc4337?.entrypoint, - callGasLimit: 0n, - maxFeePerGas: 0n, - maxPriorityFeePerGas: 0n, - paymaster: undefined, - paymasterData: '0x', - preVerificationGas: 0n, - verificationGasLimit: 0n, - factory, - factoryData, - }, - ...(await this.prepareBlankEnvelope(Number(chainId), provider)), - } - } - - async build4337Transaction( - provider: Provider.Provider, - envelope: Envelope.Signed, - ): Promise<{ operation: UserOperation.RpcV07; entrypoint: Address.Address }> { - const status = await this.getStatus(provider) - - const updatedEnvelope = { ...envelope, configuration: status.configuration } - const { weight, threshold } = Envelope.weightOf(updatedEnvelope) - if (weight < threshold) { - throw new Error('insufficient weight in envelope') - } - - const signature = Envelope.encodeSignature(updatedEnvelope) - const operation = Payload.to4337UserOperation( - envelope.payload, - this.address, - Bytes.toHex( - SequenceSignature.encodeSignature({ - ...signature, - suffix: status.pendingUpdates.map(({ signature }) => signature), - }), - ), - ) - - return { - operation: UserOperation.toRpc(operation), - entrypoint: envelope.payload.entrypoint, - } - } - - async prepareTransaction( - provider: Provider.Provider, - calls: Payload.Call[], - options?: { - space?: bigint - noConfigUpdate?: boolean - unsafe?: boolean - }, - ): Promise> { - const space = options?.space ?? 0n - - // If safe mode is set, then we check that the transaction - // is not "dangerous", aka it does not have any delegate calls - // or calls to the wallet contract itself - if (!options?.unsafe) { - for (const call of calls) { - if (call.delegateCall) { - throw new Error('delegate calls are not allowed in safe mode') - } - if (Address.isEqual(call.to, this.address) && call.data !== '0x') { - throw new Error('calls to the wallet contract itself are not allowed in safe mode') - } - } - } - - const [chainId, nonce] = await Promise.all([ - provider.request({ method: 'eth_chainId' }), - this.getNonce(provider, space), - ]) - - // If the latest configuration does not match the onchain configuration - // then we bundle the update into the transaction envelope - if (!options?.noConfigUpdate) { - const status = await this.getStatus(provider) - if (status.imageHash !== status.onChainImageHash) { - calls.push({ - to: this.address, - value: 0n, - data: AbiFunction.encodeData(Constants.UPDATE_IMAGE_HASH, [status.imageHash]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }) - } - } - - return { - payload: { - type: 'call', - space, - nonce, - calls, - }, - ...(await this.prepareBlankEnvelope(Number(chainId), provider)), - } - } - - async buildTransaction(provider: Provider.Provider, envelope: Envelope.Signed) { - const status = await this.getStatus(provider) - - const updatedEnvelope = { ...envelope, configuration: status.configuration } - const { weight, threshold } = Envelope.weightOf(updatedEnvelope) - if (weight < threshold) { - throw new Error('insufficient weight in envelope') - } - - const signature = Envelope.encodeSignature(updatedEnvelope) - - if (status.isDeployed) { - return { - to: this.address, - data: AbiFunction.encodeData(Constants.EXECUTE, [ - Bytes.toHex(Payload.encode(envelope.payload)), - Bytes.toHex( - SequenceSignature.encodeSignature({ - ...signature, - suffix: status.pendingUpdates.map(({ signature }) => signature), - }), - ), - ]), - } - } else { - const deploy = await this.buildDeployTransaction() - - return { - to: this.guest, - data: Bytes.toHex( - Payload.encode({ - type: 'call', - space: 0n, - nonce: 0n, - calls: [ - { - to: deploy.to, - value: 0n, - data: deploy.data, - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - { - to: this.address, - value: 0n, - data: AbiFunction.encodeData(Constants.EXECUTE, [ - Bytes.toHex(Payload.encode(envelope.payload)), - Bytes.toHex( - SequenceSignature.encodeSignature({ - ...signature, - suffix: status.pendingUpdates.map(({ signature }) => signature), - }), - ), - ]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - }), - ), - } - } - } - - async prepareMessageSignature( - message: string | Hex.Hex | Payload.TypedDataToSign, - chainId: number, - ): Promise> { - let encodedMessage: Hex.Hex - if (typeof message !== 'string') { - encodedMessage = TypedData.encode(message) - } else { - const hexMessage = Hex.validate(message) ? message : Hex.fromString(message) - const messageSize = Hex.size(hexMessage) - encodedMessage = Hex.concat(Hex.fromString(`${`\x19Ethereum Signed Message:\n${messageSize}`}`), hexMessage) - } - return { - ...(await this.prepareBlankEnvelope(chainId)), - payload: Payload.fromMessage(encodedMessage), - } - } - - async buildMessageSignature( - envelope: Envelope.Signed, - provider?: Provider.Provider, - ): Promise { - const status = await this.getStatus(provider) - const signature = Envelope.encodeSignature(envelope) - if (!status.isDeployed) { - const deployTransaction = await this.buildDeployTransaction() - signature.erc6492 = { to: deployTransaction.to, data: Bytes.fromHex(deployTransaction.data) } - } - const encoded = SequenceSignature.encodeSignature({ - ...signature, - suffix: status.pendingUpdates.map(({ signature }) => signature), - }) - return encoded - } - - private async prepareBlankEnvelope(chainId: number, provider?: Provider.Provider) { - const status = await this.getStatus(provider) - - return { - wallet: this.address, - chainId: chainId, - configuration: status.configuration, - } - } -} diff --git a/packages/wallet/core/test/constants.ts b/packages/wallet/core/test/constants.ts deleted file mode 100644 index 42b51d49b7..0000000000 --- a/packages/wallet/core/test/constants.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { config as dotenvConfig } from 'dotenv' -import { Abi, AbiEvent, Address } from 'ox' - -// eslint-disable-next-line turbo/no-undeclared-env-vars -const envFile = process.env.CI ? '.env.test' : '.env.test.local' -dotenvConfig({ path: envFile }) - -// Contracts are deployed on Arbitrum - -// Requires https://example.com redirectUrl -export const EMITTER_ADDRESS1: Address.Address = '0xad90eB52BC180Bd9f66f50981E196f3E996278D3' -// Requires https://another-example.com redirectUrl -export const EMITTER_ADDRESS2: Address.Address = '0x4cb8d282365C7bee8C0d3Bf1B3ca5828e0Db553F' -export const EMITTER_FUNCTIONS = Abi.from(['function explicitEmit()', 'function implicitEmit()']) -export const EMITTER_EVENT_TOPICS = [ - AbiEvent.encode(AbiEvent.from('event Explicit(address sender)')).topics[0], - AbiEvent.encode(AbiEvent.from('event Implicit(address sender)')).topics[0], -] -export const USDC_ADDRESS: Address.Address = '0xaf88d065e77c8cc2239327c5edb3a432268e5831' - -// Environment variables -// eslint-disable-next-line turbo/no-undeclared-env-vars -export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' diff --git a/packages/wallet/core/test/envelope.test.ts b/packages/wallet/core/test/envelope.test.ts deleted file mode 100644 index c92cf900eb..0000000000 --- a/packages/wallet/core/test/envelope.test.ts +++ /dev/null @@ -1,616 +0,0 @@ -import { Address, Hex } from 'ox' -import { describe, expect, it } from 'vitest' -import { Config, Network, Payload, Signature } from '@0xsequence/wallet-primitives' - -import * as Envelope from '../src/envelope.js' - -// Test addresses and data -const TEST_ADDRESS_1 = Address.from('0x1234567890123456789012345678901234567890') -const TEST_ADDRESS_3 = Address.from('0x9876543210987654321098765432109876543210') -const TEST_WALLET = Address.from('0xfedcbafedcbafedcbafedcbafedcbafedcbafe00') -const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') -const TEST_IMAGE_HASH_2 = Hex.from('0x1111111111111111111111111111111111111111111111111111111111111111') - -// Mock payload -const mockPayload: Payload.Calls = { - type: 'call', - nonce: 1n, - space: 0n, - calls: [ - { - to: TEST_ADDRESS_1, - value: 1000000000000000000n, - data: '0x12345678', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], -} - -// Mock configuration with single signer -const mockConfig: Config.Config = { - threshold: 2n, - checkpoint: 0n, - topology: { type: 'signer', address: TEST_ADDRESS_1, weight: 2n }, -} - -// Mock signatures -const mockHashSignature: Signature.SignatureOfSignerLeaf = { - type: 'hash', - r: 123n, - s: 456n, - yParity: 0, -} - -const mockEthSignSignature: Signature.SignatureOfSignerLeaf = { - type: 'eth_sign', - r: 789n, - s: 101112n, - yParity: 1, -} - -const mockErc1271Signature: Signature.SignatureOfSignerLeaf = { - type: 'erc1271', - address: TEST_ADDRESS_1, - data: '0xabcdef123456', -} - -const mockSapientSignatureData: Signature.SignatureOfSapientSignerLeaf = { - type: 'sapient', - address: TEST_ADDRESS_3, - data: '0x987654321', -} - -// Create test envelope -const testEnvelope: Envelope.Envelope = { - wallet: TEST_WALLET, - chainId: Network.ChainId.MAINNET, - configuration: mockConfig, - payload: mockPayload, -} - -describe('Envelope', () => { - describe('type guards', () => { - describe('isSignature', () => { - it('should return true for valid signature objects', () => { - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - - expect(Envelope.isSignature(signature)).toBe(true) - }) - - it('should return false for sapient signatures', () => { - const sapientSig: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - - expect(Envelope.isSignature(sapientSig)).toBe(false) - }) - - it('should return false for invalid objects', () => { - // Skip null test due to source code limitation with 'in' operator - expect(Envelope.isSignature(undefined)).toBe(false) - expect(Envelope.isSignature({})).toBe(false) - expect(Envelope.isSignature({ address: TEST_ADDRESS_1 })).toBe(false) - expect(Envelope.isSignature({ signature: mockHashSignature })).toBe(false) - expect(Envelope.isSignature('string')).toBe(false) - expect(Envelope.isSignature(123)).toBe(false) - }) - }) - - describe('isSapientSignature', () => { - it('should return true for valid sapient signature objects', () => { - const sapientSig: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - - expect(Envelope.isSapientSignature(sapientSig)).toBe(true) - }) - - it('should return false for regular signatures', () => { - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - - expect(Envelope.isSapientSignature(signature)).toBe(false) - }) - - it('should return false for invalid objects', () => { - // Skip null test due to source code limitation with 'in' operator - expect(Envelope.isSapientSignature(undefined)).toBe(false) - expect(Envelope.isSapientSignature({})).toBe(false) - expect(Envelope.isSapientSignature({ imageHash: TEST_IMAGE_HASH })).toBe(false) - expect(Envelope.isSapientSignature({ signature: mockSapientSignatureData })).toBe(false) - }) - }) - - describe('isSigned', () => { - it('should return true for signed envelopes', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - expect(Envelope.isSigned(signedEnvelope)).toBe(true) - }) - - it('should return false for unsigned envelopes', () => { - expect(Envelope.isSigned(testEnvelope)).toBe(false) - }) - - it('should return false for invalid objects', () => { - // Skip null test due to source code limitation with 'in' operator - expect(Envelope.isSigned(undefined as any)).toBe(false) - expect(Envelope.isSigned({} as any)).toBe(false) - }) - }) - }) - - describe('toSigned', () => { - it('should convert envelope to signed envelope with empty signatures', () => { - const signed = Envelope.toSigned(testEnvelope) - - expect(signed).toEqual({ - ...testEnvelope, - signatures: [], - }) - expect(Envelope.isSigned(signed)).toBe(true) - }) - - it('should convert envelope to signed envelope with provided signatures', () => { - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signed = Envelope.toSigned(testEnvelope, signatures) - - expect(signed).toEqual({ - ...testEnvelope, - signatures, - }) - }) - - it('should handle mixed signature types', () => { - const signatures: (Envelope.Signature | Envelope.SapientSignature)[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - }, - ] - - const signed = Envelope.toSigned(testEnvelope, signatures) - - expect(signed.signatures).toEqual(signatures) - }) - }) - - describe('signatureForLeaf', () => { - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signedEnvelope = Envelope.toSigned(testEnvelope, signatures) - - it('should find signature for regular signer leaf', () => { - const leaf: Config.SignerLeaf = { type: 'signer', address: TEST_ADDRESS_1, weight: 2n } - const foundSig = Envelope.signatureForLeaf(signedEnvelope, leaf) - - expect(foundSig).toEqual(signatures[0]) - }) - - it('should find signature for sapient signer leaf', () => { - const sapientSignatures: Envelope.SapientSignature[] = [ - { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - }, - ] - const sapientEnvelope = Envelope.toSigned(testEnvelope, sapientSignatures) - - const leaf: Config.SapientSignerLeaf = { - type: 'sapient-signer', - address: TEST_ADDRESS_3, - weight: 2n, - imageHash: TEST_IMAGE_HASH, - } - const foundSig = Envelope.signatureForLeaf(sapientEnvelope, leaf) - - expect(foundSig).toEqual(sapientSignatures[0]) - }) - - it('should return undefined for non-existent signer', () => { - const leaf: Config.SignerLeaf = { - type: 'signer', - address: Address.from('0x0000000000000000000000000000000000000000'), - weight: 1n, - } - const foundSig = Envelope.signatureForLeaf(signedEnvelope, leaf) - - expect(foundSig).toBeUndefined() - }) - - it('should return undefined for mismatched imageHash', () => { - const leaf: Config.SapientSignerLeaf = { - type: 'sapient-signer', - address: TEST_ADDRESS_3, - weight: 2n, - imageHash: TEST_IMAGE_HASH_2, - } - const foundSig = Envelope.signatureForLeaf(signedEnvelope, leaf) - - expect(foundSig).toBeUndefined() - }) - - it('should return undefined for unsupported leaf types', () => { - const leaf = { type: 'node', data: '0x123' } as any - const foundSig = Envelope.signatureForLeaf(signedEnvelope, leaf) - - expect(foundSig).toBeUndefined() - }) - }) - - describe('weightOf', () => { - it('should calculate weight correctly with partial signatures', () => { - // Empty signatures - no weight - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const { weight, threshold } = Envelope.weightOf(signedEnvelope) - - expect(weight).toBe(0n) // No signatures - expect(threshold).toBe(2n) // Threshold from config - }) - - it('should calculate weight correctly with all signatures', () => { - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signedEnvelope = Envelope.toSigned(testEnvelope, signatures) - const { weight, threshold } = Envelope.weightOf(signedEnvelope) - - expect(weight).toBe(2n) // Single signer with weight 2 - expect(threshold).toBe(2n) - }) - - it('should handle envelope with no signatures', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const { weight, threshold } = Envelope.weightOf(signedEnvelope) - - expect(weight).toBe(0n) - expect(threshold).toBe(2n) - }) - }) - - describe('reachedThreshold', () => { - it('should return false when weight is below threshold', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) // No signatures - expect(Envelope.reachedThreshold(signedEnvelope)).toBe(false) - }) - - it('should return true when weight meets threshold', () => { - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signedEnvelope = Envelope.toSigned(testEnvelope, signatures) - expect(Envelope.reachedThreshold(signedEnvelope)).toBe(true) - }) - - it('should return true when weight exceeds threshold', () => { - // Create config with lower threshold - const lowThresholdConfig: Config.Config = { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: TEST_ADDRESS_1, weight: 2n }, - } - - const lowThresholdEnvelope = { - ...testEnvelope, - configuration: lowThresholdConfig, - } - - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signedEnvelope = Envelope.toSigned(lowThresholdEnvelope, signatures) - expect(Envelope.reachedThreshold(signedEnvelope)).toBe(true) // 2 > 1 - }) - }) - - describe('addSignature', () => { - it('should add regular signature to empty envelope', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - - Envelope.addSignature(signedEnvelope, signature) - - expect(signedEnvelope.signatures).toHaveLength(1) - expect(signedEnvelope.signatures[0]).toEqual(signature) - }) - - it('should add sapient signature to envelope', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const signature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - - Envelope.addSignature(signedEnvelope, signature) - - expect(signedEnvelope.signatures).toHaveLength(1) - expect(signedEnvelope.signatures[0]).toEqual(signature) - }) - - it('should throw error when adding duplicate signature without replace', () => { - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [signature]) - - const duplicateSignature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockEthSignSignature, - } - - expect(() => { - Envelope.addSignature(signedEnvelope, duplicateSignature) - }).toThrow('Signature already defined for signer') - }) - - it('should replace signature when replace option is true', () => { - const originalSignature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [originalSignature]) - - const newSignature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockEthSignSignature, - } - - Envelope.addSignature(signedEnvelope, newSignature, { replace: true }) - - expect(signedEnvelope.signatures).toHaveLength(1) - expect(signedEnvelope.signatures[0]).toEqual(newSignature) - }) - - it('should do nothing when adding identical signature', () => { - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [signature]) - - const identicalSignature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: { ...mockHashSignature }, - } - - Envelope.addSignature(signedEnvelope, identicalSignature) - - expect(signedEnvelope.signatures).toHaveLength(1) - expect(signedEnvelope.signatures[0]).toEqual(signature) - }) - - it('should handle identical ERC1271 signatures', () => { - const signature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockErc1271Signature, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [signature]) - - const identicalSignature: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: { ...mockErc1271Signature }, - } - - Envelope.addSignature(signedEnvelope, identicalSignature) - - expect(signedEnvelope.signatures).toHaveLength(1) - }) - - it('should handle identical sapient signatures', () => { - const signature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [signature]) - - const identicalSignature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: { ...mockSapientSignatureData }, - } - - Envelope.addSignature(signedEnvelope, identicalSignature) - - expect(signedEnvelope.signatures).toHaveLength(1) - }) - - it('should throw error for unsupported signature type', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const invalidSignature = { invalid: 'signature' } as any - - expect(() => { - Envelope.addSignature(signedEnvelope, invalidSignature) - }).toThrow('Unsupported signature type') - }) - - it('should handle sapient signature replacement', () => { - const originalSignature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [originalSignature]) - - const newSignature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: { - type: 'sapient', - address: TEST_ADDRESS_3, - data: '0xnewdata', - }, - } - - Envelope.addSignature(signedEnvelope, newSignature, { replace: true }) - - expect(signedEnvelope.signatures).toHaveLength(1) - expect(signedEnvelope.signatures[0]).toEqual(newSignature) - }) - - it('should throw error for duplicate sapient signature without replace', () => { - const signature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - const signedEnvelope = Envelope.toSigned(testEnvelope, [signature]) - - const duplicateSignature: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: { - type: 'sapient', - address: TEST_ADDRESS_3, - data: '0xdifferent', - }, - } - - expect(() => { - Envelope.addSignature(signedEnvelope, duplicateSignature) - }).toThrow('Signature already defined for signer') - }) - }) - - describe('encodeSignature', () => { - it('should encode signature with filled topology', () => { - const signatures: Envelope.Signature[] = [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ] - - const signedEnvelope = Envelope.toSigned(testEnvelope, signatures) - const encoded = Envelope.encodeSignature(signedEnvelope) - - expect(encoded.noChainId).toBe(false) // chainId is 1n, not 0n - expect(encoded.configuration.threshold).toBe(2n) - expect(encoded.configuration.checkpoint).toBe(0n) - expect(encoded.configuration.topology).toBeDefined() - expect(typeof encoded.configuration.topology).toBe('object') - }) - - it('should set noChainId to true when chainId is 0', () => { - const zeroChainEnvelope = { - ...testEnvelope, - chainId: 0, - } - - const signedEnvelope = Envelope.toSigned(zeroChainEnvelope, []) - const encoded = Envelope.encodeSignature(signedEnvelope) - - expect(encoded.noChainId).toBe(true) - }) - - it('should handle envelope with no signatures', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - const encoded = Envelope.encodeSignature(signedEnvelope) - - expect(encoded.configuration).toBeDefined() - expect(encoded.noChainId).toBe(false) - }) - }) - - describe('edge cases and complex scenarios', () => { - it('should handle multiple signatures for different signers', () => { - const signedEnvelope = Envelope.toSigned(testEnvelope, []) - - const sig1: Envelope.Signature = { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - } - - const sig2: Envelope.SapientSignature = { - imageHash: TEST_IMAGE_HASH, - signature: mockSapientSignatureData, - } - - Envelope.addSignature(signedEnvelope, sig1) - Envelope.addSignature(signedEnvelope, sig2) - - expect(signedEnvelope.signatures).toHaveLength(2) - }) - - it('should handle single signer configuration', () => { - const singleSignerConfig: Config.Config = { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: TEST_ADDRESS_1, weight: 1n }, - } - - const singleSignerEnvelope = { - ...testEnvelope, - configuration: singleSignerConfig, - } - - const signedEnvelope = Envelope.toSigned(singleSignerEnvelope, [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ]) - - expect(Envelope.reachedThreshold(signedEnvelope)).toBe(true) - expect(Envelope.weightOf(signedEnvelope).weight).toBe(1n) - }) - - it('should handle nested configuration topology', () => { - const nestedConfig: Config.Config = { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: TEST_ADDRESS_1, weight: 2n }, - } - - const nestedEnvelope = { - ...testEnvelope, - configuration: nestedConfig, - } - - const signedEnvelope = Envelope.toSigned(nestedEnvelope, [ - { - address: TEST_ADDRESS_1, - signature: mockHashSignature, - }, - ]) - - const { weight, threshold } = Envelope.weightOf(signedEnvelope) - expect(threshold).toBe(1n) - expect(weight).toBe(2n) // Signer weight - }) - }) -}) diff --git a/packages/wallet/core/test/relayer/bundler.test.ts b/packages/wallet/core/test/relayer/bundler.test.ts deleted file mode 100644 index bc565e1cc3..0000000000 --- a/packages/wallet/core/test/relayer/bundler.test.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest' -import { Address, Hex } from 'ox' -import { UserOperation } from 'ox/erc4337' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { Bundler, isBundler } from '../../src/bundler/index.js' -import { Relayer } from '@0xsequence/relayer' - -// Test addresses and data -const TEST_WALLET_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_ENTRYPOINT_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const TEST_CHAIN_ID = Network.ChainId.MAINNET -const TEST_OP_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') - -describe('Bundler', () => { - describe('isBundler type guard', () => { - it('should return true for valid bundler objects', () => { - const mockBundler: Bundler = { - kind: 'bundler', - id: 'test-bundler', - estimateLimits: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - isAvailable: vi.fn(), - } - - expect(isBundler(mockBundler)).toBe(true) - }) - - it('should return false for objects missing required methods', () => { - // Missing estimateLimits - const missing1 = { - kind: 'bundler' as const, - id: 'test-bundler', - relay: vi.fn(), - status: vi.fn(), - isAvailable: vi.fn(), - } - expect(isBundler(missing1)).toBe(false) - - // Missing relay - const missing2 = { - kind: 'bundler' as const, - id: 'test-bundler', - estimateLimits: vi.fn(), - status: vi.fn(), - isAvailable: vi.fn(), - } - expect(isBundler(missing2)).toBe(false) - - // Missing isAvailable - const missing3 = { - kind: 'bundler' as const, - id: 'test-bundler', - estimateLimits: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - } - expect(isBundler(missing3)).toBe(false) - }) - - it('should return false for non-objects', () => { - // These will throw due to the 'in' operator, so we need to test the actual behavior - expect(() => isBundler(null)).toThrow() - expect(() => isBundler(undefined)).toThrow() - expect(() => isBundler('string')).toThrow() - expect(() => isBundler(123)).toThrow() - expect(() => isBundler(true)).toThrow() - // Arrays and objects should not throw, but should return false - expect(isBundler([])).toBe(false) - }) - - it('should return false for objects with properties but wrong types', () => { - const wrongTypes = { - kind: 'bundler' as const, - id: 'test-bundler', - estimateLimits: 'not a function', - relay: vi.fn(), - status: vi.fn(), - isAvailable: vi.fn(), - } - // The current implementation only checks if properties exist, not their types - // So this will actually return true since all required properties exist - expect(isBundler(wrongTypes)).toBe(true) - }) - - it('should return false for relayer objects', () => { - const mockRelayer = { - kind: 'relayer' as const, - type: 'test', - id: 'test-relayer', - isAvailable: vi.fn(), - feeOptions: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - checkPrecondition: vi.fn(), - } - expect(isBundler(mockRelayer)).toBe(false) - }) - }) - - describe('Bundler interface contract', () => { - let mockBundler: Bundler - let mockPayload: Payload.Calls4337_07 - let mockUserOperation: UserOperation.RpcV07 - - beforeEach(() => { - mockBundler = { - kind: 'bundler', - id: 'mock-bundler', - estimateLimits: vi.fn(), - relay: vi.fn(), - status: vi.fn(), - isAvailable: vi.fn(), - } - - mockPayload = { - type: 'call_4337_07', - calls: [ - { - to: TEST_WALLET_ADDRESS, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - entrypoint: TEST_ENTRYPOINT_ADDRESS, - space: 0n, - nonce: 0n, - callGasLimit: 50000n, - verificationGasLimit: 50000n, - preVerificationGas: 21000n, - maxFeePerGas: 1000000000n, - maxPriorityFeePerGas: 1000000000n, - paymaster: undefined, - paymasterData: undefined, - paymasterVerificationGasLimit: 50000n, - paymasterPostOpGasLimit: 50000n, - factory: undefined, - factoryData: undefined, - } - - mockUserOperation = { - sender: TEST_WALLET_ADDRESS, - nonce: '0x0', - callData: '0x', - callGasLimit: '0xc350', - verificationGasLimit: '0xc350', - preVerificationGas: '0x5208', - maxFeePerGas: '0x3b9aca00', - maxPriorityFeePerGas: '0x3b9aca00', - paymasterData: '0x', - signature: '0x', - } - }) - - it('should have required properties', () => { - expect(mockBundler.kind).toBe('bundler') - expect(mockBundler.id).toBe('mock-bundler') - }) - - it('should have required methods with correct signatures', () => { - expect(typeof mockBundler.estimateLimits).toBe('function') - expect(typeof mockBundler.relay).toBe('function') - expect(typeof mockBundler.status).toBe('function') - expect(typeof mockBundler.isAvailable).toBe('function') - }) - - it('should support typical bundler workflow methods', async () => { - // Mock the methods to return expected types - vi.mocked(mockBundler.isAvailable).mockResolvedValue(true) - vi.mocked(mockBundler.estimateLimits).mockResolvedValue([ - { - speed: 'standard', - payload: mockPayload, - }, - ]) - vi.mocked(mockBundler.relay).mockResolvedValue({ - opHash: TEST_OP_HASH, - }) - vi.mocked(mockBundler.status).mockResolvedValue({ - status: 'confirmed', - transactionHash: TEST_OP_HASH, - }) - - // Test method calls - const isAvailable = await mockBundler.isAvailable(TEST_ENTRYPOINT_ADDRESS, TEST_CHAIN_ID) - expect(isAvailable).toBe(true) - - const estimateResult = await mockBundler.estimateLimits(TEST_WALLET_ADDRESS, mockPayload) - expect(estimateResult).toHaveLength(1) - expect(estimateResult[0].speed).toBe('standard') - expect(estimateResult[0].payload).toBe(mockPayload) - - const relayResult = await mockBundler.relay(TEST_ENTRYPOINT_ADDRESS, mockUserOperation) - expect(relayResult.opHash).toBe(TEST_OP_HASH) - - const statusResult = await mockBundler.status(TEST_OP_HASH, TEST_CHAIN_ID) - expect(statusResult.status).toBe('confirmed') - }) - - it('should handle estimateLimits with different speed options', async () => { - const estimateResults = [ - { speed: 'slow' as const, payload: mockPayload }, - { speed: 'standard' as const, payload: mockPayload }, - { speed: 'fast' as const, payload: mockPayload }, - { payload: mockPayload }, // No speed specified - ] - - vi.mocked(mockBundler.estimateLimits).mockResolvedValue(estimateResults) - - const result = await mockBundler.estimateLimits(TEST_WALLET_ADDRESS, mockPayload) - expect(result).toHaveLength(4) - expect(result[0].speed).toBe('slow') - expect(result[1].speed).toBe('standard') - expect(result[2].speed).toBe('fast') - expect(result[3].speed).toBeUndefined() - }) - - it('should handle various operation statuses', async () => { - const statuses: Relayer.OperationStatus[] = [ - { status: 'unknown' }, - { status: 'pending' }, - { status: 'confirmed', transactionHash: TEST_OP_HASH }, - { status: 'failed', reason: 'UserOp reverted' }, - ] - - for (const expectedStatus of statuses) { - vi.mocked(mockBundler.status).mockResolvedValue(expectedStatus) - const result = await mockBundler.status(TEST_OP_HASH, TEST_CHAIN_ID) - expect(result.status).toBe(expectedStatus.status) - } - }) - }) - - describe('Type compatibility', () => { - it('should work with Address and Hex types from ox', () => { - // Test that the interfaces work correctly with ox types - const address = Address.from('0x1234567890123456789012345678901234567890') - const hex = Hex.from('0xabcdef') - const chainId = 1n - - expect(Address.validate(address)).toBe(true) - expect(Hex.validate(hex)).toBe(true) - expect(typeof chainId).toBe('bigint') - }) - - it('should work with ERC4337 UserOperation types', () => { - // Test basic compatibility with UserOperation types - const mockUserOp: UserOperation.RpcV07 = { - sender: TEST_WALLET_ADDRESS, - nonce: '0x0', - callData: '0x', - callGasLimit: '0xc350', - verificationGasLimit: '0xc350', - preVerificationGas: '0x5208', - maxFeePerGas: '0x3b9aca00', - maxPriorityFeePerGas: '0x3b9aca00', - paymasterData: '0x', - signature: '0x', - } - - expect(mockUserOp.sender).toBe(TEST_WALLET_ADDRESS) - expect(mockUserOp.nonce).toBe('0x0') - expect(mockUserOp.signature).toBe('0x') - }) - - it('should work with wallet-primitives Payload types', () => { - // Test basic compatibility with Payload types - const mockPayload: Payload.Calls4337_07 = { - type: 'call_4337_07', - calls: [ - { - to: TEST_WALLET_ADDRESS, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - entrypoint: TEST_ENTRYPOINT_ADDRESS, - space: 0n, - nonce: 0n, - callGasLimit: 50000n, - verificationGasLimit: 50000n, - preVerificationGas: 21000n, - maxFeePerGas: 1000000000n, - maxPriorityFeePerGas: 1000000000n, - paymaster: undefined, - paymasterData: undefined, - paymasterVerificationGasLimit: 50000n, - paymasterPostOpGasLimit: 50000n, - factory: undefined, - factoryData: undefined, - } - - expect(mockPayload.type).toBe('call_4337_07') - expect(mockPayload.calls).toHaveLength(1) - expect(mockPayload.entrypoint).toBe(TEST_ENTRYPOINT_ADDRESS) - }) - }) -}) diff --git a/packages/wallet/core/test/session-manager.test.ts b/packages/wallet/core/test/session-manager.test.ts deleted file mode 100644 index 7ba342ebc1..0000000000 --- a/packages/wallet/core/test/session-manager.test.ts +++ /dev/null @@ -1,1703 +0,0 @@ -import { Constants, Extensions } from '@0xsequence/wallet-primitives' -import { AbiEvent, AbiFunction, Address, Bytes, Hex, Provider, RpcTransport, Secp256k1 } from 'ox' -import { describe, expect, it } from 'vitest' -import { Attestation, GenericTree, Payload, Permission, SessionConfig } from '../../primitives/src/index.js' -import { Envelope, Signers, State, Utils, Wallet } from '../src/index.js' -import { ExplicitSessionConfig } from '../src/utils/session/types.js' -import { - EMITTER_ADDRESS1, - EMITTER_ADDRESS2, - EMITTER_EVENT_TOPICS, - EMITTER_FUNCTIONS, - LOCAL_RPC_URL, - USDC_ADDRESS, -} from './constants' -const { PermissionBuilder, ERC20PermissionBuilder } = Utils - -function randomAddress(): Address.Address { - return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) -} - -const ALL_EXTENSIONS = [ - { - name: 'Dev1', - ...Extensions.Dev1, - }, - { - name: 'Dev2', - ...Extensions.Dev2, - }, - { - name: 'Rc3', - ...Extensions.Rc3, - }, - { - name: 'Rc4', - ...Extensions.Rc4, - }, - { - name: 'Rc5', - ...Extensions.Rc5, - }, -] - -// Handle the increment call being first or last depending on the session manager version -const includeIncrement = (calls: Payload.Call[], increment: Payload.Call, sessionManagerAddress: Address.Address) => { - if ( - Address.isEqual(sessionManagerAddress, Extensions.Dev1.sessions) || - Address.isEqual(sessionManagerAddress, Extensions.Dev2.sessions) - ) { - // Increment is last - return [...calls, increment] - } - // Increment is first - return [increment, ...calls] -} - -for (const extension of ALL_EXTENSIONS) { - describe(`SessionManager (${extension.name})`, () => { - const timeout = 30000 - - const createImplicitSigner = async (redirectUrl: string, signingKey: Hex.Hex) => { - const implicitPrivateKey = Secp256k1.randomPrivateKey() - const implicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: implicitPrivateKey })) - const attestation: Attestation.Attestation = { - approvedSigner: implicitAddress, - identityType: new Uint8Array(4), - issuerHash: new Uint8Array(32), - audienceHash: new Uint8Array(32), - applicationData: new Uint8Array(), - authData: { - redirectUrl, - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - const identitySignature = Secp256k1.sign({ - payload: Attestation.hash(attestation), - privateKey: signingKey, - }) - return new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, implicitAddress) - } - - it( - 'should load from state', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - let topology = SessionConfig.emptySessionsTopology(identityAddress) - // Add random signer to the topology - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ - { - target: randomAddress(), - rules: [ - { - cumulative: true, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x'), 32), - offset: 0n, - mask: Bytes.padLeft(Bytes.fromHex('0x'), 32), - }, - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x01'), 32), - offset: 2n, - mask: Bytes.padLeft(Bytes.fromHex('0x03'), 32), - }, - ], - }, - ], - } - const randomSigner = randomAddress() - topology = SessionConfig.addExplicitSession(topology, { - ...sessionPermission, - signer: randomSigner, - }) - // Add random blacklist to the topology - const randomBlacklistAddress = randomAddress() - topology = SessionConfig.addToImplicitBlacklist(topology, randomBlacklistAddress) - - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - - // Save the topology to storage - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - - // Create a wallet with the session manager topology as a leaf - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - }, - { - stateProvider, - }, - ) - - // Create the session manager using the storage - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }) - - // Check config is correct - const actualTopology = await sessionManager.topology - const actualImageHash = await sessionManager.imageHash - expect(actualImageHash).toBe(imageHash) - expect(SessionConfig.isCompleteSessionsTopology(actualTopology)).toBe(true) - expect(SessionConfig.getIdentitySigners(actualTopology)).toStrictEqual([identityAddress]) - expect(SessionConfig.getImplicitBlacklist(actualTopology)).toStrictEqual([randomBlacklistAddress]) - const actualPermissions = SessionConfig.getSessionPermissions(actualTopology, randomSigner) - expect(actualPermissions).toStrictEqual({ - ...sessionPermission, - type: 'session-permissions', - signer: randomSigner, - }) - }, - timeout, - ) - - it( - 'should create and sign with an implicit session', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create implicit signer - const implicitPrivateKey = Secp256k1.randomPrivateKey() - const implicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: implicitPrivateKey })) - // -- This is sent to the wallet (somehow)-- - const attestation: Attestation.Attestation = { - approvedSigner: implicitAddress, - identityType: new Uint8Array(4), - issuerHash: new Uint8Array(32), - audienceHash: new Uint8Array(32), - applicationData: new Uint8Array(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - const identitySignature = Secp256k1.sign({ - payload: Attestation.hash(attestation), - privateKey: identityPrivateKey, - }) - const topology = SessionConfig.emptySessionsTopology(identityAddress) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - // -- Back in dapp -- - const implicitSigner = new Signers.Session.Implicit( - implicitPrivateKey, - attestation, - identitySignature, - implicitAddress, - ) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }).withImplicitSigner(implicitSigner) - - // Create a test transaction - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - const payload: Payload.Parented = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [call], - parentWallets: [wallet.address], - } - - // Sign the transaction - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - const signature = await sessionManager.signSapient(wallet.address, chainId, payload, imageHash) - - expect(signature.type).toBe('sapient') - expect(signature.address).toBe(sessionManager.address) - expect(signature.data).toBeDefined() - - // Check if the signature is valid - const isValid = await sessionManager.isValidSapientSignature(wallet.address, chainId, payload, signature) - expect(isValid).toBe(true) - }, - timeout, - ) - - it( - 'should create and sign with a multiple implicit sessions', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const implicitSigner1 = await createImplicitSigner('https://example.com', identityPrivateKey) - const implicitSigner2 = await createImplicitSigner('https://another-example.com', identityPrivateKey) - const topology = SessionConfig.emptySessionsTopology(identityAddress) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }) - .withImplicitSigner(implicitSigner1) - .withImplicitSigner(implicitSigner2) - - // Create a test transaction - const payload: Payload.Parented = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [ - { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - { - to: EMITTER_ADDRESS2, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - parentWallets: [wallet.address], - } - - // Sign the transaction - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - const signature = await sessionManager.signSapient(wallet.address, chainId, payload, imageHash) - - expect(signature.type).toBe('sapient') - expect(signature.address).toBe(sessionManager.address) - expect(signature.data).toBeDefined() - - // Check if the signature is valid - const isValid = await sessionManager.isValidSapientSignature(wallet.address, chainId, payload, signature) - expect(isValid).toBe(true) - }, - timeout, - ) - - it( - 'should fail to sign with a multiple implicit sessions with different identity signers', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const identityPrivateKey2 = Secp256k1.randomPrivateKey() - const identityAddress2 = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey2 })) - - const implicitSigner1 = await createImplicitSigner('https://example.com', identityPrivateKey) - const implicitSigner2 = await createImplicitSigner('https://another-example.com', identityPrivateKey2) - let topology = SessionConfig.emptySessionsTopology(identityAddress) - topology = SessionConfig.addIdentitySigner(topology, identityAddress2) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }) - .withImplicitSigner(implicitSigner1) - .withImplicitSigner(implicitSigner2) - - // Create a test transaction - const payload: Payload.Parented = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [ - { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - { - to: EMITTER_ADDRESS2, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - parentWallets: [wallet.address], - } - - // Sign the transaction - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - await expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( - 'Multiple implicit signers with different identity signers', - ) - }, - timeout, - ) - - const shouldCreateAndSignWithExplicitSession = async (useChainId: boolean) => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitPermissions: ExplicitSessionConfig = { - chainId: useChainId ? chainId : 0, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, explicitPermissions) - // Create the topology and wallet - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...explicitPermissions, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }).withExplicitSigner(explicitSigner) - - // Create a test transaction within permissions - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - const payload: Payload.Calls = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [call], - } - - // Sign the transaction - const signature = await sessionManager.signSapient(wallet.address, chainId, payload, imageHash) - - expect(signature.type).toBe('sapient') - expect(signature.address).toBe(sessionManager.address) - expect(signature.data).toBeDefined() - - // Check if the signature is valid - const isValid = await sessionManager.isValidSapientSignature(wallet.address, chainId, payload, signature) - expect(isValid).toBe(true) - } - - it( - 'should create and sign with an explicit session', - async () => { - await shouldCreateAndSignWithExplicitSession(true) - }, - timeout, - ) - - it( - 'should create and sign with an explicit session with 0 chainId', - async () => { - await shouldCreateAndSignWithExplicitSession(false) - }, - timeout, - ) - - it( - 'should fail to sign with an expired explicit session', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = 0 - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitPermissions: Signers.Session.ExplicitParams = { - chainId, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) - 3600), // 1 hour ago - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, explicitPermissions) - // Create the topology and wallet - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...explicitPermissions, - signer: explicitSigner.address, - chainId, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - }, - { - stateProvider, - }, - ) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - }).withExplicitSigner(explicitSigner) - - // Create a test transaction within permissions - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - const payload: Payload.Calls = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [call], - } - - // Sign the transaction - await expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( - `Signer supporting call is expired: ${explicitSigner.address}`, - ) - }, - timeout, - ) - - const buildAndSignCall = async ( - wallet: Wallet, - sessionManager: Signers.SessionManager, - calls: Payload.Call[], - provider: Provider.Provider, - chainId: number, - ) => { - // Prepare the transaction - const envelope = await wallet.prepareTransaction(provider, calls) - const parentedEnvelope: Payload.Parented = { - ...envelope.payload, - parentWallets: [wallet.address], - } - const imageHash = await sessionManager.imageHash - if (!imageHash) { - throw new Error('Image hash is undefined') - } - const signature = await sessionManager.signSapient(wallet.address, chainId, parentedEnvelope, imageHash) - const sapientSignature: Envelope.SapientSignature = { - imageHash, - signature, - } - // Sign the envelope - const signedEnvelope = Envelope.toSigned(envelope, [sapientSignature]) - const transaction = await wallet.buildTransaction(provider, signedEnvelope) - return transaction - } - - const simulateTransaction = async ( - provider: Provider.Provider, - transaction: { to: Address.Address; data: Hex.Hex }, - expectedEventTopic?: Hex.Hex, - ) => { - // Generate and use a random sender address to prevent race conditions - const senderAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) - await provider.request({ - method: 'anvil_setBalance', - params: [senderAddress, Hex.fromNumber(1000000000000000000n)], - }) - await provider.request({ - method: 'anvil_impersonateAccount', - params: [senderAddress], - }) - - console.log('Simulating transaction', transaction) - const txHash = await provider.request({ - method: 'eth_sendTransaction', - params: [ - { - ...transaction, - from: senderAddress, - }, - ], - }) - console.log('Transaction hash:', txHash) - - // Wait for transaction receipt - await new Promise((resolve) => setTimeout(resolve, 3000)) - const receipt = await provider.request({ - method: 'eth_getTransactionReceipt', - params: [txHash], - }) - if (!receipt) { - throw new Error('Transaction receipt not found') - } - - if (expectedEventTopic) { - // Check for event - if (!receipt.logs) { - throw new Error('No events emitted') - } - if (!receipt.logs.some((log) => log.topics.includes(expectedEventTopic))) { - throw new Error(`Expected topic ${expectedEventTopic} not found in events: ${JSON.stringify(receipt.logs)}`) - } - } - - return receipt - } - - it( - 'signs a payload using an implicit session', - async () => { - // Check the contracts have been deployed - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create an implicit signer - const implicitPrivateKey = Secp256k1.randomPrivateKey() - const implicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: implicitPrivateKey })) - // -- This is sent to the wallet (somehow)-- - const attestation: Attestation.Attestation = { - approvedSigner: implicitAddress, - identityType: new Uint8Array(4), - issuerHash: new Uint8Array(32), - audienceHash: new Uint8Array(32), - applicationData: new Uint8Array(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - const identitySignature = Secp256k1.sign({ - payload: Attestation.hash(attestation), - privateKey: identityPrivateKey, - }) - const topology = SessionConfig.emptySessionsTopology(identityAddress) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(topology)) - // -- Back in dapp -- - const implicitSigner = new Signers.Session.Implicit( - implicitPrivateKey, - attestation, - identitySignature, - implicitAddress, - ) - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - implicitSigners: [implicitSigner], - }) - - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[1]), // Implicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, [call], provider, chainId) - await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[1]) - }, - timeout, - ) - - it( - 'signs a payload using an explicit session with allowAll and value limit', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - // Test manually building the session topology - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - // Random explicit signer will randomise the image hash - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, [call], provider, chainId) - await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0]) - }, - timeout, - ) - - it( - 'signs using explicit session with onlyOnce, consumes usage and rejects second call', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - // Test manually building the session topology - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - // Random explicit signer will randomise the image hash - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment).not.toBeNull() - expect(increment).toBeDefined() - - if (!increment) { - return - } - - const calls = includeIncrement([call], increment, extension.sessions) - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0]) - - // Repeat call fails because the usage limit has been reached - try { - await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - throw new Error('Expected call as no signer supported to fail') - } catch (error) { - expect(error).toBeDefined() - expect(error.message).toContain('No signer supported') - } - }, - timeout, - ) - - it( - 'signs an ERC20 approve using an explicit session', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitPrivateKey })) - const approveAmount = 10000000n // 10 USDC - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ERC20PermissionBuilder.buildApprove(USDC_ADDRESS, explicitAddress, approveAmount)], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - // Test manually building the session topology - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - // Random explicit signer will randomise the image hash - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: USDC_ADDRESS, - value: 0n, - data: AbiFunction.encodeData(AbiFunction.from('function approve(address spender, uint256 amount)'), [ - explicitAddress, - approveAmount, - ]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment).not.toBeNull() - expect(increment).toBeDefined() - - if (!increment) { - return - } - - const calls = includeIncrement([call], increment, extension.sessions) - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction( - provider, - transaction, - AbiEvent.encode( - AbiEvent.from('event Approval(address indexed _owner, address indexed _spender, uint256 _value)'), - ).topics[0], - ) - - // Repeat call fails because the usage limit has been reached - try { - await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - throw new Error('Expected call as no signer supported to fail') - } catch (error) { - expect(error).toBeDefined() - expect(error.message).toContain('No signer supported') - } - }, - timeout, - ) - - it( - 'signs a payload sending value using an explicit session', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitPrivateKey })) - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [PermissionBuilder.for(explicitAddress).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - // Test manually building the session topology - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - // Random explicit signer will randomise the image hash - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Force 1 ETH to the wallet - await provider.request({ - method: 'anvil_setBalance', - params: [wallet.address, Hex.fromNumber(1000000000000000000n)], - }) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: explicitAddress, - value: 1000000000000000000n, // 1 ETH - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment).not.toBeNull() - expect(increment).toBeDefined() - - if (!increment) { - return - } - - const calls = includeIncrement([call], increment, extension.sessions) - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction(provider, transaction) - - // Check the balances - const walletBalance = await provider.request({ - method: 'eth_getBalance', - params: [wallet.address, 'latest'], - }) - expect(BigInt(walletBalance)).toBe(0n) - const explicitAddressBalance = await provider.request({ - method: 'eth_getBalance', - params: [explicitAddress, 'latest'], - }) - expect(BigInt(explicitAddressBalance)).toBe(1000000000000000000n) - - // Repeat call fails because the usage limit has been reached - try { - await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - throw new Error('Expected call as no signer supported to fail') - } catch (error) { - expect(error).toBeDefined() - expect(error.message).toContain('No signer supported') - } - }, - timeout, - ) - - it( - 'signs a payload sending two transactions with cumulative rules using an explicit session', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitPrivateKey })) - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ - { - target: explicitAddress, - rules: [ - // This rule is a hack. The selector "usage" will increment for testing. As we check for greater than or equal, - // the test will always pass even though it is cumulative. - { - cumulative: true, - operation: Permission.ParameterOperation.GREATER_THAN_OR_EQUAL, - value: Bytes.fromHex(AbiFunction.getSelector(EMITTER_FUNCTIONS[0]), { size: 32 }), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }, - ], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Force 1 ETH to the wallet - await provider.request({ - method: 'anvil_setBalance', - params: [wallet.address, Hex.fromNumber(1000000000000000000n)], - }) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: explicitAddress, - value: 500000000000000000n, // 0.5 ETH - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Do it twice to test cumulative rules - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call, call]) - expect(increment).not.toBeNull() - expect(increment).toBeDefined() - - if (!increment) { - return - } - - const calls = includeIncrement([call, call], increment, extension.sessions) - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction(provider, transaction) - - // Check the balances - const walletBalance = await provider.request({ - method: 'eth_getBalance', - params: [wallet.address, 'latest'], - }) - expect(BigInt(walletBalance)).toBe(0n) - const explicitAddressBalance = await provider.request({ - method: 'eth_getBalance', - params: [explicitAddress, 'latest'], - }) - expect(BigInt(explicitAddressBalance)).toBe(1000000000000000000n) - - // Repeat call fails because the ETH usage limit has been reached - try { - await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - throw new Error('Expected call as no signer supported to fail') - } catch (error) { - expect(error).toBeDefined() - expect(error.message).toContain('No signer supported') - } - }, - timeout, - ) - - it( - 'using explicit session, sends value, then uses a non-incremental permission', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create unique identity and state provider for this test - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - // Create explicit signer - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitPrivateKey })) - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [PermissionBuilder.for(explicitAddress).allowAll().build()], - } - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermission) - // Test manually building the session topology - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - // Create the wallet - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - // Random explicit signer will randomise the image hash - { - type: 'sapient-signer', - address: extension.sessions, - weight: 1n, - imageHash, - }, - // Include a random node leaf (bytes32) to prevent image hash collision - Hex.random(32), - ], - }, - { - stateProvider, - }, - ) - // Force 1 ETH to the wallet - await provider.request({ - method: 'anvil_setBalance', - params: [wallet.address, Hex.fromNumber(1000000000000000000n)], - }) - // Create the session manager - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const call: Payload.Call = { - to: explicitAddress, - value: 1000000000000000000n, // 1 ETH - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment).not.toBeNull() - expect(increment).toBeDefined() - - if (!increment) { - return - } - - const calls = includeIncrement([call], increment, extension.sessions) - - // Build, sign and send the transaction - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction(provider, transaction) - - // Check the balances - const walletBalance = await provider.request({ - method: 'eth_getBalance', - params: [wallet.address, 'latest'], - }) - expect(BigInt(walletBalance)).toBe(0n) - const explicitAddressBalance = await provider.request({ - method: 'eth_getBalance', - params: [explicitAddress, 'latest'], - }) - expect(BigInt(explicitAddressBalance)).toBe(1000000000000000000n) - - // Next call is non-incremental - const call2: Payload.Call = { - to: explicitAddress, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), // Explicit emit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - // Even though we are using a non incremental permission, the previous value usage is still included - const increment2 = await sessionManager.prepareIncrement(wallet.address, chainId, [call2]) - expect(increment2).not.toBeNull() - expect(increment2).toBeDefined() - - if (!increment2) { - return - } - - const calls2 = includeIncrement([call2], increment2, extension.sessions) - - // Build, sign and send the transaction - const transaction2 = await buildAndSignCall(wallet, sessionManager, calls2, provider, chainId) - await simulateTransaction(provider, transaction2) - }, - timeout, - ) - - it( - 'two explicit sessions with same value limit: exhaust first then second send uses second session (increment from non-increment calls only)', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const targetAddress = randomAddress() - const valueLimit = 500000000000000000n // 0.5 ETH per session - - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [PermissionBuilder.for(targetAddress).allowAll().build()], - } - - const explicitPrivateKey1 = Secp256k1.randomPrivateKey() - const explicitSigner1 = new Signers.Session.Explicit(explicitPrivateKey1, { - ...sessionPermission, - }) - - const explicitPrivateKey2 = Secp256k1.randomPrivateKey() - const explicitSigner2 = new Signers.Session.Explicit(explicitPrivateKey2, { - ...sessionPermission, - }) - - let sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermission, - signer: explicitSigner1.address, - }) - sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { - ...sessionPermission, - signer: explicitSigner2.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], - }, - { stateProvider }, - ) - // Fund wallet with 2 ETH so we can send 0.5 ETH twice (each tx needs value + gas) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet.address, Hex.fromNumber(2n * 1000000000000000000n)], - }) - - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner1, explicitSigner2], - }) - - const call: Payload.Call = { - to: targetAddress, - value: valueLimit, // one full limit - data: '0x' as Hex.Hex, - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // First send: uses first session (exhausts it) - const increment1 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment1).not.toBeNull() - const calls1 = includeIncrement([call], increment1!, extension.sessions) - const tx1 = await buildAndSignCall(wallet, sessionManager, calls1, provider, chainId) - await simulateTransaction(provider, tx1) - - // Second send: same call. First session is exhausted so findSignersForCalls picks second session. - // Payload is [call, increment] (or [increment, call]). signSapient must derive expectedIncrement - // from non-increment calls only so it matches the increment we built for the second session. - const increment2 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment2).not.toBeNull() - const calls2 = includeIncrement([call], increment2!, extension.sessions) - const tx2 = await buildAndSignCall(wallet, sessionManager, calls2, provider, chainId) - await simulateTransaction(provider, tx2) - }, - timeout, - ) - - describe('increment built from non-increment calls only', () => { - it( - 'prepareIncrement returns null when calls contain only an increment call (no non-increment calls)', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], - } - const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) - const sessionTopology = SessionConfig.addExplicitSession( - SessionConfig.emptySessionsTopology(identityAddress), - { - ...sessionPermission, - signer: explicitSigner.address, - }, - ) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - Hex.random(32), - ], - }, - { stateProvider }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - // Only an increment call (no non-increment calls). Contract would reject payload with only self-call; we return null. - const incrementOnlyCall: Payload.Call = { - to: extension.sessions, - data: AbiFunction.encodeData(Constants.INCREMENT_USAGE_LIMIT, [[]]), - value: 0n, - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - const result = await sessionManager.prepareIncrement(wallet.address, chainId, [incrementOnlyCall]) - expect(result).toBeNull() - }, - timeout, - ) - - it( - 'prepareIncrement([increment, nonIncrementCall]) produces same increment data as prepareIncrement([nonIncrementCall])', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], - } - const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) - const sessionTopology = SessionConfig.addExplicitSession( - SessionConfig.emptySessionsTopology(identityAddress), - { - ...sessionPermission, - signer: explicitSigner.address, - }, - ) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - Hex.random(32), - ], - }, - { stateProvider }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const nonIncrementCall: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const fromNonIncrementOnly = await sessionManager.prepareIncrement(wallet.address, chainId, [ - nonIncrementCall, - ]) - expect(fromNonIncrementOnly).not.toBeNull() - - // Pass [staleIncrement, nonIncrementCall] — increment is ignored when building; result should match - const withStaleIncrement = await sessionManager.prepareIncrement(wallet.address, chainId, [ - fromNonIncrementOnly!, - nonIncrementCall, - ]) - expect(withStaleIncrement).not.toBeNull() - expect(withStaleIncrement!.data).toEqual(fromNonIncrementOnly!.data) - }, - timeout, - ) - - it( - 'payload with implicit and explicit: increment built only from explicit non-increment call', - async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const redirectUrl = 'https://example.com' - const implicitSigner = await createImplicitSigner(redirectUrl, identityPrivateKey) - const sessionPermission: ExplicitSessionConfig = { - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], - } - const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) - - // Topology: identity + blacklist (implicit) + explicit session - let sessionTopology = SessionConfig.emptySessionsTopology(identityAddress) - sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { - ...sessionPermission, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [ - { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, - Hex.random(32), - ], - }, - { stateProvider }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - provider, - sessionManagerAddress: extension.sessions, - implicitSigners: [implicitSigner], - explicitSigners: [explicitSigner], - }) - - // Explicit call only (onlyOnce produces an increment; implicit does not match this target) - const call: Payload.Call = { - to: EMITTER_ADDRESS1, - value: 0n, - data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) - expect(increment).not.toBeNull() - const calls = includeIncrement([call], increment!, extension.sessions) - const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) - await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0]) - }, - timeout, - ) - - it('signSapient handles selector-only self increment call consistently', async () => { - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const stateProvider = new State.Local.Provider() - - const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), { - chainId: 0, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], - }) - - const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...explicitSigner.sessionPermissions, - signer: explicitSigner.address, - }) - await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) - - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], - }, - { stateProvider }, - ) - const sessionManager = new Signers.SessionManager(wallet, { - sessionManagerAddress: extension.sessions, - explicitSigners: [explicitSigner], - }) - - const payload: Payload.Parented = { - type: 'call', - nonce: 0n, - space: 0n, - calls: [ - { - to: extension.sessions, - data: AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT), - value: 0n, - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - parentWallets: [wallet.address], - } - - const signature = await sessionManager.signSapient(wallet.address, 0, payload, imageHash) - expect(signature.type).toBe('sapient') - }) - }) - }) -} diff --git a/packages/wallet/core/test/setup.ts b/packages/wallet/core/test/setup.ts deleted file mode 100644 index e19587147c..0000000000 --- a/packages/wallet/core/test/setup.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { indexedDB, IDBFactory } from 'fake-indexeddb' -import { Provider, RpcTransport } from 'ox' -import { vi } from 'vitest' -import { LOCAL_RPC_URL } from './constants' - -// Add IndexedDB support to the test environment -global.indexedDB = indexedDB -global.IDBFactory = IDBFactory - -// Mock navigator.locks API for Node.js environment --- - -// 1. Ensure the global navigator object exists -if (typeof global.navigator === 'undefined') { - console.log('mocking navigator') - global.navigator = {} as Navigator -} - -// 2. Define or redefine the 'locks' property on navigator -// Check if 'locks' is falsy (null or undefined), OR if it's an object -// that doesn't have the 'request' property we expect in our mock. -if (!global.navigator.locks || !('request' in global.navigator.locks)) { - Object.defineProperty(global.navigator, 'locks', { - // The value of the 'locks' property will be our mock object - value: { - // Mock the 'request' method - request: vi - .fn() - .mockImplementation(async (name: string, callback: (lock: { name: string } | null) => Promise) => { - // Simulate acquiring the lock immediately in the test environment. - const mockLock = { name } // A minimal mock lock object - try { - // Execute the callback provided to navigator.locks.request - const result = await callback(mockLock) - return result // Return the result of the callback - } catch (e) { - // Log errors from the callback for better debugging in tests - console.error(`Error occurred within mocked lock callback for lock "${name}":`, e) - throw e // Re-throw the error so the test potentially fails - } - }), - // Mock the 'query' method - query: vi.fn().mockResolvedValue({ held: [], pending: [] }), - }, - writable: true, - configurable: true, - enumerable: true, - }) -} else { - console.log('navigator.locks already exists and appears to have a "request" property.') -} - -export function mockEthereum() { - // Add window.ethereum support, pointing to the the Anvil local RPC - if (typeof (window as any).ethereum === 'undefined') { - ;(window as any).ethereum = { - request: vi.fn().mockImplementation(async (args: any) => { - // Pipe the request to the Anvil local RPC - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - return provider.request(args) - }), - } - } -} diff --git a/packages/wallet/core/test/signers-guard.test.ts b/packages/wallet/core/test/signers-guard.test.ts deleted file mode 100644 index f42335e03d..0000000000 --- a/packages/wallet/core/test/signers-guard.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { describe, it, expect, vi } from 'vitest' -import { Attestation, Config, Network, Payload } from '@0xsequence/wallet-primitives' -import * as GuardService from '@0xsequence/guard' -import { Address, Bytes, Hash, Hex, Signature, TypedData } from 'ox' -import { Envelope } from '../src/index.js' -import { Guard } from '../src/signers/guard.js' - -// Test addresses and data -const TEST_ADDRESS_1 = Address.from('0x1234567890123456789012345678901234567890') -const TEST_ADDRESS_2 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const TEST_WALLET = Address.from('0xfedcbafedcbafedcbafedcbafedcbafedcbafe00') - -// Mock configuration with single signer -const mockConfig: Config.Config = { - threshold: 2n, - checkpoint: 0n, - topology: { type: 'signer', address: TEST_ADDRESS_1, weight: 2n }, -} - -// Create test envelope -const blankEnvelope = { - wallet: TEST_WALLET, - chainId: Network.ChainId.MAINNET, - configuration: mockConfig, -} - -// Mock signatures -const mockHashSignature: Envelope.Signature = { - address: TEST_ADDRESS_2, - signature: { - type: 'hash', - r: 123n, - s: 456n, - yParity: 0, - }, -} -const mockEthSignSignature: Envelope.Signature = { - address: TEST_ADDRESS_2, - signature: { - type: 'eth_sign', - r: 789n, - s: 101112n, - yParity: 1, - }, -} -const mockErc1271Signature: Envelope.Signature = { - address: TEST_ADDRESS_2, - signature: { - type: 'erc1271', - address: TEST_ADDRESS_2, - data: '0xabcdef123456' as Hex.Hex, - }, -} -const mockSapientSignature: Envelope.SapientSignature = { - imageHash: '0x987654321', - signature: { - type: 'sapient', - address: TEST_ADDRESS_2, - data: '0x9876543210987654321098765432109876543210' as Hex.Hex, - }, -} - -const expectedSignatures = [ - { - type: GuardService.SignatureType.Hash, - address: TEST_ADDRESS_2, - data: Signature.toHex(mockHashSignature.signature as any), - }, - { - type: GuardService.SignatureType.EthSign, - address: TEST_ADDRESS_2, - data: Signature.toHex(mockEthSignSignature.signature as any), - }, - { - type: GuardService.SignatureType.Erc1271, - address: TEST_ADDRESS_2, - data: (mockErc1271Signature.signature as any).data, - }, - { - type: GuardService.SignatureType.Sapient, - address: TEST_ADDRESS_2, - data: mockSapientSignature.signature.data, - imageHash: mockSapientSignature.imageHash, - }, -] - -describe('Guard Signer', () => { - it('should sign call payloads', async () => { - const signFn = vi.fn().mockResolvedValue({ - r: 1n, - s: 2n, - yParity: 0, - }) - const guard = new Guard({ - address: TEST_ADDRESS_1, - signPayload: signFn, - }) - - const call = { - to: '0x1234567890123456789012345678901234567890' as Address.Address, - value: 0n, - data: '0x1234567890123456789012345678901234567890' as Hex.Hex, - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'ignore' as const, - } - - const payload = Payload.fromCall(0n, 0n, [call]) - const envelope = { - payload, - ...blankEnvelope, - } as Envelope.Envelope - - const signatures = [mockHashSignature, mockEthSignSignature, mockErc1271Signature, mockSapientSignature] - const signedEnvelope = Envelope.toSigned(envelope, signatures) - const token = { id: 'TOTP' as const, code: '123456' } - - const result = await guard.signEnvelope(signedEnvelope, token) - expect(result).toEqual({ - address: TEST_ADDRESS_1, - signature: { - type: 'hash', - r: 1n, - s: 2n, - yParity: 0, - }, - }) - - const typedData = Payload.toTyped(TEST_WALLET, Network.ChainId.MAINNET, payload) - const expectedDigest = Bytes.fromHex(TypedData.getSignPayload(typedData)) - const expectedMessage = Bytes.fromString(TypedData.serialize(typedData)) - - expect(signFn).toHaveBeenCalledExactlyOnceWith( - TEST_WALLET, - Network.ChainId.MAINNET, - GuardService.PayloadType.Calls, - expectedDigest, - expectedMessage, - expectedSignatures, - { id: 'TOTP', token: '123456' }, - ) - }) - - it('should sign message payloads', async () => { - const signFn = vi.fn().mockResolvedValue({ - r: 1n, - s: 2n, - yParity: 0, - }) - const guard = new Guard({ - address: TEST_ADDRESS_1, - signPayload: signFn, - }) - - const payload = Payload.fromMessage(Hex.fromString('Test message')) - const envelope = { - payload, - ...blankEnvelope, - } as Envelope.Envelope - - const signatures = [mockHashSignature, mockEthSignSignature, mockErc1271Signature, mockSapientSignature] - const signedEnvelope = Envelope.toSigned(envelope, signatures) - const token = { id: 'TOTP' as const, code: '123456' } - - const result = await guard.signEnvelope(signedEnvelope, token) - expect(result).toEqual({ - address: TEST_ADDRESS_1, - signature: { - type: 'hash', - r: 1n, - s: 2n, - yParity: 0, - }, - }) - - const typedData = Payload.toTyped(TEST_WALLET, Network.ChainId.MAINNET, payload) - const expectedDigest = Bytes.fromHex(TypedData.getSignPayload(typedData)) - const expectedMessage = Bytes.fromString(TypedData.serialize(typedData)) - - expect(signFn).toHaveBeenCalledExactlyOnceWith( - TEST_WALLET, - Network.ChainId.MAINNET, - GuardService.PayloadType.Message, - expectedDigest, - expectedMessage, - expectedSignatures, - { id: 'TOTP', token: '123456' }, - ) - }) - - it('should sign config update payloads', async () => { - const signFn = vi.fn().mockResolvedValue({ - r: 1n, - s: 2n, - yParity: 0, - }) - const guard = new Guard({ - address: TEST_ADDRESS_1, - signPayload: signFn, - }) - - const payload = Payload.fromConfigUpdate(Hex.fromString('0x987654321098765432109876543210')) - const envelope = { - payload, - ...blankEnvelope, - } as Envelope.Envelope - - const signatures = [mockHashSignature, mockEthSignSignature, mockErc1271Signature, mockSapientSignature] - const signedEnvelope = Envelope.toSigned(envelope, signatures) - const token = { id: 'TOTP' as const, code: '123456' } - - const result = await guard.signEnvelope(signedEnvelope, token) - expect(result).toEqual({ - address: TEST_ADDRESS_1, - signature: { - type: 'hash', - r: 1n, - s: 2n, - yParity: 0, - }, - }) - - const typedData = Payload.toTyped(TEST_WALLET, Network.ChainId.MAINNET, payload) - const expectedDigest = Bytes.fromHex(TypedData.getSignPayload(typedData)) - const expectedMessage = Bytes.fromString(TypedData.serialize(typedData)) - - expect(signFn).toHaveBeenCalledExactlyOnceWith( - TEST_WALLET, - Network.ChainId.MAINNET, - GuardService.PayloadType.ConfigUpdate, - expectedDigest, - expectedMessage, - expectedSignatures, - { id: 'TOTP', token: '123456' }, - ) - }) - - it('should sign session implicit authorize payloads', async () => { - const signFn = vi.fn().mockResolvedValue({ - r: 1n, - s: 2n, - yParity: 0, - }) - const guard = new Guard({ - address: TEST_ADDRESS_1, - signPayload: signFn, - }) - - const payload = { - type: 'session-implicit-authorize', - sessionAddress: TEST_ADDRESS_2, - attestation: { - approvedSigner: TEST_ADDRESS_2, - identityType: Bytes.fromHex('0x00000001'), - issuerHash: Hash.keccak256(Bytes.fromString('issuer')), - audienceHash: Hash.keccak256(Bytes.fromString('audience')), - applicationData: Bytes.fromString('applicationData'), - authData: { - redirectUrl: 'https://example.com', - issuedAt: 1n, - }, - }, - } as Payload.SessionImplicitAuthorize - const envelope = { - payload, - ...blankEnvelope, - } as Envelope.Envelope - - const signatures = [mockHashSignature, mockEthSignSignature, mockErc1271Signature, mockSapientSignature] - const signedEnvelope = Envelope.toSigned(envelope, signatures) - const token = { id: 'TOTP' as const, code: '123456' } - - const result = await guard.signEnvelope(signedEnvelope, token) - expect(result).toEqual({ - address: TEST_ADDRESS_1, - signature: { - type: 'hash', - r: 1n, - s: 2n, - yParity: 0, - }, - }) - - const expectedDigest = Hash.keccak256(Attestation.encode(payload.attestation)) - const expectedMessage = Bytes.fromString(Attestation.toJson(payload.attestation)) - - expect(signFn).toHaveBeenCalledExactlyOnceWith( - TEST_WALLET, - Network.ChainId.MAINNET, - GuardService.PayloadType.SessionImplicitAuthorize, - expectedDigest, - expectedMessage, - expectedSignatures, - { id: 'TOTP', token: '123456' }, - ) - }) -}) diff --git a/packages/wallet/core/test/signers-index.test.ts b/packages/wallet/core/test/signers-index.test.ts deleted file mode 100644 index 553310ab8e..0000000000 --- a/packages/wallet/core/test/signers-index.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Address, Hex } from 'ox' -import { isSapientSigner, isSigner, Signer, SapientSigner } from '../src/signers/index.js' - -describe('Signers Index Type Guards', () => { - const mockAddress = '0x1234567890123456789012345678901234567890' as Address.Address - const mockImageHash = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - - describe('isSapientSigner', () => { - it('Should return true for objects with signSapient method', () => { - const sapientSigner = { - address: mockAddress, - imageHash: mockImageHash, - signSapient: () => ({ signature: Promise.resolve({} as any) }), - } as SapientSigner - - expect(isSapientSigner(sapientSigner)).toBe(true) - }) - - it('Should return false for objects without signSapient method', () => { - const regularSigner = { - address: mockAddress, - sign: () => ({ signature: Promise.resolve({} as any) }), - } as Signer - - expect(isSapientSigner(regularSigner)).toBe(false) - }) - - it('Should return false for objects with sign but not signSapient', () => { - const mixedObject = { - address: mockAddress, - sign: () => ({ signature: Promise.resolve({} as any) }), - // Missing signSapient method - } - - expect(isSapientSigner(mixedObject as any)).toBe(false) - }) - }) - - describe('isSigner', () => { - it('Should return true for objects with sign method', () => { - const regularSigner = { - address: mockAddress, - sign: () => ({ signature: Promise.resolve({} as any) }), - } as Signer - - expect(isSigner(regularSigner)).toBe(true) - }) - - it('Should return false for objects without sign method', () => { - const sapientSigner = { - address: mockAddress, - imageHash: mockImageHash, - signSapient: () => ({ signature: Promise.resolve({} as any) }), - } as SapientSigner - - expect(isSigner(sapientSigner)).toBe(false) - }) - - it('Should return true for objects that have both sign and signSapient', () => { - const hybridSigner = { - address: mockAddress, - imageHash: mockImageHash, - sign: () => ({ signature: Promise.resolve({} as any) }), - signSapient: () => ({ signature: Promise.resolve({} as any) }), - } - - expect(isSigner(hybridSigner as any)).toBe(true) - }) - }) - - describe('Type guard integration', () => { - it('Should correctly identify different signer types in arrays', () => { - const regularSigner = { - address: mockAddress, - sign: () => ({ signature: Promise.resolve({} as any) }), - } as Signer - - const sapientSigner = { - address: mockAddress, - imageHash: mockImageHash, - signSapient: () => ({ signature: Promise.resolve({} as any) }), - } as SapientSigner - - const mixedSigners = [regularSigner, sapientSigner] - - const sapientSigners = mixedSigners.filter(isSapientSigner) - const regularSigners = mixedSigners.filter(isSigner) - - expect(sapientSigners).toHaveLength(1) - expect(sapientSigners[0]).toBe(sapientSigner) - expect(regularSigners).toHaveLength(1) - expect(regularSigners[0]).toBe(regularSigner) - }) - }) -}) diff --git a/packages/wallet/core/test/signers-passkey.test.ts b/packages/wallet/core/test/signers-passkey.test.ts deleted file mode 100644 index 8de54fd744..0000000000 --- a/packages/wallet/core/test/signers-passkey.test.ts +++ /dev/null @@ -1,666 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes } from 'ox' -import { Payload, Extensions } from '@0xsequence/wallet-primitives' -import { - Passkey, - PasskeyOptions, - isWitnessMessage, - WitnessMessage, - CreatePasskeyOptions, -} from '../src/signers/passkey.js' -import { State } from '../src/index.js' - -// Add mock for WebAuthnP256 at the top -vi.mock('ox', async () => { - const actual = await vi.importActual('ox') - return { - ...actual, - WebAuthnP256: { - createCredential: vi.fn(), - sign: vi.fn(), - }, - } -}) - -describe('Passkey Signers', () => { - const mockAddress = '0x1234567890123456789012345678901234567890' as Address.Address - const mockImageHash = - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - const mockWallet = '0xfedcbafedcbafedcbafedcbafedcbafedcbafedcba' as Address.Address - - const mockPublicKey: Extensions.Passkeys.PublicKey = { - x: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex, - y: '0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321' as Hex.Hex, - requireUserVerification: true, - } - - const mockExtensions: Pick = { - passkeys: mockAddress, - } - - const mockMetadata: Extensions.Passkeys.PasskeyMetadata = { - credentialId: 'test-credential-id', - } - - describe('isWitnessMessage type guard', () => { - it('Should return true for valid WitnessMessage objects', () => { - const validMessage: WitnessMessage = { - action: 'consent-to-be-part-of-wallet', - wallet: mockWallet, - publicKey: mockPublicKey, - timestamp: Date.now(), - } - - expect(isWitnessMessage(validMessage)).toBe(true) - }) - - it('Should return true for valid WitnessMessage with metadata', () => { - const validMessageWithMetadata: WitnessMessage = { - action: 'consent-to-be-part-of-wallet', - wallet: mockWallet, - publicKey: mockPublicKey, - timestamp: Date.now(), - metadata: mockMetadata, - } - - expect(isWitnessMessage(validMessageWithMetadata)).toBe(true) - }) - - it('Should return false for objects with wrong action', () => { - const invalidMessage = { - action: 'wrong-action', - wallet: mockWallet, - publicKey: mockPublicKey, - timestamp: Date.now(), - } - - expect(isWitnessMessage(invalidMessage)).toBe(false) - }) - - it('Should return false for objects missing action', () => { - const invalidMessage = { - wallet: mockWallet, - publicKey: mockPublicKey, - timestamp: Date.now(), - } - - expect(isWitnessMessage(invalidMessage)).toBe(false) - }) - - it('Should return false for null or undefined', () => { - expect(isWitnessMessage(null)).toBe(false) - expect(isWitnessMessage(undefined)).toBe(false) - }) - - it('Should return false for non-objects', () => { - expect(isWitnessMessage('string')).toBe(false) - expect(isWitnessMessage(123)).toBe(false) - expect(isWitnessMessage(true)).toBe(false) - }) - }) - - describe('Passkey Constructor', () => { - it('Should construct with basic options', () => { - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - } - - const passkey = new Passkey(options) - - expect(passkey.address).toBe(mockExtensions.passkeys) - expect(passkey.publicKey).toBe(mockPublicKey) - expect(passkey.credentialId).toBe('test-credential') - expect(passkey.embedMetadata).toBe(false) // default value - expect(passkey.metadata).toBeUndefined() - }) - - it('Should construct with embedMetadata option', () => { - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - embedMetadata: true, - } - - const passkey = new Passkey(options) - - expect(passkey.embedMetadata).toBe(true) - }) - - it('Should construct with metadata option', () => { - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - metadata: mockMetadata, - } - - const passkey = new Passkey(options) - - expect(passkey.metadata).toBe(mockMetadata) - }) - - it('Should compute imageHash from publicKey', () => { - // Mock the Extensions.Passkeys.rootFor function - const mockImageHash = '0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba' as Hex.Hex - vi.spyOn(Extensions.Passkeys, 'rootFor').mockReturnValue(mockImageHash) - - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - } - - const passkey = new Passkey(options) - - expect(passkey.imageHash).toBe(mockImageHash) - expect(Extensions.Passkeys.rootFor).toHaveBeenCalledWith(mockPublicKey) - }) - - it('Should handle all options together', () => { - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - embedMetadata: true, - metadata: mockMetadata, - } - - const passkey = new Passkey(options) - - expect(passkey.address).toBe(mockExtensions.passkeys) - expect(passkey.publicKey).toBe(mockPublicKey) - expect(passkey.credentialId).toBe('test-credential') - expect(passkey.embedMetadata).toBe(true) - expect(passkey.metadata).toBe(mockMetadata) - }) - }) - - describe('loadFromWitness static method', () => { - let mockStateReader: State.Reader - - beforeEach(() => { - mockStateReader = { - getWitnessForSapient: vi.fn(), - } as any - vi.clearAllMocks() - }) - - it('Should throw error when witness not found', async () => { - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(undefined) - - await expect(Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash)).rejects.toThrow( - 'Witness for wallet not found', - ) - - expect(mockStateReader.getWitnessForSapient).toHaveBeenCalledWith( - mockWallet, - mockExtensions.passkeys, - mockImageHash, - ) - }) - - it('Should throw error when witness payload is not a message', async () => { - const mockWitness = { - payload: { type: 'call', calls: [] }, // Not a message type - signature: { data: '0x123456' }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - - await expect(Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash)).rejects.toThrow( - 'Witness payload is not a message', - ) - }) - - it('Should throw error when witness message is invalid JSON', async () => { - const mockWitness = { - payload: { - type: 'message', - message: Hex.fromString('invalid json'), - }, - signature: { data: '0x123456' }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - - await expect( - Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash), - ).rejects.toThrow() - }) - - it('Should throw error when witness message is not a witness message', async () => { - const invalidMessage = { - action: 'wrong-action', - wallet: mockWallet, - } - - const mockWitness = { - payload: { - type: 'message', - message: Hex.fromString(JSON.stringify(invalidMessage)), - }, - signature: { data: '0x123456' }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - - await expect(Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash)).rejects.toThrow( - 'Witness payload is not a witness message', - ) - }) - - it('Should throw error when metadata is string or undefined', async () => { - const witnessMessage: WitnessMessage = { - action: 'consent-to-be-part-of-wallet', - wallet: mockWallet, - publicKey: { - ...mockPublicKey, - metadata: 'string-metadata' as any, - }, - timestamp: Date.now(), - } - - const mockWitness = { - payload: { - type: 'message', - message: Hex.fromString(JSON.stringify(witnessMessage)), - }, - signature: { data: '0x123456' }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - - await expect(Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash)).rejects.toThrow( - 'Metadata does not contain credential id', - ) - }) - - it('Should successfully load passkey from valid witness with publicKey metadata', async () => { - const validWitnessMessage: WitnessMessage = { - action: 'consent-to-be-part-of-wallet', - wallet: mockWallet, - publicKey: { - ...mockPublicKey, - metadata: mockMetadata, - }, - timestamp: Date.now(), - } - - const mockEncodedSignature = new Uint8Array([1, 2, 3, 4]) - const mockDecodedSignature = { - embedMetadata: true, - } - - const mockWitness = { - payload: { - type: 'message', - message: Hex.fromString(JSON.stringify(validWitnessMessage)), - }, - signature: { data: Bytes.toHex(mockEncodedSignature) }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - vi.spyOn(Extensions.Passkeys, 'decode').mockReturnValue(mockDecodedSignature as any) - - const result = await Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash) - - expect(result).toBeInstanceOf(Passkey) - expect(result.credentialId).toBe(mockMetadata.credentialId) - expect(result.publicKey).toEqual(validWitnessMessage.publicKey) - expect(result.embedMetadata).toBe(true) - expect(result.metadata).toEqual(mockMetadata) - }) - - it('Should successfully load passkey from valid witness with separate metadata field', async () => { - const validWitnessMessage: WitnessMessage = { - action: 'consent-to-be-part-of-wallet', - wallet: mockWallet, - publicKey: mockPublicKey, - timestamp: Date.now(), - metadata: mockMetadata, - } - - const mockEncodedSignature = new Uint8Array([1, 2, 3, 4]) - const mockDecodedSignature = { - embedMetadata: false, - } - - const mockWitness = { - payload: { - type: 'message', - message: Hex.fromString(JSON.stringify(validWitnessMessage)), - }, - signature: { data: Bytes.toHex(mockEncodedSignature) }, - } - - vi.mocked(mockStateReader.getWitnessForSapient).mockResolvedValue(mockWitness as any) - vi.spyOn(Extensions.Passkeys, 'decode').mockReturnValue(mockDecodedSignature as any) - - const result = await Passkey.loadFromWitness(mockStateReader, mockExtensions, mockWallet, mockImageHash) - - expect(result).toBeInstanceOf(Passkey) - expect(result.credentialId).toBe(mockMetadata.credentialId) - expect(result.publicKey).toEqual(mockPublicKey) - expect(result.embedMetadata).toBe(false) - expect(result.metadata).toEqual(mockMetadata) - }) - }) - - describe('create static method', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('Should use default credential name when none provided', async () => { - const mockCredential = { - id: 'test-credential-id', - publicKey: { x: 123n, y: 456n }, - } - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.createCredential).mockResolvedValue(mockCredential as any) - vi.spyOn(Extensions.Passkeys, 'toTree').mockReturnValue({} as any) - - const result = await Passkey.create(mockExtensions) - - expect(WebAuthnP256.createCredential).toHaveBeenCalledWith({ - user: { - name: expect.stringMatching(/^Sequence \(\d+\)$/), - }, - }) - - expect(result).toBeInstanceOf(Passkey) - expect(result.credentialId).toBe('test-credential-id') - }) - - it('Should use custom credential name when provided', async () => { - const mockCredential = { - id: 'test-credential-id', - publicKey: { x: 123n, y: 456n }, - } - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.createCredential).mockResolvedValue(mockCredential as any) - vi.spyOn(Extensions.Passkeys, 'toTree').mockReturnValue({} as any) - - const options: CreatePasskeyOptions = { - credentialName: 'Custom Credential Name', - } - - await Passkey.create(mockExtensions, options) - - expect(WebAuthnP256.createCredential).toHaveBeenCalledWith({ - user: { - name: 'Custom Credential Name', - }, - }) - }) - - it('Should handle embedMetadata option', async () => { - const mockCredential = { - id: 'test-credential-id', - publicKey: { x: 123n, y: 456n }, - } - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.createCredential).mockResolvedValue(mockCredential as any) - vi.spyOn(Extensions.Passkeys, 'toTree').mockReturnValue({} as any) - - const options: CreatePasskeyOptions = { - embedMetadata: true, - } - - const result = await Passkey.create(mockExtensions, options) - - expect(result.embedMetadata).toBe(true) - expect(result.publicKey.metadata).toBeDefined() - }) - - it('Should save tree when stateProvider is provided', async () => { - const mockCredential = { - id: 'test-credential-id', - publicKey: { x: 123n, y: 456n }, - } - - const mockStateProvider = { - saveTree: vi.fn().mockResolvedValue(undefined), - } as any - - const mockTree = { mockTree: true } - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.createCredential).mockResolvedValue(mockCredential as any) - vi.spyOn(Extensions.Passkeys, 'toTree').mockReturnValue(mockTree as any) - - const options: CreatePasskeyOptions = { - stateProvider: mockStateProvider, - } - - await Passkey.create(mockExtensions, options) - - expect(mockStateProvider.saveTree).toHaveBeenCalledWith(mockTree) - }) - }) - - describe('signSapient method', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('Should generate correct signature structure', async () => { - const passkey = new Passkey({ - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - }) - - // Mock imageHash to match - vi.spyOn(passkey, 'imageHash', 'get').mockReturnValue(mockImageHash) - - const mockWebAuthnResponse = { - signature: { r: 123n, s: 456n }, - metadata: { - authenticatorData: '0xdeadbeef', - clientDataJSON: '{"test":"data"}', - }, - } - - const mockEncodedSignature = new Uint8Array([1, 2, 3, 4]) - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.sign).mockResolvedValue(mockWebAuthnResponse as any) - vi.spyOn(Extensions.Passkeys, 'encode').mockReturnValue(mockEncodedSignature) - vi.spyOn(Payload, 'hash').mockReturnValue(new Uint8Array([1, 2, 3, 4])) - - const mockPayload = Payload.fromMessage(Hex.fromString('test message')) - const result = await passkey.signSapient(mockWallet, 1, mockPayload, mockImageHash) - - expect(result).toEqual({ - address: mockExtensions.passkeys, - data: Bytes.toHex(mockEncodedSignature), - type: 'sapient_compact', - }) - - expect(WebAuthnP256.sign).toHaveBeenCalledWith({ - challenge: expect.any(String), - credentialId: 'test-credential', - userVerification: 'required', - }) - }) - - it('Should use discouraged user verification when requireUserVerification is false', async () => { - const publicKeyNoVerification = { - ...mockPublicKey, - requireUserVerification: false, - } - - const passkey = new Passkey({ - extensions: mockExtensions, - publicKey: publicKeyNoVerification, - credentialId: 'test-credential', - }) - - vi.spyOn(passkey, 'imageHash', 'get').mockReturnValue(mockImageHash) - - const mockWebAuthnResponse = { - signature: { r: 123n, s: 456n }, - metadata: { - authenticatorData: '0xdeadbeef', - clientDataJSON: '{"test":"data"}', - }, - } - - const { WebAuthnP256 } = await import('ox') - vi.mocked(WebAuthnP256.sign).mockResolvedValue(mockWebAuthnResponse as any) - vi.spyOn(Extensions.Passkeys, 'encode').mockReturnValue(new Uint8Array([1, 2, 3, 4])) - vi.spyOn(Payload, 'hash').mockReturnValue(new Uint8Array([1, 2, 3, 4])) - - const mockPayload = Payload.fromMessage(Hex.fromString('test message')) - await passkey.signSapient(mockWallet, 1, mockPayload, mockImageHash) - - expect(WebAuthnP256.sign).toHaveBeenCalledWith({ - challenge: expect.any(String), - credentialId: 'test-credential', - userVerification: 'discouraged', - }) - }) - }) - - describe('witness method', () => { - let mockStateWriter: State.Writer - let passkey: Passkey - - beforeEach(() => { - mockStateWriter = { - saveWitnesses: vi.fn().mockResolvedValue(undefined), - } as any - - passkey = new Passkey({ - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - metadata: mockMetadata, - }) - - vi.clearAllMocks() - }) - - it('Should create witness with correct message structure', async () => { - const mockSignature = { - address: mockExtensions.passkeys, - data: '0xabcdef' as const, - type: 'sapient_compact' as const, - } - - vi.spyOn(passkey, 'signSapient').mockResolvedValue(mockSignature) - - await passkey.witness(mockStateWriter, mockWallet) - - expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1) - - const [wallet, chainId, payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - - expect(wallet).toBe(mockWallet) - expect(chainId).toBe(0) - - // Check the payload contains the witness message - const messagePayload = payload as { type: 'message'; message: Hex.Hex } - const witnessMessage = JSON.parse(Hex.toString(messagePayload.message)) - - expect(witnessMessage.action).toBe('consent-to-be-part-of-wallet') - expect(witnessMessage.wallet).toBe(mockWallet) - expect(witnessMessage.publicKey).toEqual(mockPublicKey) - expect(witnessMessage.metadata).toEqual(mockMetadata) - expect(typeof witnessMessage.timestamp).toBe('number') - - // Check the witness structure - const rawLeaf = witness as { type: 'unrecovered-signer'; weight: bigint; signature: any } - expect(rawLeaf.type).toBe('unrecovered-signer') - expect(rawLeaf.weight).toBe(1n) - expect(rawLeaf.signature).toBe(mockSignature) - }) - - it('Should include extra data in witness message', async () => { - const extraData = { customField: 'test-value', version: '1.0' } - - const mockSignature = { - address: mockExtensions.passkeys, - data: '0xabcdef' as const, - type: 'sapient_compact' as const, - } - - vi.spyOn(passkey, 'signSapient').mockResolvedValue(mockSignature) - - await passkey.witness(mockStateWriter, mockWallet, extraData) - - const [, , payload] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - - const messagePayload = payload as { type: 'message'; message: Hex.Hex } - const witnessMessage = JSON.parse(Hex.toString(messagePayload.message)) - - expect(witnessMessage.customField).toBe('test-value') - expect(witnessMessage.version).toBe('1.0') - }) - - it('Should call signSapient with correct parameters', async () => { - const mockSignature = { - address: mockExtensions.passkeys, - data: '0xabcdef' as const, - type: 'sapient_compact' as const, - } - - const signSapientSpy = vi.spyOn(passkey, 'signSapient').mockResolvedValue(mockSignature) - - await passkey.witness(mockStateWriter, mockWallet) - - expect(signSapientSpy).toHaveBeenCalledWith( - mockWallet, - 0, - expect.any(Object), // The payload - passkey.imageHash, - ) - }) - }) - - describe('Error handling for imageHash mismatch', () => { - it('Should throw error when signSapient called with wrong imageHash', async () => { - const passkey = new Passkey({ - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - }) - - const wrongImageHash = '0x9999999999999999999999999999999999999999999999999999999999999999' as Hex.Hex - const mockPayload = Payload.fromMessage(Hex.fromString('test message')) - - await expect(passkey.signSapient(mockWallet, 1, mockPayload, wrongImageHash)).rejects.toThrow( - 'Unexpected image hash', - ) - }) - }) - - describe('Properties and getters', () => { - it('Should expose all properties correctly', () => { - const options: PasskeyOptions = { - extensions: mockExtensions, - publicKey: mockPublicKey, - credentialId: 'test-credential', - embedMetadata: true, - metadata: mockMetadata, - } - - const passkey = new Passkey(options) - - // Test all public properties - expect(passkey.credentialId).toBe('test-credential') - expect(passkey.publicKey).toBe(mockPublicKey) - expect(passkey.address).toBe(mockExtensions.passkeys) - expect(passkey.embedMetadata).toBe(true) - expect(passkey.metadata).toBe(mockMetadata) - expect(passkey.imageHash).toBeDefined() - }) - }) -}) diff --git a/packages/wallet/core/test/signers-pk-encrypted.test.ts b/packages/wallet/core/test/signers-pk-encrypted.test.ts deleted file mode 100644 index 79e54ba894..0000000000 --- a/packages/wallet/core/test/signers-pk-encrypted.test.ts +++ /dev/null @@ -1,425 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes, PublicKey, Secp256k1 } from 'ox' -import { EncryptedPksDb, EncryptedPkStore, EncryptedData } from '../src/signers/pk/encrypted.js' - -// Mock Ox module -vi.mock('ox', async () => { - const actual = (await vi.importActual('ox')) as any - return { - ...actual, - Hex: { - ...(actual.Hex || {}), - random: vi.fn(), - }, - Secp256k1: { - ...(actual.Secp256k1 || {}), - getPublicKey: vi.fn(), - sign: vi.fn(), - }, - Address: { - ...(actual.Address || {}), - fromPublicKey: vi.fn(), - }, - } -}) - -// Mock global objects -const mockLocalStorage = { - setItem: vi.fn(), - getItem: vi.fn(), - removeItem: vi.fn(), -} - -const mockCryptoSubtle = { - generateKey: vi.fn(), - exportKey: vi.fn(), - importKey: vi.fn(), - encrypt: vi.fn(), - decrypt: vi.fn(), -} - -const mockCrypto = { - subtle: mockCryptoSubtle, - getRandomValues: vi.fn(), -} - -const mockIndexedDB = { - open: vi.fn(), -} - -// Setup global mocks -Object.defineProperty(globalThis, 'localStorage', { - value: mockLocalStorage, - writable: true, -}) - -Object.defineProperty(globalThis, 'crypto', { - value: mockCrypto, - writable: true, -}) - -Object.defineProperty(globalThis, 'indexedDB', { - value: mockIndexedDB, - writable: true, -}) - -// Mock window object -Object.defineProperty(globalThis, 'window', { - value: { - crypto: mockCrypto, - localStorage: mockLocalStorage, - }, - writable: true, -}) - -describe('Encrypted Private Key Signers', () => { - const mockAddress = '0x1234567890123456789012345678901234567890' as Address.Address - const mockPrivateKey = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - const mockPublicKey = { x: 123n, y: 456n } as PublicKey.PublicKey - const mockIv = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - const mockEncryptedBuffer = new ArrayBuffer(32) - const mockDigest = new Uint8Array([1, 2, 3, 4]) as Bytes.Bytes - - beforeEach(() => { - vi.clearAllMocks() - - // Reset mock implementations - mockLocalStorage.setItem.mockImplementation(() => {}) - mockLocalStorage.getItem.mockImplementation(() => null) - mockLocalStorage.removeItem.mockImplementation(() => {}) - - mockCrypto.getRandomValues.mockImplementation((array) => { - if (array instanceof Uint8Array) { - array.set(mockIv) - } - return array - }) - }) - - describe('EncryptedPksDb', () => { - let encryptedDb: EncryptedPksDb - - beforeEach(() => { - encryptedDb = new EncryptedPksDb() - }) - - describe('Constructor', () => { - it('Should construct with default parameters', () => { - const db = new EncryptedPksDb() - expect(db).toBeInstanceOf(EncryptedPksDb) - }) - - it('Should construct with custom parameters', () => { - const db = new EncryptedPksDb('custom_prefix_', 'custom_table') - expect(db).toBeInstanceOf(EncryptedPksDb) - }) - }) - - describe('computeDbKey', () => { - it('Should compute correct database key', () => { - // Access the private method via bracket notation for testing - const dbKey = (encryptedDb as any).computeDbKey(mockAddress) - expect(dbKey).toBe(`pk_${mockAddress.toLowerCase()}`) - }) - }) - - describe('generateAndStore', () => { - beforeEach(() => { - // Mock crypto operations - const mockCryptoKey = { type: 'secret' } - const mockJwk = { k: 'test-key', alg: 'A256GCM' } - - mockCryptoSubtle.generateKey.mockResolvedValue(mockCryptoKey) - mockCryptoSubtle.exportKey.mockResolvedValue(mockJwk) - mockCryptoSubtle.encrypt.mockResolvedValue(mockEncryptedBuffer) - - // Mock Ox functions using the mocked module - vi.mocked(Hex.random).mockReturnValue(mockPrivateKey) - vi.mocked(Secp256k1.getPublicKey).mockReturnValue(mockPublicKey) - vi.mocked(Address.fromPublicKey).mockReturnValue(mockAddress) - - // Mock database operations by spying on private methods - vi.spyOn(encryptedDb as any, 'putData').mockResolvedValue(undefined) - }) - - it('Should generate and store encrypted private key', async () => { - const result = await encryptedDb.generateAndStore() - - expect(result).toEqual({ - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'e_pk_key_' + mockAddress, - address: mockAddress, - publicKey: mockPublicKey, - }) - - expect(mockCryptoSubtle.generateKey).toHaveBeenCalledWith({ name: 'AES-GCM', length: 256 }, true, [ - 'encrypt', - 'decrypt', - ]) - - expect(mockLocalStorage.setItem).toHaveBeenCalledWith( - 'e_pk_key_' + mockAddress, - JSON.stringify({ k: 'test-key', alg: 'A256GCM' }), - ) - - expect(mockCryptoSubtle.encrypt).toHaveBeenCalledWith( - { name: 'AES-GCM', iv: mockIv }, - { type: 'secret' }, - expect.any(Uint8Array), - ) - }) - }) - - describe('getEncryptedEntry', () => { - it('Should return encrypted entry for valid address', async () => { - const mockEncryptedData: EncryptedData = { - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'test-key-pointer', - address: mockAddress, - publicKey: mockPublicKey, - } - - vi.spyOn(encryptedDb as any, 'getData').mockResolvedValue(mockEncryptedData) - - const result = await encryptedDb.getEncryptedEntry(mockAddress) - expect(result).toBe(mockEncryptedData) - }) - - it('Should return undefined for non-existent address', async () => { - vi.spyOn(encryptedDb as any, 'getData').mockResolvedValue(undefined) - - const result = await encryptedDb.getEncryptedEntry(mockAddress) - expect(result).toBeUndefined() - }) - }) - - describe('getEncryptedPkStore', () => { - it('Should return EncryptedPkStore for valid address', async () => { - const mockEncryptedData: EncryptedData = { - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'test-key-pointer', - address: mockAddress, - publicKey: mockPublicKey, - } - - // Spy on getEncryptedEntry - vi.spyOn(encryptedDb, 'getEncryptedEntry').mockResolvedValue(mockEncryptedData) - - const result = await encryptedDb.getEncryptedPkStore(mockAddress) - - expect(result).toBeInstanceOf(EncryptedPkStore) - expect(encryptedDb.getEncryptedEntry).toHaveBeenCalledWith(mockAddress) - }) - - it('Should return undefined when entry does not exist', async () => { - vi.spyOn(encryptedDb, 'getEncryptedEntry').mockResolvedValue(undefined) - - const result = await encryptedDb.getEncryptedPkStore(mockAddress) - - expect(result).toBeUndefined() - }) - }) - - describe('listAddresses', () => { - it('Should return list of addresses', async () => { - const mockEntries: EncryptedData[] = [ - { - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'key1', - address: mockAddress, - publicKey: mockPublicKey, - }, - { - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'key2', - address: '0x9876543210987654321098765432109876543210' as Address.Address, - publicKey: mockPublicKey, - }, - ] - - vi.spyOn(encryptedDb as any, 'getAllData').mockResolvedValue(mockEntries) - - const result = await encryptedDb.listAddresses() - expect(result).toEqual([mockAddress, '0x9876543210987654321098765432109876543210']) - }) - }) - - describe('remove', () => { - it('Should remove encrypted data from both IndexedDB and localStorage', async () => { - vi.spyOn(encryptedDb as any, 'putData').mockResolvedValue(undefined) - - await encryptedDb.remove(mockAddress) - - expect((encryptedDb as any).putData).toHaveBeenCalledWith(`pk_${mockAddress.toLowerCase()}`, undefined) - expect(mockLocalStorage.removeItem).toHaveBeenCalledWith(`e_pk_key_${mockAddress}`) - }) - }) - - describe('Database operations', () => { - it('Should handle openDB correctly', async () => { - const mockDatabase = { - transaction: vi.fn(), - objectStoreNames: { contains: vi.fn().mockReturnValue(false) }, - createObjectStore: vi.fn(), - } - - const mockRequest = { - result: mockDatabase, - onsuccess: null as any, - onerror: null as any, - onupgradeneeded: null as any, - } - - mockIndexedDB.open.mockReturnValue(mockRequest) - - const dbPromise = (encryptedDb as any).openDB() - - // Simulate successful opening - setTimeout(() => { - if (mockRequest.onsuccess) { - mockRequest.onsuccess({ target: { result: mockDatabase } }) - } - }, 0) - - const result = await dbPromise - expect(result).toBe(mockDatabase) - expect(mockIndexedDB.open).toHaveBeenCalledWith('pk-db', 1) - }) - - it('Should handle database upgrade', async () => { - const mockDatabase = { - transaction: vi.fn(), - objectStoreNames: { contains: vi.fn().mockReturnValue(false) }, - createObjectStore: vi.fn(), - } - - const mockRequest = { - result: mockDatabase, - onsuccess: null as any, - onerror: null as any, - onupgradeneeded: null as any, - } - - mockIndexedDB.open.mockReturnValue(mockRequest) - - const dbPromise = (encryptedDb as any).openDB() - - // Simulate upgrade needed then success - setTimeout(() => { - if (mockRequest.onupgradeneeded) { - mockRequest.onupgradeneeded({ target: { result: mockDatabase } }) - } - if (mockRequest.onsuccess) { - mockRequest.onsuccess({ target: { result: mockDatabase } }) - } - }, 0) - - const result = await dbPromise - expect(result).toBe(mockDatabase) - expect(mockDatabase.createObjectStore).toHaveBeenCalledWith('e_pk') - }) - }) - }) - - describe('EncryptedPkStore', () => { - let encryptedData: EncryptedData - let encryptedStore: EncryptedPkStore - - beforeEach(() => { - encryptedData = { - iv: mockIv, - data: mockEncryptedBuffer, - keyPointer: 'test-key-pointer', - address: mockAddress, - publicKey: mockPublicKey, - } - encryptedStore = new EncryptedPkStore(encryptedData) - }) - - describe('address', () => { - it('Should return the correct address', () => { - expect(encryptedStore.address()).toBe(mockAddress) - }) - }) - - describe('publicKey', () => { - it('Should return the correct public key', () => { - expect(encryptedStore.publicKey()).toBe(mockPublicKey) - }) - }) - - describe('signDigest', () => { - beforeEach(() => { - const mockJwk = { k: 'test-key', alg: 'A256GCM' } - const mockCryptoKey = { type: 'secret' } - const mockDecryptedBuffer = new TextEncoder().encode(mockPrivateKey) - const mockSignature = { r: 123n, s: 456n, yParity: 0 } - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify(mockJwk)) - mockCryptoSubtle.importKey.mockResolvedValue(mockCryptoKey) - mockCryptoSubtle.decrypt.mockResolvedValue(mockDecryptedBuffer) - vi.mocked(Secp256k1.sign).mockReturnValue(mockSignature) - }) - - it('Should sign digest successfully', async () => { - const result = await encryptedStore.signDigest(mockDigest) - - expect(result).toEqual({ r: 123n, s: 456n, yParity: 0 }) - - expect(mockLocalStorage.getItem).toHaveBeenCalledWith('test-key-pointer') - expect(mockCryptoSubtle.importKey).toHaveBeenCalledWith( - 'jwk', - { k: 'test-key', alg: 'A256GCM' }, - { name: 'AES-GCM' }, - false, - ['decrypt'], - ) - expect(mockCryptoSubtle.decrypt).toHaveBeenCalledWith( - { name: 'AES-GCM', iv: mockIv }, - { type: 'secret' }, - mockEncryptedBuffer, - ) - expect(Secp256k1.sign).toHaveBeenCalledWith({ - payload: mockDigest, - privateKey: mockPrivateKey, - }) - }) - - it('Should throw error when encryption key not found in localStorage', async () => { - mockLocalStorage.getItem.mockReturnValue(null) - - await expect(encryptedStore.signDigest(mockDigest)).rejects.toThrow('Encryption key not found in localStorage') - }) - - it('Should handle JSON parsing errors', async () => { - mockLocalStorage.getItem.mockReturnValue('invalid json') - - await expect(encryptedStore.signDigest(mockDigest)).rejects.toThrow() - }) - - it('Should handle crypto import key errors', async () => { - const mockJwk = { k: 'test-key', alg: 'A256GCM' } - mockLocalStorage.getItem.mockReturnValue(JSON.stringify(mockJwk)) - mockCryptoSubtle.importKey.mockRejectedValue(new Error('Import key failed')) - - await expect(encryptedStore.signDigest(mockDigest)).rejects.toThrow('Import key failed') - }) - - it('Should handle decryption errors', async () => { - const mockJwk = { k: 'test-key', alg: 'A256GCM' } - const mockCryptoKey = { type: 'secret' } - - mockLocalStorage.getItem.mockReturnValue(JSON.stringify(mockJwk)) - mockCryptoSubtle.importKey.mockResolvedValue(mockCryptoKey) - mockCryptoSubtle.decrypt.mockRejectedValue(new Error('Decryption failed')) - - await expect(encryptedStore.signDigest(mockDigest)).rejects.toThrow('Decryption failed') - }) - }) - }) -}) diff --git a/packages/wallet/core/test/signers-pk.test.ts b/packages/wallet/core/test/signers-pk.test.ts deleted file mode 100644 index 7a696cf6c2..0000000000 --- a/packages/wallet/core/test/signers-pk.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes, PublicKey, Secp256k1 } from 'ox' -import { Payload, Network } from '@0xsequence/wallet-primitives' -import { Pk, MemoryPkStore, PkStore } from '../src/signers/pk/index.js' -import { State } from '../src/index.js' - -describe('Private Key Signers', () => { - const testPrivateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - const testWallet = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' as Address.Address - const testChainId = Network.ChainId.ARBITRUM - - describe('MemoryPkStore', () => { - let memoryStore: MemoryPkStore - - beforeEach(() => { - memoryStore = new MemoryPkStore(testPrivateKey) - }) - - it('Should derive correct address from private key', () => { - const address = memoryStore.address() - const expectedAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: testPrivateKey })) - - expect(address).toBe(expectedAddress) - }) - - it('Should derive correct public key from private key', () => { - const publicKey = memoryStore.publicKey() - const expectedPublicKey = Secp256k1.getPublicKey({ privateKey: testPrivateKey }) - - expect(publicKey).toEqual(expectedPublicKey) - }) - - it('Should sign digest correctly', async () => { - const testDigest = Bytes.fromString('test message') - const signature = await memoryStore.signDigest(testDigest) - - expect(signature).toHaveProperty('r') - expect(signature).toHaveProperty('s') - expect(signature).toHaveProperty('yParity') - expect(typeof signature.r).toBe('bigint') - expect(typeof signature.s).toBe('bigint') - expect([0, 1]).toContain(signature.yParity) - }) - }) - - describe('Pk Class', () => { - describe('Constructor', () => { - it('Should construct with private key hex string', () => { - const pk = new Pk(testPrivateKey) - - expect(pk.address).toBeDefined() - expect(pk.pubKey).toBeDefined() - expect(typeof pk.address).toBe('string') - expect(pk.address.startsWith('0x')).toBe(true) - }) - - it('Should construct with PkStore instance', () => { - const store = new MemoryPkStore(testPrivateKey) - const pk = new Pk(store) - - expect(pk.address).toBe(store.address()) - expect(pk.pubKey).toEqual(store.publicKey()) - }) - - it('Should set correct address and public key properties', () => { - const pk = new Pk(testPrivateKey) - const expectedPubKey = Secp256k1.getPublicKey({ privateKey: testPrivateKey }) - const expectedAddress = Address.fromPublicKey(expectedPubKey) - - expect(pk.pubKey).toEqual(expectedPubKey) - expect(pk.address).toBe(expectedAddress) - }) - }) - - describe('Signing Methods', () => { - let pk: Pk - let testPayload: any - - beforeEach(() => { - pk = new Pk(testPrivateKey) - testPayload = Payload.fromMessage(Hex.fromString('Test signing message')) - }) - - it('Should sign payload correctly', async () => { - const signature = await pk.sign(testWallet, testChainId, testPayload) - - expect(signature).toHaveProperty('type', 'hash') - // Type assertion since we know it's a hash signature - const hashSig = signature as { type: 'hash'; r: bigint; s: bigint; yParity: number } - expect(hashSig).toHaveProperty('r') - expect(hashSig).toHaveProperty('s') - expect(hashSig).toHaveProperty('yParity') - expect(typeof hashSig.r).toBe('bigint') - expect(typeof hashSig.s).toBe('bigint') - }) - - it('Should sign digest directly', async () => { - const testDigest = Bytes.fromString('direct digest test') - const signature = await pk.signDigest(testDigest) - - expect(signature).toHaveProperty('type', 'hash') - const hashSig = signature as { type: 'hash'; r: bigint; s: bigint; yParity: number } - expect(hashSig).toHaveProperty('r') - expect(hashSig).toHaveProperty('s') - expect(hashSig).toHaveProperty('yParity') - }) - - it('Should produce consistent signatures for same input', async () => { - const sig1 = await pk.sign(testWallet, testChainId, testPayload) - const sig2 = await pk.sign(testWallet, testChainId, testPayload) - - const hashSig1 = sig1 as { type: 'hash'; r: bigint; s: bigint; yParity: number } - const hashSig2 = sig2 as { type: 'hash'; r: bigint; s: bigint; yParity: number } - expect(hashSig1.r).toBe(hashSig2.r) - expect(hashSig1.s).toBe(hashSig2.s) - expect(hashSig1.yParity).toBe(hashSig2.yParity) - }) - - it('Should produce different signatures for different inputs', async () => { - const payload1 = Payload.fromMessage(Hex.fromString('Message 1')) - const payload2 = Payload.fromMessage(Hex.fromString('Message 2')) - - const sig1 = await pk.sign(testWallet, testChainId, payload1) - const sig2 = await pk.sign(testWallet, testChainId, payload2) - - const hashSig1 = sig1 as { type: 'hash'; r: bigint; s: bigint; yParity: number } - expect(hashSig1.r).not.toBe((sig2 as any).r) - }) - }) - - describe('Witness Method', () => { - let pk: Pk - let mockStateWriter: State.Writer - - beforeEach(() => { - pk = new Pk(testPrivateKey) - mockStateWriter = { - saveWitnesses: vi.fn().mockResolvedValue(undefined), - } as any - }) - - it('Should create witness with default message structure', async () => { - await pk.witness(mockStateWriter, testWallet) - - expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1) - const [wallet, chainId, _payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - - expect(wallet).toBe(testWallet) - expect(chainId).toBe(0) - // Cast witness to RawLeaf since we know it's an unrecovered-signer leaf - const rawLeaf = witness as { type: 'unrecovered-signer'; weight: bigint; signature: any } - expect(rawLeaf.type).toBe('unrecovered-signer') - expect(rawLeaf.weight).toBe(1n) - expect(rawLeaf.signature).toHaveProperty('type', 'hash') - }) - - it('Should include extra data in witness payload', async () => { - const extraData = { customField: 'test-value', version: '1.0' } - await pk.witness(mockStateWriter, testWallet, extraData) - - expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1) - const [, , payload] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - - // Decode the payload message from the Message type - const messagePayload = payload as { type: 'message'; message: Hex.Hex } - const payloadMessage = Hex.toString(messagePayload.message) - const witnessData = JSON.parse(payloadMessage) - - expect(witnessData.action).toBe('consent-to-be-part-of-wallet') - expect(witnessData.wallet).toBe(testWallet) - expect(witnessData.signer).toBe(pk.address) - expect(witnessData.customField).toBe('test-value') - expect(witnessData.version).toBe('1.0') - expect(typeof witnessData.timestamp).toBe('number') - }) - - it('Should create valid signature for witness', async () => { - await pk.witness(mockStateWriter, testWallet) - - const [, , , witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - - const rawLeaf = witness as { type: 'unrecovered-signer'; weight: bigint; signature: any } - const hashSig = rawLeaf.signature as { type: 'hash'; r: bigint; s: bigint; yParity: number } - expect(hashSig).toHaveProperty('r') - expect(hashSig).toHaveProperty('s') - expect(hashSig).toHaveProperty('yParity') - expect(hashSig.type).toBe('hash') - }) - - it('Should use timestamp in witness message', async () => { - const beforeTime = Date.now() - await pk.witness(mockStateWriter, testWallet) - const afterTime = Date.now() - - const [, , payload] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] - const messagePayload = payload as { type: 'message'; message: Hex.Hex } - const witnessData = JSON.parse(Hex.toString(messagePayload.message)) - - expect(witnessData.timestamp).toBeGreaterThanOrEqual(beforeTime) - expect(witnessData.timestamp).toBeLessThanOrEqual(afterTime) - }) - }) - - describe('Integration Tests', () => { - it('Should work end-to-end with different PkStore implementations', async () => { - const memoryStore = new MemoryPkStore(testPrivateKey) - const pkWithStore = new Pk(memoryStore) - const pkWithHex = new Pk(testPrivateKey) - - const testDigest = Bytes.fromString('integration test') - - const sig1 = await pkWithStore.signDigest(testDigest) - const sig2 = await pkWithHex.signDigest(testDigest) - - expect(sig1).toEqual(sig2) - }) - }) - }) - - describe('Custom PkStore Implementation', () => { - it('Should work with custom PkStore implementation', async () => { - class CustomPkStore implements PkStore { - private privateKey: Hex.Hex - - constructor(pk: Hex.Hex) { - this.privateKey = pk - } - - address(): Address.Address { - return Address.fromPublicKey(this.publicKey()) - } - - publicKey(): PublicKey.PublicKey { - return Secp256k1.getPublicKey({ privateKey: this.privateKey }) - } - - async signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> { - return Secp256k1.sign({ payload: digest, privateKey: this.privateKey }) - } - } - - const customStore = new CustomPkStore(testPrivateKey) - const pk = new Pk(customStore) - - expect(pk.address).toBe(customStore.address()) - expect(pk.pubKey).toEqual(customStore.publicKey()) - - const signature = await pk.signDigest(Bytes.fromString('custom store test')) - expect(signature.type).toBe('hash') - }) - }) -}) diff --git a/packages/wallet/core/test/signers-session-explicit.test.ts b/packages/wallet/core/test/signers-session-explicit.test.ts deleted file mode 100644 index 74cf25f7dd..0000000000 --- a/packages/wallet/core/test/signers-session-explicit.test.ts +++ /dev/null @@ -1,571 +0,0 @@ -import { Address, Bytes, Secp256k1 } from 'ox' -import { describe, expect, it } from 'vitest' - -import { Permission, SessionConfig } from '../../primitives/src/index.js' -import { Signers } from '../src/index.js' - -function randomAddress(): Address.Address { - return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) -} - -describe('Explicit Session', () => { - describe('isValid', () => { - const identityAddress = randomAddress() - const explicitPrivateKey = Secp256k1.randomPrivateKey() - const explicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitPrivateKey })) - const targetAddress = randomAddress() - const currentTime = Math.floor(Date.now() / 1000) - const futureTime = currentTime + 3600 // 1 hour from now - const pastTime = currentTime - 3600 // 1 hour ago - - const createValidSessionPermissions = (): Signers.Session.ExplicitParams => ({ - chainId: 1, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(futureTime), - permissions: [ - { - target: targetAddress, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x'), 32), - offset: 0n, - mask: Bytes.padLeft(Bytes.fromHex('0x'), 32), - }, - ], - }, - ], - }) - - const createValidTopology = ( - sessionPermissions: Signers.Session.ExplicitParams, - ): SessionConfig.SessionsTopology => { - return SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - }) - } - - it('should return true for valid session with matching topology', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when session is expired', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - deadline: BigInt(pastTime), - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Expired') - }) - - it('should return false when session deadline equals current time', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - deadline: BigInt(currentTime), - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Expired') - }) - - it('should return false when chainId does not match (session has specific chainId)', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - chainId: 1, - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 2) // Different chainId - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Chain ID mismatch') - }) - - it('should return true when session chainId is 0 (any chain)', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - chainId: 0, // Any chain - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 999) // Any chainId - - expect(result.isValid).toBe(true) - }) - - it('should return false when session signer is not found in topology', () => { - const sessionPermissions = createValidSessionPermissions() - const differentAddress = randomAddress() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: differentAddress, // Different signer - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission not found') - }) - - it('should return false when topology has no explicit sessions', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.emptySessionsTopology(identityAddress) // No explicit sessions - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission not found') - }) - - it('should return false when deadline does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - deadline: BigInt(futureTime + 100), // Different deadline - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return false when chainId does not match in topology', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - chainId: 2, // Different chainId in topology - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return false when valueLimit does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - valueLimit: 2000000000000000000n, // Different value limit - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return false when permissions length does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - ...sessionPermissions.permissions, - { - target: randomAddress(), - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x'), 32), - offset: 0n, - mask: Bytes.padLeft(Bytes.fromHex('0x'), 32), - }, - ], - }, - ], // Extra permission - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return false when permission target does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: randomAddress(), // Different target - rules: sessionPermissions.permissions[0]!.rules, - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when permission rules length does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - ...sessionPermissions.permissions[0]!.rules, - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x'), 32), - offset: 0n, - mask: Bytes.padLeft(Bytes.fromHex('0x'), 32), - }, - ], // Extra rule - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when rule cumulative does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - { - ...sessionPermissions.permissions[0]!.rules[0]!, - cumulative: true, // Different cumulative value - }, - ], - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when rule operation does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - { - ...sessionPermissions.permissions[0]!.rules[0]!, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, // Different operation - }, - ], - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when rule value does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - { - ...sessionPermissions.permissions[0]!.rules[0]!, - value: Bytes.padLeft(Bytes.fromHex('0x01'), 32), // Different value - }, - ], - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when rule offset does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - { - ...sessionPermissions.permissions[0]!.rules[0]!, - offset: 32n, // Different offset - }, - ], - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when rule mask does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - { - target: sessionPermissions.permissions[0]!.target, - rules: [ - { - ...sessionPermissions.permissions[0]!.rules[0]!, - mask: Bytes.padLeft(Bytes.fromHex('0xff'), 32), // Different mask - }, - ], - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should return false when topology permission deadline is expired', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - deadline: BigInt(pastTime), // Expired in topology - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return false when topology permission chainId does not match', () => { - const sessionPermissions = createValidSessionPermissions() - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - chainId: 2, // Different chainId in topology - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission mismatch') - }) - - it('should return true with complex permission rules', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - chainId: 1, - valueLimit: 1000000000000000000n, - deadline: BigInt(futureTime), - permissions: [ - { - target: targetAddress, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0xa9059cbb'), 32), // transfer selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - { - cumulative: true, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromNumber(1000000000000000000n, { size: 32 }), - offset: 4n + 32n, // Second parameter - mask: Permission.MASK.UINT256, - }, - ], - }, - ], - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with multiple permissions', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - chainId: 1, - valueLimit: 1000000000000000000n, - deadline: BigInt(futureTime), - permissions: [ - { - target: targetAddress, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }, - { - target: randomAddress(), - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x095ea7b3'), 32), // approve selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }, - ], - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when one of multiple permissions does not match', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - chainId: 1, - valueLimit: 1000000000000000000n, - deadline: BigInt(futureTime), - permissions: [ - { - target: targetAddress, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }, - { - target: randomAddress(), - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x095ea7b3'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }, - ], - } - const topology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { - ...sessionPermissions, - signer: explicitAddress, - permissions: [ - sessionPermissions.permissions[0]!, // First permission matches - { - target: randomAddress(), // Different target for second permission - rules: sessionPermissions.permissions[1]!.rules, - }, - ], - }) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Permission rule mismatch') - }) - - it('should handle edge case with zero deadline', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - deadline: 0n, - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) // Zero deadline should be considered expired - }) - - it('should handle edge case with very large deadline', () => { - const sessionPermissions: Signers.Session.ExplicitParams = { - ...createValidSessionPermissions(), - deadline: BigInt(Number.MAX_SAFE_INTEGER), - } - const topology = createValidTopology(sessionPermissions) - const explicitSigner = new Signers.Session.Explicit(explicitPrivateKey, sessionPermissions) - - const result = explicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - }) -}) diff --git a/packages/wallet/core/test/signers-session-implicit.test.ts b/packages/wallet/core/test/signers-session-implicit.test.ts deleted file mode 100644 index 5b0a370823..0000000000 --- a/packages/wallet/core/test/signers-session-implicit.test.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { Address, Bytes, Hex, Secp256k1, Signature } from 'ox' -import { describe, expect, it } from 'vitest' - -import { Attestation, Permission, SessionConfig } from '../../primitives/src/index.js' -import { Signers } from '../src/index.js' - -function randomAddress(): Address.Address { - return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) -} - -describe('Implicit Session', () => { - const identityPrivateKey = Secp256k1.randomPrivateKey() - const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) - const implicitPrivateKey = Secp256k1.randomPrivateKey() - const implicitAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: implicitPrivateKey })) - const sessionManagerAddress = randomAddress() - - const createValidAttestation = (): Attestation.Attestation => ({ - approvedSigner: implicitAddress, - identityType: new Uint8Array(4), - issuerHash: new Uint8Array(32), - audienceHash: new Uint8Array(32), - applicationData: new Uint8Array(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - }) - - const createValidIdentitySignature = (attestation: Attestation.Attestation): Signature.Signature => { - return Secp256k1.sign({ - payload: Attestation.hash(attestation), - privateKey: identityPrivateKey, - }) - } - - const createValidTopology = (): SessionConfig.SessionsTopology => { - return SessionConfig.emptySessionsTopology(identityAddress) - } - - const createImplicitSigner = (attestation: Attestation.Attestation, identitySignature: Signature.Signature) => { - return new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, sessionManagerAddress) - } - - describe('constructor', () => { - it('should throw an error if the attestation is issued in the future', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(Number.MAX_SAFE_INTEGER), - }, - } - const identitySignature = createValidIdentitySignature(attestation) - expect( - () => new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, sessionManagerAddress), - ).toThrow('Attestation issued in the future') - }) - - it('should throw an error if the attestation is for a different signer', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - approvedSigner: randomAddress(), - } - const identitySignature = createValidIdentitySignature(attestation) - expect( - () => new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, sessionManagerAddress), - ).toThrow('Invalid attestation') - }) - }) - - describe('isValid', () => { - it('should return true for valid session with matching identity signer', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when topology has no identity signer', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology: SessionConfig.SessionsTopology = Hex.fromBytes(Bytes.random(32)) - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Identity signer not found') - }) - - it('should return false when identity signer does not match', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const differentIdentityAddress = randomAddress() - const topology = SessionConfig.emptySessionsTopology(differentIdentityAddress) // Different identity - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Identity signer not found') - }) - - it('should return true regardless of chainId', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - // Test with different chainIds - expect(implicitSigner.isValid(topology, 1).isValid).toBe(true) - expect(implicitSigner.isValid(topology, 137).isValid).toBe(true) - expect(implicitSigner.isValid(topology, 42161).isValid).toBe(true) - expect(implicitSigner.isValid(topology, 999999).isValid).toBe(true) - }) - - it('should return true with different identity types', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - identityType: new Uint8Array([0x12, 0x34, 0x56, 0x78]), - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with different issuer hashes', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - issuerHash: Bytes.random(32), - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with different audience hashes', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - audienceHash: Bytes.random(32), - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with different application data', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - applicationData: Bytes.fromString('custom application data'), - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with different redirect URLs', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: 'https://different-example.com', - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with different issued times', () => { - const pastTime = Math.floor(Date.now() / 1000) - 3600 // 1 hour ago - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(pastTime), - }, - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when identity signature is invalid', () => { - const attestation = createValidAttestation() - const wrongPrivateKey = Secp256k1.randomPrivateKey() - const invalidIdentitySignature = Secp256k1.sign({ - payload: Attestation.hash(attestation), - privateKey: wrongPrivateKey, // Wrong private key - }) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, invalidIdentitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Identity signer not found') - }) - - it('should return false when attestation is issued in the future', () => { - const futureTime = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: BigInt(futureTime), - }, - } - const identitySignature = createValidIdentitySignature(attestation) - - // This should throw an error during construction due to future issued time - expect(() => { - new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, sessionManagerAddress) - }).toThrow('Attestation issued in the future') - }) - - it('should return false when attestation approvedSigner does not match implicit address', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - approvedSigner: randomAddress(), // Different approved signer - } - const identitySignature = createValidIdentitySignature(attestation) - - // This should throw an error during construction due to mismatched approved signer - expect(() => { - new Signers.Session.Implicit(implicitPrivateKey, attestation, identitySignature, sessionManagerAddress) - }).toThrow('Invalid attestation') - }) - - it('should handle edge case with zero issued time', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: 'https://example.com', - issuedAt: 0n, - }, - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should handle edge case with empty identity type', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - identityType: new Uint8Array(0), // Empty identity type - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should handle edge case with empty application data', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - applicationData: new Uint8Array(0), // Empty application data - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should handle edge case with empty redirect URL', () => { - const attestation: Attestation.Attestation = { - ...createValidAttestation(), - authData: { - redirectUrl: '', // Empty redirect URL - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true with complex topology structure', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology: SessionConfig.SessionsTopology = [ - SessionConfig.emptySessionsTopology(identityAddress), - // Add explicit sessions - { - type: 'session-permissions', - signer: randomAddress(), - chainId: 1, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [ - { - target: randomAddress(), - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padLeft(Bytes.fromHex('0x'), 32), - offset: 0n, - mask: Bytes.padLeft(Bytes.fromHex('0x'), 32), - }, - ], - }, - ], - }, - ] - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should verify identity signer recovery works correctly', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - // Verify that the recovered identity signer matches the expected one - const recoveredIdentitySigner = implicitSigner.identitySigner - expect(recoveredIdentitySigner).toBe(identityAddress) - - const result = implicitSigner.isValid(topology, 1) - expect(result.isValid).toBe(true) - }) - - it('should handle signature as hex string', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() - - // Create signer with hex string signature - const implicitSigner = new Signers.Session.Implicit( - implicitPrivateKey, - attestation, - Signature.toHex(identitySignature), - sessionManagerAddress, - ) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when implicit signer is in blacklist', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - - // Create topology with the implicit signer in the blacklist - const topology = SessionConfig.addToImplicitBlacklist( - SessionConfig.emptySessionsTopology(identityAddress), - implicitAddress, - ) - - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - expect(result.invalidReason).toBe('Blacklisted') - }) - - it('should return true when implicit signer is not in blacklist', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - - // Create topology with a different address in the blacklist - const differentAddress = randomAddress() - const topology = SessionConfig.addToImplicitBlacklist( - SessionConfig.emptySessionsTopology(identityAddress), - differentAddress, - ) - - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return true when blacklist is empty', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() // No blacklist entries - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when implicit signer is in blacklist with multiple entries', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - - // Create topology with multiple blacklist entries including the implicit signer - let topology = SessionConfig.emptySessionsTopology(identityAddress) - topology = SessionConfig.addToImplicitBlacklist(topology, randomAddress()) - topology = SessionConfig.addToImplicitBlacklist(topology, implicitAddress) // Add our signer - topology = SessionConfig.addToImplicitBlacklist(topology, randomAddress()) - - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - }) - - it('should return true when implicit signer is not in blacklist with multiple entries', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - - // Create topology with multiple blacklist entries but not our signer - let topology = SessionConfig.emptySessionsTopology(identityAddress) - topology = SessionConfig.addToImplicitBlacklist(topology, randomAddress()) - topology = SessionConfig.addToImplicitBlacklist(topology, randomAddress()) - topology = SessionConfig.addToImplicitBlacklist(topology, randomAddress()) - - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(true) - }) - - it('should return false when implicit signer is in blacklist even with valid identity signer', () => { - const attestation = createValidAttestation() - const identitySignature = createValidIdentitySignature(attestation) - - // Create topology with valid identity signer but implicit signer in blacklist - const topology = SessionConfig.addToImplicitBlacklist( - SessionConfig.emptySessionsTopology(identityAddress), - implicitAddress, - ) - - const implicitSigner = createImplicitSigner(attestation, identitySignature) - - const result = implicitSigner.isValid(topology, 1) - - expect(result.isValid).toBe(false) - }) - }) -}) diff --git a/packages/wallet/core/test/state/arweave/arweave.test.ts b/packages/wallet/core/test/state/arweave/arweave.test.ts deleted file mode 100644 index 8c7aa5cd20..0000000000 --- a/packages/wallet/core/test/state/arweave/arweave.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs' -import { Address } from 'ox' -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' - -import { Arweave, Reader, Sequence } from '../../../src/state/index' - -const TEST_TIMEOUT_MS = 20_000 -const RECORDING_FILE = new URL('./recording', import.meta.url) - -type RecordedRequest = { - method: string - url: string - headers: Record - body: string -} - -type RecordedResponse = { - status: number - statusText: string - headers: Record - body: string -} - -type RecordingEntry = { - request: RecordedRequest - response: RecordedResponse -} - -const tests: { [method in keyof Reader]: { [description: string]: Parameters } } = { - getConfiguration: { - 'image hash: 0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78': [ - '0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78', - ], - }, - getDeploy: { - 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE': ['0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE'], - }, - getWallets: { - 'signer: 0x94835215CaA1aD3E304F9A7E2148623fe661dEB7': ['0x94835215CaA1aD3E304F9A7E2148623fe661dEB7'], - }, - getWalletsForSapient: { - 'signer: 0x000000000000AB36D17eB1150116371520565205, image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': - [ - '0x000000000000AB36D17eB1150116371520565205', - '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', - ], - }, - getWitnessFor: { - 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE, signer: 0x94835215CaA1aD3E304F9A7E2148623fe661dEB7': [ - '0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE', - '0x94835215CaA1aD3E304F9A7E2148623fe661dEB7', - ], - }, - getWitnessForSapient: { - 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE, signer: 0x000000000000AB36D17eB1150116371520565205, image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': - [ - '0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE', - '0x000000000000AB36D17eB1150116371520565205', - '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', - ], - }, - getConfigurationUpdates: { - 'wallet: 0x135769a58639b4Fa7d779a9df9B57A706FBCa816, from: 0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a': - [ - '0x135769a58639b4Fa7d779a9df9B57A706FBCa816', - '0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a', - ], - }, - getTree: { - 'image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': [ - '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', - ], - }, - getPayload: { - 'calls payload: 0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576': [ - '0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576', - ], - 'message payload: 0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4': [ - '0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4', - ], - 'config update payload: 0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b': [ - '0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b', - ], - 'digest payload: 0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432': [ - '0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432', - ], - }, -} - -function normalize(value: any): any { - switch (typeof value) { - case 'string': - if (Address.validate(value)) { - return Address.checksum(value) - } - - break - - case 'object': - if (value === null) { - return value - } - - if (Array.isArray(value)) { - return value.map(normalize) - } - - return Object.fromEntries( - Object.entries(value) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => [Address.validate(key) ? Address.checksum(key) : key, normalize(value)]), - ) - } - - return value -} - -function normalizeHeaders(headers: Headers): Record { - return Object.fromEntries([...headers.entries()].sort(([left], [right]) => left.localeCompare(right))) -} - -async function serializeRequest(input: RequestInfo | URL, init?: RequestInit): Promise { - const request = new Request(input, init) - - return { - method: request.method.toUpperCase(), - url: request.url, - headers: normalizeHeaders(request.headers), - body: request.method === 'GET' || request.method === 'HEAD' ? '' : await request.clone().text(), - } -} - -function serializeResponse(response: Response, body: string): RecordedResponse { - return { - status: response.status, - statusText: response.statusText, - headers: normalizeHeaders(response.headers), - body, - } -} - -function requestKey(request: RecordedRequest): string { - return JSON.stringify(request) -} - -describe('Arweave state reader', () => { - let arweave: Arweave.Reader - let sequence: Sequence.Provider - let originalFetch: typeof globalThis.fetch | undefined - - beforeAll(() => { - originalFetch = globalThis.fetch - if (!originalFetch) { - throw new Error('fetch is not available') - } - - if (existsSync(RECORDING_FILE)) { - const entries = JSON.parse(readFileSync(RECORDING_FILE, 'utf8')) as RecordingEntry[] - const responsesByRequest = new Map() - - for (const entry of entries) { - const key = requestKey(entry.request) - const responses = responsesByRequest.get(key) - - if (responses) { - responses.push(entry.response) - } else { - responsesByRequest.set(key, [entry.response]) - } - } - - globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { - const request = await serializeRequest(input, init) - const response = responsesByRequest.get(requestKey(request))?.shift() - - if (!response) { - throw new Error(`no recorded response for request ${JSON.stringify(request, null, 2)}`) - } - - return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }) - }) as typeof fetch - } else { - const entries: RecordingEntry[] = [] - - globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { - const request = await serializeRequest(input, init) - const response = await originalFetch!(input, init) - const body = await response.clone().text() - - entries.push({ request, response: serializeResponse(response, body) }) - writeFileSync(RECORDING_FILE, JSON.stringify(entries, null, 2)) - - return response - }) as typeof fetch - } - - arweave = new Arweave.Reader() - sequence = new Sequence.Provider() - }) - - afterAll(() => { - if (originalFetch) { - globalThis.fetch = originalFetch - } - }) - - const methods = Object.entries(tests).filter(([, methodTests]) => Object.keys(methodTests).length > 0) - if (methods.length === 0) { - it.skip('no configured test cases', () => {}) - } - - for (const [method, methodTests] of methods) { - describe(method, () => { - for (const [description, args] of Object.entries(methodTests)) { - it( - description, - async () => { - const [actual, expected] = await Promise.all([arweave[method](...args), sequence[method](...args)]) - expect(normalize(actual)).toEqual(normalize(expected)) - }, - TEST_TIMEOUT_MS, - ) - } - }) - } -}) diff --git a/packages/wallet/core/test/state/arweave/recording b/packages/wallet/core/test/state/arweave/recording deleted file mode 100644 index f40e18f2e3..0000000000 --- a/packages/wallet/core/test/state/arweave/recording +++ /dev/null @@ -1,2455 +0,0 @@ -[ - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Config", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"imageHash\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb2e8831a2cf-YUL", - "connection": "keep-alive", - "content-length": "888", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:24 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"config\":{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb2ef8f75a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:24 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=5,cfOrigin;dur=450", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kpbR2IS45A+6Ufh8O1Qh02tddaebPFaIhBE+3wUVbVfW", - "x-77-nzt-ray": "331b5e0fe1a2ac959bd7df69036d5039", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NTk3LCJ3VE1XLVpndWhxQzgyZGNpek5jM0ctNWF0anlOMjJ4UkxfOW8waTNHX2swIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"wTMW-ZguhqC82dcizNc3G-5atjyN22xRL_9o0i3G_k0\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000008008000000802084048200000180000021400280141002000500020421\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/wTMW-ZguhqC82dcizNc3G-5atjyN22xRL_9o0i3G_k0", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:SnWk69XgOgnlTPEDUhZ6TNgWTJCmBUiMvvNY/xaSOxQ=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:24 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000008008000000802084048200000180000021400280141002000500020421", - "sequence-sessions-signers-count": "7", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-d6gtbzsyiobir2md4otysq5na7mzvhkjbgdadaxlgww=:/bnk/h4oAlnZ9L4ktaWXBfj7HuyUMt6ShWgm14KRw54=:, comm-wtmw-zguhqc82dciznc3g-5atjyn22xrl_9o0i3g_k0=:0nVpmzPqHnqRks1pepdX3cbGNhqiPS6Ol/kEVmluwqYFSdXi03u6JM/dsj+O7z45yu1mJoZqr0xAdi15BEE3Exw=:", - "signature-input": "comm-d6gtbzsyiobir2md4otysq5na7mzvhkjbgdadaxlgww=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-wtmw-zguhqc82dciznc3g-5atjyn22xrl_9o0i3g_k0=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhmZDMyZTAxZDdlODE0MjkyZjQ5ZjU3ZTc5NzIyY2E2NjQyMzgzM2FjZjhmMjVlYmE3NzBmYWYzNDgzZmYzZTc4, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Nw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMDAwODAyMDg0MDQ4MjAwMDAwMTgwMDAwMDIxNDAwMjgwMTQxMDAyMDAwNTAwMDIwNDIx\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "knpw3SVX5Mh6YEaxRsshOVYzMudH3if+g0tVslJtIwUt8A24pg", - "x-77-nzt-ray": "8705ec3425f5520e9cd7df696bdc2d25", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/DeployHash", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb3378b3a2cf-YUL", - "connection": "keep-alive", - "content-length": "467", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:24 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"deployHash\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\",\"context\":{\"factory\":\"0x00000000000018A77519fcCCa060c2537c9D6d3F\",\"guestModule\":\"0x0000000000006Ac72ed1d192fa28f0058D3F8806\",\"mainModule\":\"0x0000000000001f3C39d61698ab21131a12134454\",\"mainModuleUpgradable\":\"0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C\",\"version\":3,\"walletCreationCode\":\"0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3\"}}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"wallet\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb339c615a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:25 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=348", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kt/gjX4CVlM2ee69NnlYVC8osIhfZGiPqYCvAJSX1XIV", - "x-77-nzt-ray": "331b5e0fe1a2ac959cd7df69011f0f2b", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjE3LCItVGVjZW5JSUNIclN5ckJPMXd2VDlReTk4bXFTWms4VnZ0NkN0X0tJYlhNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"-TecenIICHrSyrBO1wvT9Qy98mqSZk8Vvt6Ct_KIbXM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"wallet\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Deploy-Config\",\"value\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"},{\"name\":\"Sequence-Sessions-Deploy-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Deploy-Config-Attached\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Deploy-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Deploy-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-Deploy-Signers-Bloom\",\"value\":\"0x0000008008000000802084048200000180000021400280141002000500020421\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-Context-Factory\",\"value\":\"0x00000000000018A77519fcCCa060c2537c9D6d3F\"},{\"name\":\"Sequence-Sessions-Context-Stage-1\",\"value\":\"0x0000000000001f3C39d61698ab21131a12134454\"},{\"name\":\"Sequence-Sessions-Context-Stage-2\",\"value\":\"0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C\"},{\"name\":\"Sequence-Sessions-Context-Guest\",\"value\":\"0x0000000000006Ac72ed1d192fa28f0058D3F8806\"},{\"name\":\"Sequence-Sessions-Context-Creation-Code\",\"value\":\"0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/-TecenIICHrSyrBO1wvT9Qy98mqSZk8Vvt6Ct_KIbXM", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:SnWk69XgOgnlTPEDUhZ6TNgWTJCmBUiMvvNY/xaSOxQ=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:25 GMT", - "sequence-sessions-context-creation-code": "0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3", - "sequence-sessions-context-factory": "0x00000000000018A77519fcCCa060c2537c9D6d3F", - "sequence-sessions-context-guest": "0x0000000000006Ac72ed1d192fa28f0058D3F8806", - "sequence-sessions-context-stage-1": "0x0000000000001f3C39d61698ab21131a12134454", - "sequence-sessions-context-stage-2": "0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C", - "sequence-sessions-deploy-config": "0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78", - "sequence-sessions-deploy-config-attached": "true", - "sequence-sessions-deploy-config-complete": "true", - "sequence-sessions-deploy-signers-bloom": "0x0000008008000000802084048200000180000021400280141002000500020421", - "sequence-sessions-deploy-signers-count": "7", - "sequence-sessions-deploy-version": "3", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-type": "wallet", - "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", - "server": "CDN77-Turbo", - "signature": "comm--teceniichrsyrbo1wvt9qy98mqszk8vvt6ct_kibxm=:pxoQyhQGtlTMj25OLPFlmoFQD6+qgXzw9Mn/hhq2LUoqwg0JXwvxnnPPnZKa+ftI12jcxWydJUK0G6qYmqNqWxs=:, comm-qxsthx4mhrdeoltmaxgbf66wpeqlm_i5t_3mo9bfuau=:ZwH97MHIMAuwUTjyRuTtrkUiomCQM5+fS3Fj5B4XUZs=:", - "signature-input": "comm--teceniichrsyrbo1wvt9qy98mqszk8vvt6ct_kibxm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-context-creation-code\" \"sequence-sessions-context-factory\" \"sequence-sessions-context-guest\" \"sequence-sessions-context-stage-1\" \"sequence-sessions-context-stage-2\" \"sequence-sessions-deploy-config\" \"sequence-sessions-deploy-config-attached\" \"sequence-sessions-deploy-config-complete\" \"sequence-sessions-deploy-signers-bloom\" \"sequence-sessions-deploy-signers-count\" \"sequence-sessions-deploy-version\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-type\" \"sequence-sessions-wallet\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:d2FsbGV0, 10:Sequence-Sessions-Deploy-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMDAwODAyMDg0MDQ4MjAwMDAwMTgwMDAwMDIxNDAwMjgwMTQxMDAyMDAwNTAwMDIwNDIx, 11:Sequence-Sessions-Major-Version:Mg, 12:Sequence-Sessions-Context-Factory:MHgwMDAwMDAwMDAwMDAxOEE3NzUxOWZjQ0NhMDYwYzI1MzdjOUQ2ZDNG, 13:Sequence-Sessions-Context-Stage-1:MHgwMDAwMDAwMDAwMDAxZjNDMzlkNjE2OThhYjIxMTMxYTEyMTM0NDU0, 14:Sequence-Sessions-Context-Stage-2:MHhEMGFlOGVGOTNiN0RBNGVhYmIzMkVjNGQ4MWI3YTUwMURDYTA0RDRD, 15:Sequence-Sessions-Context-Guest:MHgwMDAwMDAwMDAwMDA2QWM3MmVkMWQxOTJmYTI4ZjAwNThEM0Y4ODA2, 16:Sequence-Sessions-Context-Creation-Code:MHg2MDQxNjAwZTNkMzk2MDIxODA1MTMwNTUzZGYzM2QzZDM2MTUzNDAyNjAxZjU3MzYzZDNkMzczZDM2M2QzMDU0NWFmNDNkODI4MDNlOTAzZDkxNjAxZjU3ZmQ1YmYz, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:YXBwbGljYXRpb24vanNvbg, 4:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 5:Sequence-Sessions-Deploy-Config:MHhmZDMyZTAxZDdlODE0MjkyZjQ5ZjU3ZTc5NzIyY2E2NjQyMzgzM2FjZjhmMjVlYmE3NzBmYWYzNDgzZmYzZTc4, 6:Sequence-Sessions-Deploy-Version:Mw, 7:Sequence-Sessions-Deploy-Config-Attached:dHJ1ZQ, 8:Sequence-Sessions-Deploy-Config-Complete:dHJ1ZQ, 9:Sequence-Sessions-Deploy-Signers-Count:Nw\", comm-qxsthx4mhrdeoltmaxgbf66wpeqlm_i5t_3mo9bfuau=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-context-creation-code\" \"sequence-sessions-context-factory\" \"sequence-sessions-context-guest\" \"sequence-sessions-context-stage-1\" \"sequence-sessions-context-stage-2\" \"sequence-sessions-deploy-config\" \"sequence-sessions-deploy-config-attached\" \"sequence-sessions-deploy-config-complete\" \"sequence-sessions-deploy-signers-bloom\" \"sequence-sessions-deploy-signers-count\" \"sequence-sessions-deploy-version\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-type\" \"sequence-sessions-wallet\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kkAzyuQWwUzYj0WpINtvDJmekDtWJOum72Ty4sJb/fWjilgW3w", - "x-77-nzt-ray": "3f9f132567ed7bbc9dd7df69cbb53410", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Wallets", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"signer\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb37c830a2cf-YUL", - "connection": "keep-alive", - "content-length": "772", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:25 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"wallets\":{\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\":{\"digest\":\"\",\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\",\"type\":\"message\"},\"toImageHash\":\"\",\"chainID\":\"0\",\"type\":\"EIP712\",\"signature\":\"0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c\",\"sapientHash\":\"\",\"validOnBlockHash\":\"\"}},\"cursor\":4309735}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb37ef8d5a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:25 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=9,cfOrigin;dur=251", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kn3s8DIJvjUDlJ3OTuNzNuSM4/bbBuAW3a93q34v+ryr", - "x-77-nzt-ray": "331b5e0fe1a2ac959dd7df698b4b2418", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjM1LCJVZ3hGMno4MUlzZDA0b3JWVTRtS1FqSjR1R2hXaWpnSmN3SXd2R0ljUEdNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"signature\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eip-712\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:DQMtV30uXskqMMCu16zNMvZyWfa6yEsZvfOQURm4xFU=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:26 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eip-712", - "sequence-sessions-signer": "0x94835215CaA1aD3E304F9A7E2148623fe661dEB7", - "sequence-sessions-subdigest": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", - "sequence-sessions-type": "signature", - "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=:TP9EhccyKY5tLhKceOrZYXdXXyqjT3DfJwn6iBwTAVk=:, comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=:sR5lQIrliBqj6gDxCHx+VOOKwmvqSnQyKpOgtmI2bfV3ROL1TODu9BYbIgtRCXabSSfY8JufjbK/kIMaFxgG1Rw=:", - "signature-input": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:c2lnbmF0dXJl, 10:Sequence-Sessions-Major-Version:Mg, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZWlwLTcxMg, 5:Sequence-Sessions-Signer:MHg5NDgzNTIxNUNhQTFhRDNFMzA0RjlBN0UyMTQ4NjIzZmU2NjFkRUI3, 6:Sequence-Sessions-Subdigest:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 7:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kkQcVZpruA8xMS3x0uudlfeLetOaOl1vUtIgg4zT7+y7NtcUQw", - "x-77-nzt-ray": "f03d0613ac3d59c59dd7df69ba83d131", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb3bdad15a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:26 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "klJ2qtcwZnoH8Z2gpSNApegfi+abKtYFw8FFk2M1CpNk", - "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df693deb5502", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODczLCJacEMxWDZQM2ttTUo2ZVBLRzhyZjVicEN4bjVzSlVnU2FqRGpTYlJ3ZjQ4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:WrFeEzEBLlRPhslU5kXRAZENf+CRW4DvMJbK+3q4fBw=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:26 GMT", - "sequence-sessions-address": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-payload": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", - "sequence-sessions-payload-type": "message", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=:/XWIuXs2TLz6Uq6WLXmvwx3R0MrP0B8Hg/vL2cSg0tw=:, comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=:FFC2OB/9mHTDSiPM2jaM/W9DndWBvsNcKZGT81tluPk13ODkpIf8XYLaJ90yscVPami5RQscKmBY9OmxfzL3gBs=:", - "signature-input": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 6:Sequence-Sessions-Address:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "klRRFUM66/ntQEiXsPNtmx1THYfl0HFEcIiLg9YiTZPnpccl+Q", - "x-77-nzt-ray": "f03d06133b5c02e39ed7df69da818923", - "x-77-pop": "newyorkUSNY" - }, - "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\"" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Wallets", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"signer\":\"0x000000000000AB36D17eB1150116371520565205\",\"sapientHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb3fad85a2cf-YUL", - "connection": "keep-alive", - "content-length": "25", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:26 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"wallets\":{},\"cursor\":0}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x000000000000AB36D17eB1150116371520565205\\\"] }, { name: \\\"Sequence-Sessions-Image-Hash\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"sapient\\\", \\\"sapient-compact\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb3fce2c5a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:26 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=11,cfOrigin;dur=213", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "khmh6JDIHNcpGLJnUaMbETHny2ujNPAg4nL2VQbfpRJU", - "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df697da8d827", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[]}}}\n" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Witness", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"signer\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb41885ea2cf-YUL", - "connection": "keep-alive", - "content-length": "708", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"witness\":{\"digest\":\"\",\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\",\"type\":\"message\"},\"toImageHash\":\"\",\"chainID\":\"0\",\"type\":\"EIP712\",\"signature\":\"0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c\",\"sapientHash\":\"\",\"validOnBlockHash\":\"\"}}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb41af855a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=9,cfOrigin;dur=244", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kmdP+2wrt6KTzTa7i5NS39F0SWlUh55mmkJYE0vTU5j7", - "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df692d4c8d39", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjM1LCJVZ3hGMno4MUlzZDA0b3JWVTRtS1FqSjR1R2hXaWpnSmN3SXd2R0ljUEdNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"signature\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eip-712\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:DQMtV30uXskqMMCu16zNMvZyWfa6yEsZvfOQURm4xFU=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eip-712", - "sequence-sessions-signer": "0x94835215CaA1aD3E304F9A7E2148623fe661dEB7", - "sequence-sessions-subdigest": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", - "sequence-sessions-type": "signature", - "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=:TP9EhccyKY5tLhKceOrZYXdXXyqjT3DfJwn6iBwTAVk=:, comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=:sR5lQIrliBqj6gDxCHx+VOOKwmvqSnQyKpOgtmI2bfV3ROL1TODu9BYbIgtRCXabSSfY8JufjbK/kIMaFxgG1Rw=:", - "signature-input": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:c2lnbmF0dXJl, 10:Sequence-Sessions-Major-Version:Mg, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZWlwLTcxMg, 5:Sequence-Sessions-Signer:MHg5NDgzNTIxNUNhQTFhRDNFMzA0RjlBN0UyMTQ4NjIzZmU2NjFkRUI3, 6:Sequence-Sessions-Subdigest:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 7:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-age": "1", - "x-77-cache": "HIT", - "x-77-nzt": "ktcsB2vBzPXSPSGPD1IQ68FPpsz0JveQuLOk4aaBYBoRC7gHow", - "x-77-nzt-ray": "f03d0613ac3d59c59fd7df692252fe12", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb3bdad15a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kmDFEymCjaCQYBxiS+RUbDG8nDrbjToz2fktrzleS5uG", - "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df691b96cd15", - "x-77-pop": "montrealCAQC" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODczLCJacEMxWDZQM2ttTUo2ZVBLRzhyZjVicEN4bjVzSlVnU2FqRGpTYlJ3ZjQ4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:WrFeEzEBLlRPhslU5kXRAZENf+CRW4DvMJbK+3q4fBw=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "sequence-sessions-address": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-payload": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", - "sequence-sessions-payload-type": "message", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=:/XWIuXs2TLz6Uq6WLXmvwx3R0MrP0B8Hg/vL2cSg0tw=:, comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=:FFC2OB/9mHTDSiPM2jaM/W9DndWBvsNcKZGT81tluPk13ODkpIf8XYLaJ90yscVPami5RQscKmBY9OmxfzL3gBs=:", - "signature-input": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 6:Sequence-Sessions-Address:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-age": "1", - "x-77-cache": "HIT", - "x-77-nzt": "kto/Tx3txZyfUHYUjDdHYYatpm/yIhRWCo6zu8XkeGRdMPcm8g", - "x-77-nzt-ray": "f03d06133b5c02e39fd7df691d7c821b", - "x-77-pop": "newyorkUSNY" - }, - "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\"" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Witness", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"signer\":\"0x000000000000AB36D17eB1150116371520565205\",\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\",\"sapientHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" - }, - "response": { - "status": 404, - "statusText": "Not Found", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb44cde6a2cf-YUL", - "connection": "keep-alive", - "content-length": "527", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"error\":\"NotFound\",\"code\":2,\"msg\":\"not found\",\"cause\":\"unable to find witness for sapient signer 0x000000000000ab36d17eb1150116371520565205 image hash 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3 wallet 0x47e0e44de649b35cf7863998be6c5a7d5d8c63be: unable to find witness for sapient signer 0x000000000000ab36d17eb1150116371520565205 image hash 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3 wallet 0x47e0e44de649b35cf7863998be6c5a7d5d8c63be: pgkit: no rows in result set\",\"status\":404}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x000000000000AB36D17eB1150116371520565205\\\"] }, { name: \\\"Sequence-Sessions-Image-Hash\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"sapient\\\", \\\"sapient-compact\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb44fa375a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=193", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "krTnhHzpB0qpko17WyAtyDvAyvrzk2wWisOQst+mtd9e", - "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df69277a2f1d", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[]}}}\n" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/ConfigUpdates", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"wallet\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\",\"fromImageHash\":\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb4688b6a2cf-YUL", - "connection": "keep-alive", - "content-length": "1715", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:27 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"updates\":[{\"toImageHash\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\",\"signature\":\"0x0600017126c7b33eb2463c323d6c12e14b128c935a28d41abfa9a200c12a2c622992ee81e16f14850f8ff490fc850c4740543eb0b96ab785eec461f669fe1261001df1b1\"},{\"toImageHash\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\",\"signature\":\"0x0601027172830470d9a9a8f28d3f7554dae9806e14f34f75ffdc8cdff62f691566ff3bb3884ff61967f0180f0c7cb56c860a97cad1c810894becfae50768e1425486d3f371116bea5f536c7745117315baef9743ed6e9bd0bb4d6b0e0dcba7fffdab393a2a50ae10f526151f937cdf8fe67f6b2199e6127e4637017bc056dd7f08d959e0981188a25af00c5590a0583c7f4d994ec87e5cde0ba2\"},{\"toImageHash\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\",\"signature\":\"0x060502113114b2ede71533c3100842b62e3773e930989a1e71d74d840a07de51b5012bc94fcf3a7715bfa1b7845b2674874a71bef4a7f3cd269eb75136e3eadfe33931302452d1cccca8819e3f0615fb36ba71aaeb8465b70571ecd8bcb73872ad91461bba93b42b363b7048d61bc29c813ef890ae5904cb5a0bbad5796934d1c54bac5ee162dbe9acf2ad16cb34e656b4e6c3cc1880c151cf1b\"},{\"toImageHash\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\",\"signature\":\"0x06060171e375fd12b814b897838f13daf9e36bd9d66c78d606580b7822c1364ae2c56ca2b87ca29afec44adba341c80b4a0b45c072087043cd123ad084c7d57ba6b4d0721107a87dc7616f121951e81a997d32ed03c38566071149fca172d48f43f8e8e45685c1581d9ab14f9fae\"},{\"toImageHash\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\",\"signature\":\"0x0608011143dc62e7f055b75948ba6d0288e233027f08f4df71af1656da7ca89e388d1c500826c5d7dd0ee670f68b83615b7e1aac78b0cae4c19cdab9e9c4af8fe5564aa49b0c50fad1218023dc6e3fa87307e29b23952a757011004702aad30e9fcb3af53a635f86c4a4f0dff2ef\"}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb46ab715a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:28 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=281", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kof1ezmpOdlXDn9T8faYG8D1IuPZzf6IiLyidLSfg7Kc", - "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df69317af82d", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJTOVNHYmpmbVdKVmxRSmxqbjZXOXBWZmthSTR4R2pkdmJaX2NzVmo1ZWlnIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"S9SGbjfmWJVlQJljn6W9pVfkaI4xGjdvbZ_csVj5eig\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000000000000020001000000000000000000000408000000000000000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/S9SGbjfmWJVlQJljn6W9pVfkaI4xGjdvbZ_csVj5eig", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:Gv6Njbd+6bmZJQS19Kpl/N2I/J9u5zxx0RWSNateHh8=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:28 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000000000000000020001000000000000000000000408000000000000000000", - "sequence-sessions-signers-count": "1", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-5q2kghl8cf8zhsqsfgox1fau_q0tyzru55pwlfnbi0c=:FDu+idLtiySYuW38oIhAvf6uLQ2fMB96MlBgdXz4mtc=:, comm-s9sgbjfmwjvlqjljn6w9pvfkai4xgjdvbz_csvj5eig=:n/6B3uOAtJhxMyQAtcAGLUtoFUen3JQ8JdOYJ5z+CKBw3k3rqZJFt5KUzi53pCISKhx1I5zmYNNRyhHhSYBfAxs=:", - "signature-input": "comm-5q2kghl8cf8zhsqsfgox1fau_q0tyzru55pwlfnbi0c=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-s9sgbjfmwjvlqjljn6w9pvfkai4xgjdvbz_csvj5eig=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhYTE0YWZmOTEwOTFlOTRkNzUyMTYyNWFiMWM3MTMyNzNlODZhOGMyMWEwYWZiNmNlZTM1YmUyOGFmNDc3Mzhh, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:MQ, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDIwMDAxMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDA4MDAwMDAwMDAwMDAwMDAwMDAw\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kiDzhOXXjZ+sIg4zjsnuzEoNTgPhUMI3GY9/WQVHZTb/t3PYgA", - "x-77-nzt-ray": "f03d0613653cfd25a0d7df69fbb68c0d", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"0\",\"threshold\":1,\"tree\":{\"address\":\"0x1494927eC78E099321151C2b9d06c5bB98a21191\",\"weight\":1}}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x1494927eC78E099321151C2b9d06c5bB98a21191\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb4a6e725a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:28 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=175", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "knD6rYB3F2GDXZE2xiF+pjLj1xwpUGaRJTOITX3anJAL", - "x-77-nzt-ray": "331b5e0fe1a2ac95a0d7df6920e69515", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJpenQ4V3lWNDF0SkZiS1Yzdlo3cUxFLW54M05lMXVER0gycUF2SUxiUjhRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"izt8WyV41tJFbKV3vZ7qLE-nx3Ne1uDGH2qAvILbR8Q\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x1494927eC78E099321151C2b9d06c5bB98a21191\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xc68f343dd513b86c83a47c20218eb39d5dc7f2cfadd435c4253e268f7b7f4016\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004500400000000800100000000000001080000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/izt8WyV41tJFbKV3vZ7qLE-nx3Ne1uDGH2qAvILbR8Q", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:5V17yO++KQTYC6pt/sLS3ygLP6/h0QJnF+9e2FHFQPY=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:28 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0x1494927eC78E099321151C2b9d06c5bB98a21191", - "sequence-sessions-subdigest": "0xc68f343dd513b86c83a47c20218eb39d5dc7f2cfadd435c4253e268f7b7f4016", - "sequence-sessions-to-checkpoint": "1", - "sequence-sessions-to-config": "0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000502000000100000004500400000000800100000000000001080000000", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-izt8wyv41tjfbkv3vz7qle-nx3ne1udgh2qavilbr8q=:Rtry+88OHbpQW31vhv2P+YQaydNrVPtgldw2BJqd8qsDwDsjM7vcO4Yj54LiPMkuVn9nOGv4lqfQ5ODsfhROnRs=:, comm-l89rjriyuff5qj8_r92qjquo7xjqgirvu0_lu1b29xq=:KhFweURuUOR33tKJjhlPjpOO9f8kl7fPjyLMabPjwOE=:", - "signature-input": "comm-izt8wyv41tjfbkv3vz7qle-nx3ne1udgh2qavilbr8q=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhNDExZjhiZjA1ODM5MDcxNzYxYTNjZmVjZTJhYzNlY2UyYTU5YTI1N2Q5ODlkOTE1MDY4ZDVmN2RkYWFiMjk0, 12:Sequence-Sessions-To-Checkpoint:MQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMDAwMTAwMDAwMDA0NTAwNDAwMDAwMDAwODAwMTAwMDAwMDAwMDAwMDAxMDgwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgxNDk0OTI3ZUM3OEUwOTkzMjExNTFDMmI5ZDA2YzViQjk4YTIxMTkx, 6:Sequence-Sessions-Subdigest:MHhjNjhmMzQzZGQ1MTNiODZjODNhNDdjMjAyMThlYjM5ZDVkYzdmMmNmYWRkNDM1YzQyNTNlMjY4ZjdiN2Y0MDE2, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\", comm-l89rjriyuff5qj8_r92qjquo7xjqgirvu0_lu1b29xq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kmaEcGv4HK6lYliBsjNRILzxFuSArvL/LGTD71iIv9gLs+oxXQ", - "x-77-nzt-ray": "f03d0613bf5c6c3ba0d7df69f68c742b", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x26c7b33eb2463c323d6c12e14b128c935a28d41abfa9a200c12a2c622992ee81616f14850f8ff490fc850c4740543eb0b96ab785eec461f669fe1261001df1b11c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb4d18905a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=185", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kjlqZaVBXeQLSEYmXiAVLejJodr3HMepIYXYP3x9MM84", - "x-77-nzt-ray": "331b5e0fe1a2ac95a0d7df69c9e72d2f", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJOWjJIWWNGZDAtOThVZExudWJLc2N1S09valJ1dGlnX1RwcC1sMkgtSWtFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"NZ2HYcFd0-98UdLnubKscuKOojRutig_Tpp-l2H-IkE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000502000000100000004500400000000800100000000000001080000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/NZ2HYcFd0-98UdLnubKscuKOojRutig_Tpp-l2H-IkE", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:SV08eWGigUB+aigp46hMh2a0MgdrwYXYXNl1hV8xqBc=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000000502000000100000004500400000000800100000000000001080000000", - "sequence-sessions-signers-count": "3", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-hkjz12krcwpdpscafdqy2i1mtx_crmzwrygba7ev28i=:F016DF5NiP4rsrw3xFweRK7a6ufEShE27XbmIZ0mM0U=:, comm-nz2hycfd0-98udlnubkscukoojrutig_tpp-l2h-ike=:0szAi+qf/tbBpzdEDAb6YmZaWpdGZxgz8EwfVWItkCMqC1OCCco2um+SGlxB4upmVWuhuqvmPrd3n/r3FbIMqxs=:", - "signature-input": "comm-hkjz12krcwpdpscafdqy2i1mtx_crmzwrygba7ev28i=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-nz2hycfd0-98udlnubkscukoojrutig_tpp-l2h-ike=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhNDExZjhiZjA1ODM5MDcxNzYxYTNjZmVjZTJhYzNlY2UyYTU5YTI1N2Q5ODlkOTE1MDY4ZDVmN2RkYWFiMjk0, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMDAwMTAwMDAwMDA0NTAwNDAwMDAwMDAwODAwMTAwMDAwMDAwMDAwMDAxMDgwMDAwMDAw\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "khsoikltqnrtzOqDWKW2HYe5dYh3rJZRF4vNeKiJmIIu4/HoPA", - "x-77-nzt-ray": "3f9f1325610a726ea1d7df69ec8dcd08", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"1\",\"threshold\":2,\"tree\":[[{\"address\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\",\"weight\":1},{\"address\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\",\"weight\":1}],{\"address\":\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\",\"weight\":1}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x3114b2Ede71533C3100842B62e3773e930989A1E\\\", \\\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\\\", \\\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb4fbaa55a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=220", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "koCOMWdJBLCcnc0fzZurVvjw4i3Nn0rg5nRirg2TFixh", - "x-77-nzt-ray": "331b5e0fe1a2ac95a1d7df6936d7fc0c", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCI0VjZoTjI4M29XWW9WTFhwRGE5MGxIeTUtNEN0VmZFTlMzMjB4eXJobWxjIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"4V6hN283oWYoVLXpDa90lHy5-4CtVfENS320xyrhmlc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJEX0huLUZETFJIeThORjFWN1BGOTI1dFlEZ3paSTVYWng2NFdDckhUd1ZzIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"D_Hn-FDLRHy8NF1V7PF925tYDgzZI5XZx64WCrHTwVs\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJGejRFQmo3V0xmVXEyVlZzRzJ0emxWb0ttODZLbU9YRGJVRi1qNm1kOWpzIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJfRWNTa3dxT3BSQTdRWjlGUndfSjRHVlQxVDdIYmVvelJ1RVU1UnRTTnlJIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJlMXhjRE1scnJMNTVBZ2ZVWDZ1MkdzNGRlcm1KeDZ0SnQtTjhWdnNmQ2ZNIl0sImluZGV4Ijo0fQ==\",\"node\":{\"id\":\"e1xcDMlrrL55AgfUX6u2Gs4dermJx6tJt-N8VvsfCfM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x406a8fb3dee0ed7a025cc2bab7e1ac829b489b2e8692fea8f694bd3719b88eb5\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd0dedc8b8e2407f4ca09d86b381a8da08e7e8fb551d5699f56a4daa6273bf13d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x1000000502000000100000004000400000042800100000000000000000020000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJsN1ZwTWJ3MV9nNEpNOU9XWHZYcHFneDlseVhlbGdVUldMVEQ0YVhKSmxvIl0sImluZGV4Ijo1fQ==\",\"node\":{\"id\":\"l7VpMbw1_g4JM9OWXvXpqgx9lyXelgURWLTD4aXJJlo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x568723a41b651b52c87c0a9769243e76e02a6ebdfcf4ff89d551429deac205c2\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe0d9a803e3dbd676c10cea040e6501b80d011a7acf3c6312d0aad291326862c5\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400080000800100000000000020000000100\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ3NU54RFJ2a0VCaTJWeG5KZ2p6NS02d2RXUEF2X3ZDUjRoWkwtV0RIaHNNIl0sImluZGV4Ijo2fQ==\",\"node\":{\"id\":\"w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:gl4mTPi1DdVGbow10VFIzL14OgN2Y35fOYI2KgBElAU=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548", - "sequence-sessions-subdigest": "0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3", - "sequence-sessions-to-checkpoint": "6", - "sequence-sessions-to-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "false", - "server": "CDN77-Turbo", - "signature": "comm-dkuigffhmyx2wop753phvp2o56fbn2efe-i-orzwsre=:NRXf0YANg7WO0IyBzvdHidfMGeMiGG1dCE2VWRMnn2U=:, comm-fz4ebj7wlfuq2vvsg2tzlvokm86kmoxdbuf-j6md9js=:PIRkvIK56BiA3gNYpQ3Y6Iqqh7iwqYFutzjp5RF8sy1nr+/VlYKTI5npT/LFyyBv13laNRFXgAV2mCc9uzrH9Rw=:", - "signature-input": "comm-dkuigffhmyx2wop753phvp2o56fbn2efe-i-orzwsre=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-fz4ebj7wlfuq2vvsg2tzlvokm86kmoxdbuf-j6md9js=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 12:Sequence-Sessions-To-Checkpoint:Ng, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhiMjBFOWZmMzFBNzUxOTBCNUFhMjVmRmZhZTY0NkFGNzgyMUQ0NTQ4, 6:Sequence-Sessions-Subdigest:MHg3YmFiNWQwZjRmM2Y0MDg3NzM2YjlkYzg1MDAyMDJlNzNlY2U3ZWViNjQ1YWJkZDRlZTMyNTJmZDJkYjZmNmMz, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "ktuYrhp0DCHMjpvuQl6ghF489ixAO0MeTI6HIQ6uRTrrdrCatQ", - "x-77-nzt-ray": "8705ec34b6d394d7a1d7df693595b823", - "x-77-pop": "newyorkUSNY" - }, - "body": "0xd74d840a07de51b5012bc94fcf3a7715bfa1b7845b2674874a71bef4a7f3cd261eb75136e3eadfe33931302452d1cccca8819e3f0615fb36ba71aaeb8465b7051c" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:VBsXK1M8uZVlxPKBBimHHRZfzcKY+OP9ht9pd1e8OYY=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548", - "sequence-sessions-subdigest": "0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0", - "sequence-sessions-to-checkpoint": "5", - "sequence-sessions-to-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-_ecskwqopra7qz9frw_j4gvt1t7hbeozrueu5rtsnyi=:mSJ7Wfbo2CDWKJ4Tc2PdmncNyLqy1ejvND6aZGdduvsmhVqizKMuM4rAWyFb38VxYD5NmaBz584iSsTvEzsHZRs=:, comm-sqp56wurfwh57gj_yhzmnwcwrflfaznkcus5vu0is28=:iX5RbPW4qPV3vnOamVp087pHbC3RE4Ux1daDnGuT6W0=:", - "signature-input": "comm-_ecskwqopra7qz9frw_j4gvt1t7hbeozrueu5rtsnyi=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 12:Sequence-Sessions-To-Checkpoint:NQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhiMjBFOWZmMzFBNzUxOTBCNUFhMjVmRmZhZTY0NkFGNzgyMUQ0NTQ4, 6:Sequence-Sessions-Subdigest:MHhlODQyZDlmZWE4MDhhMjljNzdlNTNlMDZlNWNjNzhlMTIyMzc0YWI1YjBjMjQ5NGU4YTBiYWFjZDFiNDhiNmEw, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\", comm-sqp56wurfwh57gj_yhzmnwcwrflfaznkcus5vu0is28=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kjf1MaCVro4tzO8Y+rtRj2DW+I0WgSG8mxMPrsRr+jU5xYziGg", - "x-77-nzt-ray": "3f9f13256fea358aa1d7df69c155482d", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x116bea5f536c7745117315baef9743ed6e9bd0bb4d6b0e0dcba7fffdab393a2a50ae10f526151f937cdf8fe67f6b2199e6127e4637017bc056dd7f08d959e0981b" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:2ZuMVXKr8u5m20NWLWAyvvaN1rrF5LETVZPHv5u2sIw=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:29 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0x3114b2Ede71533C3100842B62e3773e930989A1E", - "sequence-sessions-subdigest": "0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0", - "sequence-sessions-to-checkpoint": "5", - "sequence-sessions-to-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "false", - "server": "CDN77-Turbo", - "signature": "comm-1lo1olkgrwhm3l5m_kzswkx4op49pdu_cddhrgaemdk=:DkOaOPc1X07fP3Pm80/Ic3FVqDNdqR6GoLK8eO0zwqA=:, comm-w5nxdrvkebi2vxnjgjz5-6wdwpav_vcr4hzl-wdhhsm=:lP/eLayDbnw09wD2+CDqLmugY5DxoBNt5A64ljWv+wAKZNuM6Qy9UJCE4OdLW58tpetZenD2nUBdfl3duCs0oRs=:", - "signature-input": "comm-1lo1olkgrwhm3l5m_kzswkx4op49pdu_cddhrgaemdk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-w5nxdrvkebi2vxnjgjz5-6wdwpav_vcr4hzl-wdhhsm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 12:Sequence-Sessions-To-Checkpoint:NQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgzMTE0YjJFZGU3MTUzM0MzMTAwODQyQjYyZTM3NzNlOTMwOTg5QTFF, 6:Sequence-Sessions-Subdigest:MHhlODQyZDlmZWE4MDhhMjljNzdlNTNlMDZlNWNjNzhlMTIyMzc0YWI1YjBjMjQ5NGU4YTBiYWFjZDFiNDhiNmEw, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kvh3BkK8TrK1nTICmKayfCCRR1hM+1BYVjHoi6xwQVy0WElnJA", - "x-77-nzt-ray": "8705ec341a2987e1a1d7df69ca29e131", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x72830470d9a9a8f28d3f7554dae9806e14f34f75ffdc8cdff62f691566ff3bb3084ff61967f0180f0c7cb56c860a97cad1c810894becfae50768e1425486d3f31c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb542e345a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:30 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=207", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "khIGCL18wor0fRfALbLVb+Dna3rNs5tWWmLEe9up1Eoj", - "x-77-nzt-ray": "331b5e0fe1a2ac95a1d7df6975d36337", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJPNlFyTWV3VDhhWkpNQ0djNjBCSjlxQ3d1QTI5ME5RaUYwTnBreEhDNVk4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"O6QrMewT8aZJMCGc60BJ9qCwuA290NQiF0NpkxHC5Y8\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/O6QrMewT8aZJMCGc60BJ9qCwuA290NQiF0NpkxHC5Y8", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:lOaTY4n1aPuJ6aIVdvkExjh8xxlEMMzle3itHhyVcqQ=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:30 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", - "sequence-sessions-signers-count": "3", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-eq9xeugrr-8aivbdkcqvhhnrput45awkg5fu2w7n2bs=:bf/sXvC3hyBe+2Svwtz166VEvDFznyNqUtUiphysPzI=:, comm-o6qrmewt8azjmcgc60bj9qcwua290nqif0npkxhc5y8=:76kyiO7vdyBjhrDD7pMGU8GLU6xxEWc7E0sTjzPPVbZJWnDBWvIZs84oaBEjgl+WU2EkL41oyLGiM2f2QfFEDBw=:", - "signature-input": "comm-eq9xeugrr-8aivbdkcqvhhnrput45awkg5fu2w7n2bs=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-o6qrmewt8azjmcgc60bj9qcwua290nqif0npkxhc5y8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "ks4H+fRU5lxCmKBu64MCm0ROK5j1onKuklVRaCW11n86PbgNyg", - "x-77-nzt-ray": "f03d06130872167ba2d7df6903120c12", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"5\",\"threshold\":2,\"tree\":[[{\"address\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\",\"weight\":1},{\"address\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\",\"weight\":1}],{\"address\":\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\",\"weight\":1}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x3114b2Ede71533C3100842B62e3773e930989A1E\\\", \\\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\\\", \\\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb5708f35a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:30 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=226", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kjXn2kmQkyX8246oMcLumt2q+Ue5i54AU4j/EPpLph+d", - "x-77-nzt-ray": "331b5e0fe1a2ac95a2d7df6903837317", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJEX0huLUZETFJIeThORjFWN1BGOTI1dFlEZ3paSTVYWng2NFdDckhUd1ZzIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"D_Hn-FDLRHy8NF1V7PF925tYDgzZI5XZx64WCrHTwVs\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJGejRFQmo3V0xmVXEyVlZzRzJ0emxWb0ttODZLbU9YRGJVRi1qNm1kOWpzIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJSSnlvbnFmZGtfeDU2a0JZVjhvdDNxYWRFa1BQcE5JSlBjVDNJcTR0U3lFIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"RJyonqfdk_x56kBYV8ot3qadEkPPpNIJPcT3Iq4tSyE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJfRWNTa3dxT3BSQTdRWjlGUndfSjRHVlQxVDdIYmVvelJ1RVU1UnRTTnlJIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJlMXhjRE1scnJMNTVBZ2ZVWDZ1MkdzNGRlcm1KeDZ0SnQtTjhWdnNmQ2ZNIl0sImluZGV4Ijo0fQ==\",\"node\":{\"id\":\"e1xcDMlrrL55AgfUX6u2Gs4dermJx6tJt-N8VvsfCfM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x406a8fb3dee0ed7a025cc2bab7e1ac829b489b2e8692fea8f694bd3719b88eb5\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd0dedc8b8e2407f4ca09d86b381a8da08e7e8fb551d5699f56a4daa6273bf13d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x1000000502000000100000004000400000042800100000000000000000020000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJsN1ZwTWJ3MV9nNEpNOU9XWHZYcHFneDlseVhlbGdVUldMVEQ0YVhKSmxvIl0sImluZGV4Ijo1fQ==\",\"node\":{\"id\":\"l7VpMbw1_g4JM9OWXvXpqgx9lyXelgURWLTD4aXJJlo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x568723a41b651b52c87c0a9769243e76e02a6ebdfcf4ff89d551429deac205c2\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe0d9a803e3dbd676c10cea040e6501b80d011a7acf3c6312d0aad291326862c5\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400080000800100000000000020000000100\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ3NU54RFJ2a0VCaTJWeG5KZ2p6NS02d2RXUEF2X3ZDUjRoWkwtV0RIaHNNIl0sImluZGV4Ijo2fQ==\",\"node\":{\"id\":\"w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/RJyonqfdk_x56kBYV8ot3qadEkPPpNIJPcT3Iq4tSyE", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:PEukna0+GD2hJOvo/DIg/ImTk2ZUTgAFkHUxpkl/mMk=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:30 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4", - "sequence-sessions-subdigest": "0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3", - "sequence-sessions-to-checkpoint": "6", - "sequence-sessions-to-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-fsvleswb_4wzsytpfbpcqjcck-uidvwsfoyzyhistd8=:4fue3tEjvSRint0i8SzqRVqnTcIuPVaFvXmjdXoAp/E=:, comm-rjyonqfdk_x56kbyv8ot3qadekpppnijpct3iq4tsye=:xybXj2uxTejbwNwuYv6CMlz07SZCHDc8v5Vo6zhGgm8bckzYbIdpas31XHDzBB7w4tjo9cyzUzt3ez3JFLBQQRs=:", - "signature-input": "comm-fsvleswb_4wzsytpfbpcqjcck-uidvwsfoyzyhistd8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-rjyonqfdk_x56kbyv8ot3qadekpppnijpct3iq4tsye=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 12:Sequence-Sessions-To-Checkpoint:Ng, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhEYmJGMDU5MEEyOWMyMDNEMTVkYmM0NjJDRTJjZUY0M0M4MjEwZkI0, 6:Sequence-Sessions-Subdigest:MHg3YmFiNWQwZjRmM2Y0MDg3NzM2YjlkYzg1MDAyMDJlNzNlY2U3ZWViNjQ1YWJkZDRlZTMyNTJmZDJkYjZmNmMz, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kspqVXqy0lxAq0Exzh68cxy5isFudoM5KH6a2vuaSHHeX+netw", - "x-77-nzt-ray": "8705ec347811ca07a2d7df691ca6912f", - "x-77-pop": "newyorkUSNY" - }, - "body": "0xecd8bcb73872ad91461bba93b42b363b7048d61bc29c813ef890ae5904cb5a0b3ad5796934d1c54bac5ee162dbe9acf2ad16cb34e656b4e6c3cc1880c151cf1b1c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb5a2bb75a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=5,cfOrigin;dur=185", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "ko5mQ8om74otrj47od4kQf7P4MGlI39VvitW/gEGOscM", - "x-77-nzt-ray": "331b5e0fe1a2ac95a2d7df690ddf0835", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCItQjRuenQ5blBYOGYtUVFGSlZVVXkzNlctamxrcjFSR1F3S3UxcjZ3WFowIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"-B4nzt9nPX8f-QQFJVUUy36W-jlkr1RGQwKu1r6wXZ0\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/-B4nzt9nPX8f-QQFJVUUy36W-jlkr1RGQwKu1r6wXZ0", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:ksByouZ0PZUghZHHFzg2DenqCafpnya2RRPuydP3cGk=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", - "sequence-sessions-signers-count": "3", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm--b4nzt9npx8f-qqfjvuuy36w-jlkr1rgqwku1r6wxz0=:tzZO2MoXIqZXnZvr1LXRRtCxCUQrFr5WWrl1Cnek9EM1owGM3nJ2cFoNDwamyCuLRCuyb3PbuMtadAARRgwvZxw=:, comm-7u-kon49dtsoauudnzs8fdrc6yo-vilu7sanv6dwqp4=:kYXPoiF9pt7xSqMyHiBPnetFHt4iwZJR9edbkLvVhtM=:", - "signature-input": "comm--b4nzt9npx8f-qqfjvuuy36w-jlkr1rgqwku1r6wxz0=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw\", comm-7u-kon49dtsoauudnzs8fdrc6yo-vilu7sanv6dwqp4=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "ksmw8yXPXCv/QiVuFuJA0fM39DGJpQOz6gS2rTgGTcOlG2IHSw", - "x-77-nzt-ray": "3f9f13251bfbd5cea3d7df69ce140a0f", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"6\",\"threshold\":1,\"tree\":[[{\"address\":\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\",\"weight\":1},{\"address\":\"0x07a87DC7616F121951e81A997D32eD03c3856607\",\"weight\":1}],{\"address\":\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\",\"weight\":1}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\\\", \\\"0x07a87DC7616F121951e81A997D32eD03c3856607\\\", \\\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb5cddd05a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=206", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kqPYwNmgDa7sX+6+d24x+6w0D4GaaBcyrq2OrEckWBl/", - "x-77-nzt-ray": "331b5e0fe1a2ac95a3d7df6939d52113", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJLYzd0MGNBWVZUc0o5b0FiZHF4aXlPaTV1MFpaLUF2TGZ4WXJkZUdKVUJRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"Kc7t0cAYVTsJ9oAbdqxiyOi5u0ZZ-AvLfxYrdeGJUBQ\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"8\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJzM1BWUVpZV2dDN2VJNHJNTTNRN2RaZEEzUWtzYURrV0JiSU5mU21EV0tZIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"s3PVQZYWgC7eI4rMM3Q7dZdA3QksaDkWBbINfSmDWKY\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xbe193096e90de1cbeea21495f3c246168a715f7d34a3bcecc7852019e0784856\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x6a484f49f59ea0d5d9134ac92bf36ccf0f06a15030e07624f576b4294a5fdd7c\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x000800019000000002000002000000000000010020000c010000000001000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ4TTdfN1hQbUNWVUxHNElMYWk0dUZmcEFoU2VQcEN1cjJWQS1hNVNDYmVvIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"xM7_7XPmCVULG4ILai4uFfpAhSePpCur2VA-a5SCbeo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x07a87DC7616F121951e81A997D32eD03c3856607\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"8\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/xM7_7XPmCVULG4ILai4uFfpAhSePpCur2VA-a5SCbeo", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:3CJOMRYcb9BRY+qlR4qHnRoD+o7cCstWySxk6xTFYes=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0x07a87DC7616F121951e81A997D32eD03c3856607", - "sequence-sessions-subdigest": "0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89", - "sequence-sessions-to-checkpoint": "8", - "sequence-sessions-to-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-f7rp8sdytpirgwvoy_bpvzninwx3fe32tw36amj0jxc=:5LC5m46+2oi/xeTp/QMdclEzludnK+ISrSmWazfO6kA=:, comm-xm7_7xpmcvulg4ilai4uffpahseppcur2va-a5scbeo=:BArCNITJliJDJrORialbgBAEjuKKGCM8y1NIJY3EoGY5uo00kzl0BohEUiN0znskgXjuQsm3Cd/msMtsOWsfrRw=:", - "signature-input": "comm-f7rp8sdytpirgwvoy_bpvzninwx3fe32tw36amj0jxc=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-xm7_7xpmcvulg4ilai4uffpahseppcur2va-a5scbeo=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 12:Sequence-Sessions-To-Checkpoint:OA, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgwN2E4N0RDNzYxNkYxMjE5NTFlODFBOTk3RDMyZUQwM2MzODU2NjA3, 6:Sequence-Sessions-Subdigest:MHgxZGViYzkwYzY4ZjIzMjMxMGI0MzRiN2ZjOGE2NzJmYjdhZWJlOTU0MjJhZDQyNjcwN2VjMWQxZTAzZmUwYjg5, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kqNwqjfqmGocmde0Peb+pShGFRd1tnyeV8b+67b+/1QX0h+s7Q", - "x-77-nzt-ray": "3f9f1325f7fba7e2a3d7df698817de28", - "x-77-pop": "newyorkUSNY" - }, - "body": "0x5937db592a0799449fb2442eed3932ce32f2cef81ce8c359cbda05f8288da27f2fc09a0f17005e8c1fbd27ac58b47046c4feebf1278783c12f64fa0ab9ae12441c" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/Kc7t0cAYVTsJ9oAbdqxiyOi5u0ZZ-AvLfxYrdeGJUBQ", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:9d4wu0YAdl5mfAKuEkIXHdXO6uCAQkxgneeV3Zr9+9s=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0x8534Cb5487E769e510c5F71b790B2ca02D1819a0", - "sequence-sessions-subdigest": "0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89", - "sequence-sessions-to-checkpoint": "8", - "sequence-sessions-to-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "true", - "server": "CDN77-Turbo", - "signature": "comm-2vhls37zqgon56ymq8tdapyh9dstpdixgd4yj-ittvu=:7VBwLsBh2Nn/42bwA0auDaeX6MwAD69evUz2q/00XPA=:, comm-kc7t0cayvtsj9oabdqxiyoi5u0zz-avlfxyrdegjubq=:4hG3iejxTU/Pph3BhyXkYkIT+WdnA2+ew9pW/8gFD6ZRK+/cieu2hIpQYFY0nmFneJqNkpK5a7yvEjsbXzMdQBw=:", - "signature-input": "comm-2vhls37zqgon56ymq8tdapyh9dstpdixgd4yj-ittvu=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-kc7t0cayvtsj9oabdqxiyoi5u0zz-avlfxyrdegjubq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 12:Sequence-Sessions-To-Checkpoint:OA, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHg4NTM0Q2I1NDg3RTc2OWU1MTBjNUY3MWI3OTBCMmNhMDJEMTgxOWEw, 6:Sequence-Sessions-Subdigest:MHgxZGViYzkwYzY4ZjIzMjMxMGI0MzRiN2ZjOGE2NzJmYjdhZWJlOTU0MjJhZDQyNjcwN2VjMWQxZTAzZmUwYjg5, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "knuK0dh8qHZJ1pa87ng+NmZAC5z178BwWKJB6obyHUru32Zq6A", - "x-77-nzt-ray": "3f9f1325f7fb6de3a3d7df69a61c8a2a", - "x-77-pop": "newyorkUSNY" - }, - "body": "0xe375fd12b814b897838f13daf9e36bd9d66c78d606580b7822c1364ae2c56ca2387ca29afec44adba341c80b4a0b45c072087043cd123ad084c7d57ba6b4d0721c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb5fd84a5a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:31 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=150", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kuNvMAojgQ/yrO7l7csjoJWNJKVsJ2PuiCBCpJs68no5", - "x-77-nzt-ray": "331b5e0fe1a2ac95a3d7df69c404cc2f", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIwMzNDeFAxLXplTmRMeTJ4X2dzbGlmRjZ1U2I5SS1EVng5b2RXcGFOYzljIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"033CxP1-zeNdLy2x_gslifF6uSb9I-DVx9odWpaNc9c\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/033CxP1-zeNdLy2x_gslifF6uSb9I-DVx9odWpaNc9c", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:pR1drysB449OQBdWWWfOfHQnU3tlTDtrhl+v9ns8yGk=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:32 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", - "sequence-sessions-signers-count": "3", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-033cxp1-zendly2x_gsliff6usb9i-dvx9odwpanc9c=:jlvjFuDF312zUk/2peb5tOw5rrK43A3JH+LhrpiakgZa4uY4Q/Wlea3PUolzDLIMgCfIywbxyUlmpcAHX8tmLRs=:, comm-4kmu9zanvxaovs_arucd0j5fseiwphsln5kdzm_mpba=:il2HPBkL+GNyvIbm9yeo5XLbdLLCFjcxJEEhEntxLCg=:", - "signature-input": "comm-033cxp1-zendly2x_gsliff6usb9i-dvx9odwpanc9c=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw\", comm-4kmu9zanvxaovs_arucd0j5fseiwphsln5kdzm_mpba=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kqNEoZW5LBBaGsB+Te/VCXhiu7aHH5fEZ+97fC45ic8Ps8iMWA", - "x-77-nzt-ray": "3f9f1325cfd22cf6a4d7df694e8f1f07", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"8\",\"threshold\":1,\"tree\":[[{\"address\":\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\",\"weight\":1},{\"address\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\",\"weight\":1}],{\"address\":\"0x004702AAD30e9fCb3Af53a635f86C4a4F0Dff2eF\",\"weight\":1}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\\\", \\\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\\\", \\\"0x004702AAD30e9fCb3Af53a635f86C4a4F0Dff2eF\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb626a5f5a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:32 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=159", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kpKBhuw0SzPTeV5TrX7UCPHXJhbUgZPL5A6okZgvuZX5", - "x-77-nzt-ray": "331b5e0fe1a2ac95a4d7df69fa19af0c", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIxSmZCQ0w3bnpaSzlxcmZ6OHFPQTRZVjEtR1JmLU1FLWIwenVxY3J3NmRFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"1JfBCL7nzZK9qrfz8qOA4YV1-GRf-ME-b0zuqcrw6dE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x512485d4e46a9758b98e84105d4d54755ce6645bd3ddbab0e850c968a8ef25ba\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x7178a76bb537253e8f37c2c6a400af1a85b7f5314ae9f06bfeb1a93df275fca3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"11\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0040000000000000000000000000000000411100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCI0OGdNdUE5V0NlUlB6RW9FNnFSSWE4RUs4Y3piZHBicENaNGdIRDNUQVhBIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"48gMuA9WCeRPzEoE6qRIa8EK8czbdpbpCZ4gHD3TAXA\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x00d52df963e55d1be9b37ca9af01fc1529e76aa9e4adfc8258b9bfa96883d2cb\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xcde2e7ec78ebea962db5fc8c5bd1124b37b699131db4ccd99653f10e6b463b8c\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"9\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000040000000000410100008100000000003004002090\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJHMHo4V29aYm85allnbzFsa01GbHRLcm9uc2c3em16WmVuUFRiVkZNaldjIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"G0z8WoZbo9jYgo1lkMFltKronsg7zmzZenPTbVFMjWc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xa3c45186be4593770ea3414abbd86c5e10f5791b093372b2f8a9a25482e9f826\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x2bafea1dbc6381b39848d226dccb906f23171edf7661149da26ab609d15a1339\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"10\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000000000400400100408100000001003000082010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJnZGdUclNoR09vUjBzYlFXSVI0M1hEUE5JTmI3THExUFVMUEY4SU9RNy1ZIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"12\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:YvO0UJsvKhf3KcMrD0ijPMAIjZCYoeXF1YboroG7GFo=:", - "content-encoding": "gzip", - "content-type": "text/plain", - "date": "Wed, 15 Apr 2026 18:23:32 GMT", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "2", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signature-type": "eth_sign", - "sequence-sessions-signer": "0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07", - "sequence-sessions-subdigest": "0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b", - "sequence-sessions-to-checkpoint": "12", - "sequence-sessions-to-config": "0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000000120000404000000040000000000000100000000000000312000002010", - "sequence-sessions-to-signers-count": "3", - "sequence-sessions-type": "config update", - "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", - "sequence-sessions-witness": "false", - "server": "CDN77-Turbo", - "signature": "comm-egncy3liqfquug_n0abtquya6qvhh071cvmvptlqg-4=:Dio++Bk3uNbWk+jRSV7664zTj16HKxvid9ULuWTD3tE=:, comm-gdgtrshgoor0sbqwir43xdpninb7lq1pulpf8ioq7-y=:C2XS2s2WWfgKn4IvalZanI1Mc1WCSGalLXxYwblW+45MEQYb6JCo/2s3b8rWFWuxCGbSKOAOl0wPjocnBDXfJBs=:", - "signature-input": "comm-egncy3liqfquug_n0abtquya6qvhh071cvmvptlqg-4=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-gdgtrshgoor0sbqwir43xdpninb7lq1pulpf8ioq7-y=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHgxOWI5NGE3YmQzZGZiZDVhZjUyODY1MGZmNDkyODAyYmZkYjkwOWFmZjcwNjA4Njg0ZmVhY2UyNGYzZTA4YTAx, 12:Sequence-Sessions-To-Checkpoint:MTI, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMTIwMDAwNDA0MDAwMDAwMDQwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMzEyMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhBZGE1MmU3NThkNmU2ZGZmNWU5Q2Y1RTk0NGNGNkQzZjIxMzg3RjA3, 6:Sequence-Sessions-Subdigest:MHg2NDgyNWIwMTRjMTVmMzhhMTQ2NGRhMWE0ZmM5Njk0NTg0YTU5ZjA1MmFlMjA2NWRhYzQ1NzkzNWY0ODgwNTli, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kvVh9MCLFtrHa/I5Tw0vQ/XufyHHGKaudXFCXE9xxdzG326htQ", - "x-77-nzt-ray": "8705ec3425fb5c4fa4d7df697537cf1f", - "x-77-pop": "newyorkUSNY" - }, - "body": "0xaf1656da7ca89e388d1c500826c5d7dd0ee670f68b83615b7e1aac78b0cae4c11cdab9e9c4af8fe5564aa49b0c50fad1218023dc6e3fa87307e29b23952a75701c" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb64ec385a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:33 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=13,cfOrigin;dur=391", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kkdNispgdvnwMIBxRgVePYUfIN5f7YWmXuDUaQLkUQCd", - "x-77-nzt-ray": "331b5e0fe1a2ac95a4d7df697f837524", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJiMzhSTG1QbktvdEU3cEo2SVhZRjgyT19PaEo5WGdRRk96RXB4ZDdHOWE4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"b38RLmPnKotE7pJ6IXYF82O_OhJ9XgQFOzEpxd7G9a8\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/b38RLmPnKotE7pJ6IXYF82O_OhJ9XgQFOzEpxd7G9a8", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:SiVzr20W80RCGWy7sZQeosNKLYQT9bClI+k7XXY7tZ8=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:33 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-config": "0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-signers-bloom": "0x0000000120000404000000040000000000000100000000000000312000002010", - "sequence-sessions-signers-count": "3", - "sequence-sessions-type": "config", - "sequence-sessions-version": "3", - "server": "CDN77-Turbo", - "signature": "comm-b38rlmpnkote7pj6ixyf82o_ohj9xgqfozepxd7g9a8=:VwZrTQ5NGtQYAghYnlBLUUWlY+4VH7Kv77zIbzHYkl4hnHj7BctL/+cwv6sum7MwR3gI8kDF/4N6mX7gZ7Pj9Bw=:, comm-il29hgsyyy8ti2jdr8t-9zseofap4epv1ew-_yjtrxq=:St6HmXse1t1+v1iZWEVNOTyg2CknotAMcf3FJ5B+ebI=:", - "signature-input": "comm-b38rlmpnkote7pj6ixyf82o_ohj9xgqfozepxd7g9a8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHgxOWI5NGE3YmQzZGZiZDVhZjUyODY1MGZmNDkyODAyYmZkYjkwOWFmZjcwNjA4Njg0ZmVhY2UyNGYzZTA4YTAx, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMTIwMDAwNDA0MDAwMDAwMDQwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMzEyMDAwMDAyMDEw\", comm-il29hgsyyy8ti2jdr8t-9zseofap4epv1ew-_yjtrxq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kgitQmrvR9rzg6zMlMxn7wKABUI/mFKkvgQifxHMnmtqmyzTxg", - "x-77-nzt-ray": "3f9f132583faaf24a5d7df69caa95d0a", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"12\",\"threshold\":2,\"tree\":[[{\"address\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\",\"weight\":1},{\"address\":\"0x4eEa115852357FC87Ad9E1AD4A4554fECE1e7ff7\",\"weight\":1}],{\"address\":\"0xEb80E4dF62074285675DD0b2Bc360272847444cb\",\"weight\":1}]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\\\", \\\"0x4eEa115852357FC87Ad9E1AD4A4554fECE1e7ff7\\\", \\\"0xEb80E4dF62074285675DD0b2Bc360272847444cb\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb691f205a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:33 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=604", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kgw/4qBeSg1P1uuTNx/Hcxi0jSn8niL83kSvawnOBygh", - "x-77-nzt-ray": "331b5e0fe1a2ac95a5d7df699e05d310", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIxSmZCQ0w3bnpaSzlxcmZ6OHFPQTRZVjEtR1JmLU1FLWIwenVxY3J3NmRFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"1JfBCL7nzZK9qrfz8qOA4YV1-GRf-ME-b0zuqcrw6dE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x512485d4e46a9758b98e84105d4d54755ce6645bd3ddbab0e850c968a8ef25ba\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x7178a76bb537253e8f37c2c6a400af1a85b7f5314ae9f06bfeb1a93df275fca3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"11\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0040000000000000000000000000000000411100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJHMHo4V29aYm85allnbzFsa01GbHRLcm9uc2c3em16WmVuUFRiVkZNaldjIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"G0z8WoZbo9jYgo1lkMFltKronsg7zmzZenPTbVFMjWc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xa3c45186be4593770ea3414abbd86c5e10f5791b093372b2f8a9a25482e9f826\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x2bafea1dbc6381b39848d226dccb906f23171edf7661149da26ab609d15a1339\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"10\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000000000400400100408100000001003000082010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJnZGdUclNoR09vUjBzYlFXSVI0M1hEUE5JTmI3THExUFVMUEY4SU9RNy1ZIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"12\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Tree", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb6d89d7c45b-YYZ", - "connection": "keep-alive", - "content-length": "250", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:34 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"tree\":{\"data\":\"0x53657175656e6365207265636f76657279206c6561663a0acd8b2b62b2ebe631c54ed0cc6f366794da6ff9210000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000000000\"}}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"tree\\\"] }, { name: \\\"Sequence-Sessions-Tree\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb6d6abe5a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:34 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=10,cfOrigin;dur=860", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "ktvGusekN26TEaTojilG7xgDP1HgCOxZxFZos1oIuzfe", - "x-77-nzt-ray": "331b5e0fe1a2ac95a5d7df69b184a939", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjAwLCJEWDNTWFZQM2JRMHVJdUszemdFRG01bGhOcVdobFZ5b1FSMVBCTlg1RVZRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"DX3SXVP3bQ0uIuK3zgEDm5lhNqWhlVyoQR1PBNX5EVQ\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"tree\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Tree\",\"value\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/DX3SXVP3bQ0uIuK3zgEDm5lhNqWhlVyoQR1PBNX5EVQ", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:qM7Bkyg+mTuQK5e9pXMhkxjPMYMlBs9jzrZPiEyacok=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "sequence-sessions-complete": "true", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "0", - "sequence-sessions-tree": "0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3", - "sequence-sessions-type": "tree", - "server": "CDN77-Turbo", - "signature": "comm-a11d_hsfetaqh-a7f91x_ponikv_nsvqfihjthrz-py=:L1yxUCmwZ4/xlamH02g3fxhZ07XoyZN+lDpJ7qqeCkU=:, comm-dx3sxvp3bq0uiuk3zgedm5lhnqwhlvyoqr1pbnx5evq=:X3r/xo7yfg6fvD9WoIqEpBJ+lQwDyPjyYEGnb27OcidyahZZuAdsX1OlKKmrrqRsWKcOoY7oMrCG7mPPKpNfexw=:", - "signature-input": "comm-a11d_hsfetaqh-a7f91x_ponikv_nsvqfihjthrz-py=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-tree\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-dx3sxvp3bq0uiuk3zgedm5lhnqwhlvyoqr1pbnx5evq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-tree\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:dHJlZQ, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Tree:MHhlZWY2OTc3NGUxY2I0ODhhNzFmNmQyMzVjODU4ZmE1NjQxMzRlZTdjM2FjZGE5ZmYxMTZiNmM5ZDQyYjNjZWUz, 6:Sequence-Sessions-Complete:dHJ1ZQ\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kuPrk/ujs35DBlekVTvr5jJ5pCrbdv4t5IEOkG3tG5DDuN1X9A", - "x-77-nzt-ray": "f03d06137f7e9036a6d7df697c5d623b", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"data\":\"0x53657175656e6365207265636f76657279206c6561663a0acd8b2b62b2ebe631c54ed0cc6f366794da6ff9210000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000000000\"}" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"digest\":\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb743cf0c45b-YYZ", - "connection": "keep-alive", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "transfer-encoding": "chunked", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"payload\":{\"calls\":[{\"behaviorOnError\":\"revert\",\"data\":\"0xe2a53ed00000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000000000000000000000000000000000000000000000000000000000007d1f4\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x4B3eC67c5812543924C12a07140369C29077071e\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a712ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xe6bd720e0000000000000000000000000000000000000000000000000000000000002337000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf5600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000858db1cbf6d09d447c96a11603189b49b2d1c219\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xb8dc491b0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf56\",\"delegateCall\":true,\"gasLimit\":\"0\",\"onlyFallback\":true,\"to\":\"0xBaE357CBAA04a68cbfD5a560Ab06C4E9A3328A90\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a7120000000000000000000000000000000000000000000000000000000000000000\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xd86930726aa24a63\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x0000000000000000000000000000000000000004\",\"value\":\"0\"}],\"nonce\":\"0\",\"space\":\"0\",\"type\":\"call\"},\"wallet\":\"0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5\",\"chainID\":\"137\"}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb7458815a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=160", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kky/ENH9JInPTEZqn2ndbNgN0+M4+SAb9s4G6PCc3KZq", - "x-77-nzt-ray": "331b5e0fe1a2ac95a7d7df69514c8a04", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY0LCIyUUdGMXBQVThxVjlpSnRMWkFDdXN5akRRak9NX1pQOVA1aXBXSmZNWnZZIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"2QGF1pPU8qV9iJtLZACusyjDQjOM_ZP9P5ipWJfMZvY\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"137\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"calls\"},{\"name\":\"Sequence-Sessions-Space\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Nonce\",\"value\":\"0\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/2QGF1pPU8qV9iJtLZACusyjDQjOM_ZP9P5ipWJfMZvY", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:y73VlX9cN7fjXboylCRs43zrkkidSnrYBsg/a62Xmxo=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "sequence-sessions-address": "0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5", - "sequence-sessions-chain-id": "137", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-nonce": "0", - "sequence-sessions-payload": "0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576", - "sequence-sessions-payload-type": "calls", - "sequence-sessions-space": "0", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-2qgf1ppu8qv9ijtlzacusyjdqjom_zp9p5ipwjfmzvy=:EprEWA/QS88utcQNaUU9Mm9m7CxrUBn65vOfEq8sqBAJ4v4GO9UAm+0SOkeF2uN3GwKLbavIAK1QPIPKONw58hw=:, comm-q_y3jmjfuj2dbzu0ixgerlile_uk1vh_fwegfphj-kg=:2VqYVrvsUIA5dRuJeyHv4D8jGMtRVpkfB29bRQaoOv8=:", - "signature-input": "comm-2qgf1ppu8qv9ijtlzacusyjdqjom_zp9p5ipwjfmzvy=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-nonce\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-space\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 10:Sequence-Sessions-Nonce:MA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjNzhmMzk1MTY4NmI3ZjE2ZjM5ZTI1YWVhMWZkNWFjYzBlMjE3NzA4M2MxNzBiNGM5NjJiZTZjZDQ1NjMwNTc2, 6:Sequence-Sessions-Address:MHgyRDczM2ZCOEYxZkI3NjY5QjBBRkE5ODBDMTFGMUE2ZGQ0RjYzMEY1, 7:Sequence-Sessions-Chain-ID:MTM3, 8:Sequence-Sessions-Payload-Type:Y2FsbHM, 9:Sequence-Sessions-Space:MA\", comm-q_y3jmjfuj2dbzu0ixgerlile_uk1vh_fwegfphj-kg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-nonce\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-space\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kvXz0wEERDnzpE02FEx7DASf9Vn8KQ1OK/HUGQ/AKAKRl8ru1A", - "x-77-nzt-ray": "3f9f132582d25d8aa7d7df696573b718", - "x-77-pop": "newyorkUSNY" - }, - "body": "[{\"behaviorOnError\":\"revert\",\"data\":\"0xe2a53ed00000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000000000000000000000000000000000000000000000000000000000007d1f4\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x4B3eC67c5812543924C12a07140369C29077071e\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a712ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xe6bd720e0000000000000000000000000000000000000000000000000000000000002337000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf5600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000858db1cbf6d09d447c96a11603189b49b2d1c219\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xb8dc491b0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf56\",\"delegateCall\":true,\"gasLimit\":\"0\",\"onlyFallback\":true,\"to\":\"0xBaE357CBAA04a68cbfD5a560Ab06C4E9A3328A90\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a7120000000000000000000000000000000000000000000000000000000000000000\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xd86930726aa24a63\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x0000000000000000000000000000000000000004\",\"value\":\"0\"}]" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"digest\":\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb76eccec45b-YYZ", - "connection": "keep-alive", - "content-length": "531", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307844323437443562313932353732393438634365643444624131633634386262366662666539306543222c227369676e6572223a22307830643662303266623238306533353336303364356538613462646136623432366135633762343138222c2274696d657374616d70223a313736363038333632303439382c227369676e65724b696e64223a226c6f63616c2d646576696365227d\",\"type\":\"message\"},\"wallet\":\"0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC\",\"chainID\":\"0\"}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb770ab35a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:35 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kqX6v+4iErm5YUpn1+BYcdVbqzpv/72vAxv/CecuStWy", - "x-77-nzt-ray": "331b5e0fe1a2ac95a7d7df691e56231e", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY0LCIzSGYxUFdGaF9ZdzRhdTBDWjhBYU14NldCX0ZWQ1Q5UkJIMy1OOW1wVlJnIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"3Hf1PWFh_Yw4au0CZ8AaMx6WB_FVCT9RBH3-N9mpVRg\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/3Hf1PWFh_Yw4au0CZ8AaMx6WB_FVCT9RBH3-N9mpVRg", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:A1YMLxj1j3HT9nFpONVt2VC77V87xprIq9H/yLXq9j8=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "sequence-sessions-address": "0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-payload": "0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4", - "sequence-sessions-payload-type": "message", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-3hf1pwfh_yw4au0cz8aamx6wb_fvct9rbh3-n9mpvrg=:790rl5TXvVj20BC8LJ98yNjsx4ejtsrgBSFEZm/obn9h5lK8UPI5ztHX+WA5psYNPhxi1XvTNfEhwZmyz9pG5xs=:, comm-fdqefal8-0i-shhtdty8o5mtdosf0h_yvt9gipd4ziu=:UoLz2MjgY7q35/CBTDrfiRRMA8ieQKTi5ATCLXg4YF0=:", - "signature-input": "comm-3hf1pwfh_yw4au0cz8aamx6wb_fvct9rbh3-n9mpvrg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgzYTg0MWJhMzE2M2E3YTE5Y2QxNjgzNzNkZjExNDRkMzgxMzBiMmY0NmI4ZDZlYWM5NTYxMjdmMDZmZmZlNGY0, 6:Sequence-Sessions-Address:MHhEMjQ3RDViMTkyNTcyOTQ4Y0NlZDREYkExYzY0OGJiNmZiZmU5MGVD, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\", comm-fdqefal8-0i-shhtdty8o5mtdosf0h_yvt9gipd4ziu=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kon1rwdQeQ1znuXHKI8UDqFy/lgo04k2GpXNPxF/7tZEmWehhw", - "x-77-nzt-ray": "8705ec34712958dfa8d7df69d7f8d003", - "x-77-pop": "newyorkUSNY" - }, - "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307844323437443562313932353732393438634365643444624131633634386262366662666539306543222c227369676e6572223a22307830643662303266623238306533353336303364356538613462646136623432366135633762343138222c2274696d657374616d70223a313736363038333632303439382c227369676e65724b696e64223a226c6f63616c2d646576696365227d\"" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"digest\":\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb7b6a0cc45b-YYZ", - "connection": "keep-alive", - "content-length": "197", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"payload\":{\"imageHash\":\"0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53\",\"type\":\"config-update\"},\"wallet\":\"0xC018D4EB963bDe912f3a440554Af070c7C0BF313\",\"chainID\":\"0\"}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb7b8e245a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=7,cfOrigin;dur=171", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "kvQQ+5s8UFxvEgTO6p+xw8zMfYsSlUhNC3eI+QhmYpqk", - "x-77-nzt-ray": "331b5e0fe1a2ac95a8d7df69c0c4840d", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODc3LCJUd20zU3NzTzlITTJSOG9CSkp0V1JwSUFaeThHemFjcFE5dnBFWUdLWnVFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"Twm3SssO9HM2R8oBJJtWRpIAZy8GzacpQ9vpEYGKZuE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0xC018D4EB963bDe912f3a440554Af070c7C0BF313\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53\"},{\"name\":\"Sequence-Sessions-To-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000008008000100802082840208090100000023400000140002000500020409\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/Twm3SssO9HM2R8oBJJtWRpIAZy8GzacpQ9vpEYGKZuE", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:sDSB/zhiOWptyMrGP5eMgYGCSjAG+AEX5ek1NCPhKTI=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "sequence-sessions-address": "0xC018D4EB963bDe912f3a440554Af070c7C0BF313", - "sequence-sessions-chain-id": "0", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-payload": "0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b", - "sequence-sessions-payload-type": "config update", - "sequence-sessions-to-checkpoint": "4", - "sequence-sessions-to-config": "0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53", - "sequence-sessions-to-config-complete": "true", - "sequence-sessions-to-signers-bloom": "0x0000008008000100802082840208090100000023400000140002000500020409", - "sequence-sessions-to-signers-count": "7", - "sequence-sessions-to-version": "3", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-5pnsazaxuuh8mktavldjkji_92nknngtzzri_wuxhia=:+TfdPyPtERmEfeW5geLa7tkcJ7ZcdKsRkDNgBG3oJos=:, comm-twm3ssso9hm2r8objjtwrpiazy8gzacpq9vpeygkzue=:UrFROJgchVKSsHYlhqdsLasc82cIbkqkHd7Ia9u+h5kCiuVuSHn/DGt7Dp4+fTxAYJlgr3uDy8yEK6FDPP0FVRs=:", - "signature-input": "comm-5pnsazaxuuh8mktavldjkji_92nknngtzzri_wuxhia=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-to-version\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-twm3ssso9hm2r8objjtwrpiazy8gzacpq9vpeygkzue=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-to-version\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 10:Sequence-Sessions-To-Version:Mw, 11:Sequence-Sessions-To-Checkpoint:NA, 12:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 13:Sequence-Sessions-To-Signers-Count:Nw, 14:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMTAwODAyMDgyODQwMjA4MDkwMTAwMDAwMDIzNDAwMDAwMTQwMDAyMDAwNTAwMDIwNDA5, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjYWU2MzE2NjBmZmE5MGJkZGM1ZTliNGZhOWMxMTY5MmE1MzA2MmE2MTY0MGZiOTU4ZjNmMjk1OWQyMmZlNTRi, 6:Sequence-Sessions-Address:MHhDMDE4RDRFQjk2M2JEZTkxMmYzYTQ0MDU1NEFmMDcwYzdDMEJGMzEz, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:Y29uZmlnIHVwZGF0ZQ, 9:Sequence-Sessions-To-Config:MHhlMjVkNTFkMDc2Y2JiMzRkZDRmNjhiMjY0MjE3MjRlMmY2ZjdiZTQ5ZGZkOTEzZGFlM2QwYzBmYTA1ZTg2ZDUz\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "kgtQOfxtm1ATCfanlgWwAEqGnZLSUhWuvzXjo6pK4TdefM3aSw", - "x-77-nzt-ray": "3f9f132578e09bbba8d7df6948a99d21", - "x-77-pop": "newyorkUSNY" - }, - "body": "{\"checkpoint\":\"4\",\"threshold\":2,\"tree\":[[[{\"address\":\"0xF223a6E051B743BfEC993e17555d31cee5ac9fE9\",\"weight\":1},{\"address\":\"0xB34EFD7629C431d2caDf69d9aFDEA984Ff6EC86b\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x507c907dd7fbb034d69ea4287e7a52c9d689d9795ac4d8ef38231bb87e38feec\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0x5b59a76df21cc0aebe78bafe7d705f9dffb21d5ccf633d0f53a8cc7dd35730b7\",\"weight\":255}]]}" - } - }, - { - "request": { - "method": "POST", - "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", - "headers": { - "content-type": "application/json", - "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" - }, - "body": "{\"digest\":\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "alt-svc": "h3=\":443\"; ma=86400", - "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb7e3a5cc45b-YYZ", - "connection": "keep-alive", - "content-length": "187", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "expires": "Thu, 01 Jan 1970 00:00:00 GMT", - "pragma": "no-cache", - "server": "cloudflare", - "strict-transport-security": "max-age=2592000; includeSubDomains", - "vary": "Origin", - "via": "1.1 google", - "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" - }, - "body": "{\"version\":3,\"payload\":{\"digest\":\"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\",\"type\":\"digest\"},\"wallet\":\"0x1111111111111111111111111111111111111111\",\"chainID\":\"1\"}" - } - }, - { - "request": { - "method": "POST", - "url": "https://arweave.net/graphql", - "headers": { - "content-type": "application/json" - }, - "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "cache-control": "no-store", - "cf-cache-status": "DYNAMIC", - "cf-ray": "9eccfb7e48075a46-IAD", - "connection": "keep-alive", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:36 GMT", - "server": "CDN77-Turbo", - "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=4,cfOrigin;dur=188", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding, Origin", - "x-77-cache": "MISS", - "x-77-nzt": "khTWKji6AMQLths5NYi/adhHHOlYkr5Pbz13CeYGRmzp", - "x-77-nzt-ray": "331b5e0fe1a2ac95a8d7df69e27a3228", - "x-77-pop": "montrealCAQC", - "x-upstream-url": "https://arweave-search.goldsky.com/graphql" - }, - "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY3LCJsVmgyQUxiTTNrTHlKcDdtb0pBNUk4d1Y1Y1VsY25LeUI1bnJGSVNPdkVvIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"lVh2ALbM3kLyJp7moJA5I8wV5cUlcnKyB5nrFISOvEo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x1111111111111111111111111111111111111111\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"digest\"},{\"name\":\"Sequence-Sessions-Digest\",\"value\":\"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"}]}}]}}}\n" - } - }, - { - "request": { - "method": "GET", - "url": "https://arweave.net/lVh2ALbM3kLyJp7moJA5I8wV5cUlcnKyB5nrFISOvEo", - "headers": {}, - "body": "" - }, - "response": { - "status": 200, - "statusText": "OK", - "headers": { - "access-control-allow-headers": "*", - "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", - "access-control-allow-origin": "*", - "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", - "ao-body-key": "data", - "ao-types": "status=\"integer\"", - "connection": "keep-alive", - "content-digest": "sha-256=:dCNOmK/nSY+12vHzasLXiswzlGT5UHA7jAGYkvmCuQs=:", - "content-encoding": "gzip", - "content-type": "application/json", - "date": "Wed, 15 Apr 2026 18:23:37 GMT", - "sequence-sessions-address": "0x1111111111111111111111111111111111111111", - "sequence-sessions-chain-id": "1", - "sequence-sessions-digest": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "sequence-sessions-major-version": "1", - "sequence-sessions-minor-version": "2", - "sequence-sessions-payload": "0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432", - "sequence-sessions-payload-type": "digest", - "sequence-sessions-type": "payload", - "server": "CDN77-Turbo", - "signature": "comm-_jzqikhymam_lprvj7ycaaktslaa0sbqc7czeyhvbhg=:2nut0qs1ieXwfVSjm9pppo+WVEbavBKS+FfI0s0auYI=:, comm-lvh2albm3klyjp7moja5i8wv5culcnkyb5nrfisoveo=:ZINUHXdgCdrtP9TmAanj/BS4HaadRFWWaferNtdEEKtriq3gi/My1CCCpPc5MI7FwVsOHpYmIrTP3ZRIAhG+xBs=:", - "signature-input": "comm-_jzqikhymam_lprvj7ycaaktslaa0sbqc7czeyhvbhg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-digest\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-lvh2albm3klyjp7moja5i8wv5culcnkyb5nrfisoveo=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-digest\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjZDNjMjkxZTA5MzlmMDI5YWFhNGI0ZjI5MmQ1ZDJiMmNlNDNiYWY5ODA0NmQ5YWJjMmEzZTgyODRiMjUzNDMy, 6:Sequence-Sessions-Address:MHgxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEx, 7:Sequence-Sessions-Chain-ID:MQ, 8:Sequence-Sessions-Payload-Type:ZGlnZXN0, 9:Sequence-Sessions-Digest:MHhkZWFkYmVlZmRlYWRiZWVmZGVhZGJlZWZkZWFkYmVlZmRlYWRiZWVmZGVhZGJlZWZkZWFkYmVlZmRlYWRiZWVm\"", - "status": "200", - "transfer-encoding": "chunked", - "vary": "Accept-Encoding", - "x-77-cache": "MISS", - "x-77-nzt": "knv9xlzlzyl+8BAqY6Rzi6OmidE58jsyXeJYUQRAB8zDDWWidQ", - "x-77-nzt-ray": "f03d061342437487a9d7df69cd5e1901", - "x-77-pop": "newyorkUSNY" - }, - "body": "null" - } - } -] \ No newline at end of file diff --git a/packages/wallet/core/test/state/cached.test.ts b/packages/wallet/core/test/state/cached.test.ts deleted file mode 100644 index 45dd616ca7..0000000000 --- a/packages/wallet/core/test/state/cached.test.ts +++ /dev/null @@ -1,536 +0,0 @@ -import { Address, Hex } from 'ox' -import { describe, expect, it, vi, beforeEach } from 'vitest' - -import { Cached } from '../../src/state/cached.js' -import type { Provider } from '../../src/state/index.js' -import { Network } from '@0xsequence/wallet-primitives' - -// Test data -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_ADDRESS_2 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') -const TEST_ROOT_HASH = Hex.from('0xfedcba098765432109876543210987654321098765432109876543210987654321') -const TEST_OP_HASH = Hex.from('0x1111111111111111111111111111111111111111111111111111111111111111') - -// Mock data -const mockConfig = { test: 'config' } as any -const mockContext = { test: 'context' } as any -const mockPayload = { - type: 'call', - calls: [{ to: TEST_ADDRESS, value: 0n, data: '0x123' }], -} as any - -const mockSignature = { - type: 'hash', - r: 123n, - s: 456n, - yParity: 0, -} as any - -const mockSapientSignature = { - type: 'sapient', - address: TEST_ADDRESS, - data: '0xabcdef', -} as any - -const mockWalletData = { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSignature, -} - -const mockSapientWalletData = { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSapientSignature, -} - -const mockTree = { test: 'tree' } as any -const mockSignatures = { type: 'unrecovered-signer', weight: 1n, signature: mockSignature } as any - -describe('Cached', () => { - let mockSource: Provider - let mockCache: Provider - let cached: Cached - - beforeEach(() => { - // Create comprehensive mock providers - mockSource = { - getConfiguration: vi.fn(), - getDeploy: vi.fn(), - getWallets: vi.fn(), - getWalletsForSapient: vi.fn(), - getWitnessFor: vi.fn(), - getWitnessForSapient: vi.fn(), - getConfigurationUpdates: vi.fn(), - getTree: vi.fn(), - getPayload: vi.fn(), - saveWallet: vi.fn(), - saveWitnesses: vi.fn(), - saveUpdate: vi.fn(), - saveTree: vi.fn(), - saveConfiguration: vi.fn(), - saveDeploy: vi.fn(), - savePayload: vi.fn(), - } as unknown as Provider - - mockCache = { - getConfiguration: vi.fn(), - getDeploy: vi.fn(), - getWallets: vi.fn(), - getWalletsForSapient: vi.fn(), - getWitnessFor: vi.fn(), - getWitnessForSapient: vi.fn(), - getConfigurationUpdates: vi.fn(), - getTree: vi.fn(), - getPayload: vi.fn(), - saveWallet: vi.fn(), - saveWitnesses: vi.fn(), - saveUpdate: vi.fn(), - saveTree: vi.fn(), - saveConfiguration: vi.fn(), - saveDeploy: vi.fn(), - savePayload: vi.fn(), - } as unknown as Provider - - cached = new Cached({ source: mockSource, cache: mockCache }) - }) - - describe('getConfiguration', () => { - it('should return cached config when available', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(mockConfig) - - const result = await cached.getConfiguration(TEST_IMAGE_HASH) - - expect(result).toBe(mockConfig) - expect(mockCache.getConfiguration).toHaveBeenCalledWith(TEST_IMAGE_HASH) - expect(mockSource.getConfiguration).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(undefined) - vi.mocked(mockSource.getConfiguration).mockResolvedValue(mockConfig) - - const result = await cached.getConfiguration(TEST_IMAGE_HASH) - - expect(result).toBe(mockConfig) - expect(mockCache.getConfiguration).toHaveBeenCalledWith(TEST_IMAGE_HASH) - expect(mockSource.getConfiguration).toHaveBeenCalledWith(TEST_IMAGE_HASH) - expect(mockCache.saveConfiguration).toHaveBeenCalledWith(mockConfig) - }) - - it('should return undefined when not found in cache or source', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(undefined) - vi.mocked(mockSource.getConfiguration).mockResolvedValue(undefined) - - const result = await cached.getConfiguration(TEST_IMAGE_HASH) - - expect(result).toBeUndefined() - expect(mockCache.saveConfiguration).not.toHaveBeenCalled() - }) - }) - - describe('getDeploy', () => { - const mockDeploy = { imageHash: TEST_IMAGE_HASH, context: mockContext } - - it('should return cached deploy when available', async () => { - vi.mocked(mockCache.getDeploy).mockResolvedValue(mockDeploy) - - const result = await cached.getDeploy(TEST_ADDRESS) - - expect(result).toBe(mockDeploy) - expect(mockCache.getDeploy).toHaveBeenCalledWith(TEST_ADDRESS) - expect(mockSource.getDeploy).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getDeploy).mockResolvedValue(undefined) - vi.mocked(mockSource.getDeploy).mockResolvedValue(mockDeploy) - - const result = await cached.getDeploy(TEST_ADDRESS) - - expect(result).toBe(mockDeploy) - expect(mockSource.getDeploy).toHaveBeenCalledWith(TEST_ADDRESS) - expect(mockCache.saveDeploy).toHaveBeenCalledWith(TEST_IMAGE_HASH, mockContext) - }) - }) - - describe('getWallets', () => { - it('should merge cache and source data and sync bidirectionally', async () => { - const cacheData = { - [TEST_ADDRESS]: mockWalletData, - } - const sourceData = { - [TEST_ADDRESS_2]: mockWalletData, - } - - vi.mocked(mockCache.getWallets).mockResolvedValue(cacheData) - vi.mocked(mockSource.getWallets).mockResolvedValue(sourceData) - - const result = await cached.getWallets(TEST_ADDRESS) - - // Should merge both datasets - addresses will be checksummed - expect(result).toEqual({ - [TEST_ADDRESS]: mockWalletData, - [Address.checksum(TEST_ADDRESS_2)]: mockWalletData, - }) - - // Should sync missing data to source and cache - expect(mockSource.saveWitnesses).toHaveBeenCalledWith( - TEST_ADDRESS, - mockWalletData.chainId, - mockWalletData.payload, - { - type: 'unrecovered-signer', - weight: 1n, - signature: mockWalletData.signature, - }, - ) - - expect(mockCache.saveWitnesses).toHaveBeenCalledWith( - Address.checksum(TEST_ADDRESS_2), - mockWalletData.chainId, - mockWalletData.payload, - { - type: 'unrecovered-signer', - weight: 1n, - signature: mockWalletData.signature, - }, - ) - }) - - it('should handle overlapping data without duplicate syncing', async () => { - const sharedData = { - [TEST_ADDRESS]: mockWalletData, - } - - vi.mocked(mockCache.getWallets).mockResolvedValue(sharedData) - vi.mocked(mockSource.getWallets).mockResolvedValue(sharedData) - - const result = await cached.getWallets(TEST_ADDRESS) - - expect(result).toEqual(sharedData) - // Should not sync data that exists in both - expect(mockSource.saveWitnesses).not.toHaveBeenCalled() - expect(mockCache.saveWitnesses).not.toHaveBeenCalled() - }) - - it('should handle empty cache and source', async () => { - vi.mocked(mockCache.getWallets).mockResolvedValue({}) - vi.mocked(mockSource.getWallets).mockResolvedValue({}) - - const result = await cached.getWallets(TEST_ADDRESS) - - expect(result).toEqual({}) - expect(mockSource.saveWitnesses).not.toHaveBeenCalled() - expect(mockCache.saveWitnesses).not.toHaveBeenCalled() - }) - }) - - describe('getWalletsForSapient', () => { - it('should merge cache and source data for sapient signers', async () => { - const cacheData = { - [TEST_ADDRESS]: mockSapientWalletData, - } - const sourceData = { - [TEST_ADDRESS_2]: mockSapientWalletData, - } - - vi.mocked(mockCache.getWalletsForSapient).mockResolvedValue(cacheData) - vi.mocked(mockSource.getWalletsForSapient).mockResolvedValue(sourceData) - - const result = await cached.getWalletsForSapient(TEST_ADDRESS, TEST_IMAGE_HASH) - - expect(result).toEqual({ - [TEST_ADDRESS]: mockSapientWalletData, - [TEST_ADDRESS_2]: mockSapientWalletData, - }) - - // Verify bidirectional syncing - expect(mockSource.saveWitnesses).toHaveBeenCalled() - expect(mockCache.saveWitnesses).toHaveBeenCalled() - }) - - it('should handle address normalization in syncing', async () => { - const sourceData = { - [TEST_ADDRESS.toLowerCase()]: mockSapientWalletData, - } - - vi.mocked(mockCache.getWalletsForSapient).mockResolvedValue({}) - vi.mocked(mockSource.getWalletsForSapient).mockResolvedValue(sourceData) - - await cached.getWalletsForSapient(TEST_ADDRESS, TEST_IMAGE_HASH) - - // Should sync to cache with proper address conversion - expect(mockCache.saveWitnesses).toHaveBeenCalledWith( - TEST_ADDRESS, - mockSapientWalletData.chainId, - mockSapientWalletData.payload, - { - type: 'unrecovered-signer', - weight: 1n, - signature: mockSapientWalletData.signature, - }, - ) - }) - }) - - describe('getWitnessFor', () => { - const mockWitness = { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSignature, - } - - it('should return cached witness when available', async () => { - vi.mocked(mockCache.getWitnessFor).mockResolvedValue(mockWitness) - - const result = await cached.getWitnessFor(TEST_ADDRESS, TEST_ADDRESS_2) - - expect(result).toBe(mockWitness) - expect(mockSource.getWitnessFor).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getWitnessFor).mockResolvedValue(undefined) - vi.mocked(mockSource.getWitnessFor).mockResolvedValue(mockWitness) - - const result = await cached.getWitnessFor(TEST_ADDRESS, TEST_ADDRESS_2) - - expect(result).toBe(mockWitness) - expect(mockCache.saveWitnesses).toHaveBeenCalledWith(TEST_ADDRESS, mockWitness.chainId, mockWitness.payload, { - type: 'unrecovered-signer', - weight: 1n, - signature: mockWitness.signature, - }) - }) - }) - - describe('getWitnessForSapient', () => { - const mockSapientWitness = { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSapientSignature, - } - - it('should return cached sapient witness when available', async () => { - vi.mocked(mockCache.getWitnessForSapient).mockResolvedValue(mockSapientWitness) - - const result = await cached.getWitnessForSapient(TEST_ADDRESS, TEST_ADDRESS_2, TEST_IMAGE_HASH) - - expect(result).toBe(mockSapientWitness) - expect(mockSource.getWitnessForSapient).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getWitnessForSapient).mockResolvedValue(undefined) - vi.mocked(mockSource.getWitnessForSapient).mockResolvedValue(mockSapientWitness) - - const result = await cached.getWitnessForSapient(TEST_ADDRESS, TEST_ADDRESS_2, TEST_IMAGE_HASH) - - expect(result).toBe(mockSapientWitness) - expect(mockCache.saveWitnesses).toHaveBeenCalledWith( - TEST_ADDRESS, - mockSapientWitness.chainId, - mockSapientWitness.payload, - { - type: 'unrecovered-signer', - weight: 1n, - signature: mockSapientWitness.signature, - }, - ) - }) - }) - - describe('getTree', () => { - it('should return cached tree when available', async () => { - vi.mocked(mockCache.getTree).mockResolvedValue(mockTree) - - const result = await cached.getTree(TEST_ROOT_HASH) - - expect(result).toBe(mockTree) - expect(mockSource.getTree).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getTree).mockResolvedValue(undefined) - vi.mocked(mockSource.getTree).mockResolvedValue(mockTree) - - const result = await cached.getTree(TEST_ROOT_HASH) - - expect(result).toBe(mockTree) - expect(mockCache.saveTree).toHaveBeenCalledWith(mockTree) - }) - }) - - describe('getPayload', () => { - const mockPayloadData = { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - wallet: TEST_ADDRESS, - } - - it('should return cached payload when available', async () => { - vi.mocked(mockCache.getPayload).mockResolvedValue(mockPayloadData) - - const result = await cached.getPayload(TEST_OP_HASH) - - expect(result).toBe(mockPayloadData) - expect(mockSource.getPayload).not.toHaveBeenCalled() - }) - - it('should fetch from source and cache when not in cache', async () => { - vi.mocked(mockCache.getPayload).mockResolvedValue(undefined) - vi.mocked(mockSource.getPayload).mockResolvedValue(mockPayloadData) - - const result = await cached.getPayload(TEST_OP_HASH) - - expect(result).toBe(mockPayloadData) - expect(mockCache.savePayload).toHaveBeenCalledWith( - mockPayloadData.wallet, - mockPayloadData.payload, - mockPayloadData.chainId, - ) - }) - }) - - describe('getConfigurationUpdates', () => { - it('should forward to source without caching', async () => { - const mockUpdates = [{ imageHash: TEST_IMAGE_HASH, signature: '0x123' }] as any - vi.mocked(mockSource.getConfigurationUpdates).mockResolvedValue(mockUpdates) - - const result = await cached.getConfigurationUpdates(TEST_ADDRESS, TEST_IMAGE_HASH, { allUpdates: true }) - - expect(result).toBe(mockUpdates) - expect(mockSource.getConfigurationUpdates).toHaveBeenCalledWith(TEST_ADDRESS, TEST_IMAGE_HASH, { - allUpdates: true, - }) - expect(mockCache.getConfigurationUpdates).not.toHaveBeenCalled() - }) - }) - - describe('write operations', () => { - it('should forward saveWallet to source', async () => { - await cached.saveWallet(mockConfig, mockContext) - - expect(mockSource.saveWallet).toHaveBeenCalledWith(mockConfig, mockContext) - expect(mockCache.saveWallet).not.toHaveBeenCalled() - }) - - it('should forward saveWitnesses to source', async () => { - await cached.saveWitnesses(TEST_ADDRESS, Network.ChainId.MAINNET, mockPayload, mockSignatures) - - expect(mockSource.saveWitnesses).toHaveBeenCalledWith( - TEST_ADDRESS, - Network.ChainId.MAINNET, - mockPayload, - mockSignatures, - ) - expect(mockCache.saveWitnesses).not.toHaveBeenCalled() - }) - - it('should forward saveUpdate to source', async () => { - const mockRawSignature = '0x123' as any - await cached.saveUpdate(TEST_ADDRESS, mockConfig, mockRawSignature) - - expect(mockSource.saveUpdate).toHaveBeenCalledWith(TEST_ADDRESS, mockConfig, mockRawSignature) - expect(mockCache.saveUpdate).not.toHaveBeenCalled() - }) - - it('should forward saveTree to source', async () => { - await cached.saveTree(mockTree) - - expect(mockSource.saveTree).toHaveBeenCalledWith(mockTree) - expect(mockCache.saveTree).not.toHaveBeenCalled() - }) - - it('should forward saveConfiguration to source', async () => { - await cached.saveConfiguration(mockConfig) - - expect(mockSource.saveConfiguration).toHaveBeenCalledWith(mockConfig) - expect(mockCache.saveConfiguration).not.toHaveBeenCalled() - }) - - it('should forward saveDeploy to source', async () => { - await cached.saveDeploy(TEST_IMAGE_HASH, mockContext) - - expect(mockSource.saveDeploy).toHaveBeenCalledWith(TEST_IMAGE_HASH, mockContext) - expect(mockCache.saveDeploy).not.toHaveBeenCalled() - }) - - it('should forward savePayload to source', async () => { - await cached.savePayload(TEST_ADDRESS, mockPayload, Network.ChainId.MAINNET) - - expect(mockSource.savePayload).toHaveBeenCalledWith(TEST_ADDRESS, mockPayload, Network.ChainId.MAINNET) - expect(mockCache.savePayload).not.toHaveBeenCalled() - }) - }) - - describe('error handling', () => { - it('should propagate errors from cache and source', async () => { - vi.mocked(mockCache.getConfiguration).mockRejectedValue(new Error('Cache error')) - vi.mocked(mockSource.getConfiguration).mockRejectedValue(new Error('Source error')) - - await expect(cached.getConfiguration(TEST_IMAGE_HASH)).rejects.toThrow('Cache error') - }) - - it('should propagate source errors when cache is empty', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(undefined) - vi.mocked(mockSource.getConfiguration).mockRejectedValue(new Error('Source error')) - - await expect(cached.getConfiguration(TEST_IMAGE_HASH)).rejects.toThrow('Source error') - }) - - it('should propagate cache save errors', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(undefined) - vi.mocked(mockSource.getConfiguration).mockResolvedValue(mockConfig) - vi.mocked(mockCache.saveConfiguration).mockRejectedValue(new Error('Cache save error')) - - await expect(cached.getConfiguration(TEST_IMAGE_HASH)).rejects.toThrow('Cache save error') - }) - }) - - describe('edge cases', () => { - it('should handle null/undefined returns from providers', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(null as any) - vi.mocked(mockSource.getConfiguration).mockResolvedValue(null as any) - - const result = await cached.getConfiguration(TEST_IMAGE_HASH) - - expect(result).toBeNull() - }) - - it('should handle address normalization correctly', async () => { - const cacheData = { [TEST_ADDRESS.toLowerCase()]: mockWalletData } - const sourceData = { [TEST_ADDRESS_2.toLowerCase()]: mockWalletData } - - vi.mocked(mockCache.getWallets).mockResolvedValue(cacheData) - vi.mocked(mockSource.getWallets).mockResolvedValue(sourceData) - - const result = await cached.getWallets(TEST_ADDRESS) - - // Should normalize and merge correctly - all addresses will be checksummed - expect(Object.keys(result)).toHaveLength(2) - expect(result[Address.checksum(TEST_ADDRESS)]).toBeDefined() - expect(result[Address.checksum(TEST_ADDRESS_2)]).toBeDefined() - }) - - it('should handle concurrent operations correctly', async () => { - vi.mocked(mockCache.getConfiguration).mockResolvedValue(undefined) - vi.mocked(mockSource.getConfiguration).mockResolvedValue(mockConfig) - - // Simulate concurrent calls - const promises = [ - cached.getConfiguration(TEST_IMAGE_HASH), - cached.getConfiguration(TEST_IMAGE_HASH), - cached.getConfiguration(TEST_IMAGE_HASH), - ] - - const results = await Promise.all(promises) - - results.forEach((result) => expect(result).toBe(mockConfig)) - // Each call should trigger source fetch since cache is empty - expect(mockSource.getConfiguration).toHaveBeenCalledTimes(3) - }) - }) -}) diff --git a/packages/wallet/core/test/state/debug.test.ts b/packages/wallet/core/test/state/debug.test.ts deleted file mode 100644 index 6882f45a20..0000000000 --- a/packages/wallet/core/test/state/debug.test.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { Address, Hex } from 'ox' -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' - -import { multiplex } from '../../src/state/debug.js' - -// Test data -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_UINT8ARRAY = new Uint8Array([171, 205, 239, 18, 52, 86]) - -describe('State Debug', () => { - // Mock console.trace to test logging - const originalTrace = console.trace - beforeEach(() => { - console.trace = vi.fn() - }) - afterEach(() => { - console.trace = originalTrace - }) - - describe('utility functions (tested through multiplex)', () => { - it('should handle stringifyReplacer functionality', async () => { - interface TestInterface { - testMethod(data: { bigint: bigint; uint8Array: Uint8Array; normal: string }): Promise - } - - const reference: TestInterface = { - async testMethod(data) { - return JSON.stringify(data, (key, value) => { - if (typeof value === 'bigint') return value.toString() - if (value instanceof Uint8Array) return Hex.fromBytes(value) - return value - }) - }, - } - - const candidate: TestInterface = { - async testMethod(data) { - return JSON.stringify(data, (key, value) => { - if (typeof value === 'bigint') return value.toString() - if (value instanceof Uint8Array) return Hex.fromBytes(value) - return value - }) - }, - } - - const proxy = multiplex(reference, { candidate }) - - const testData = { - bigint: 123456789012345678901234567890n, - uint8Array: TEST_UINT8ARRAY, - normal: 'test string', - } - - const result = await proxy.testMethod(testData) - - // Should properly stringify with bigint and Uint8Array conversion - expect(result).toContain('123456789012345678901234567890') - expect(result).toContain('0xabcdef123456') - expect(result).toContain('test string') - }) - - it('should handle normalize functionality for deep comparison', async () => { - interface TestInterface { - testMethod(data: any): Promise - } - - const reference: TestInterface = { - async testMethod(data) { - return data - }, - } - - // Candidate that returns equivalent but not identical data - const candidate: TestInterface = { - async testMethod(data) { - return { - ...data, - address: data.address?.toUpperCase(), // Different case - nested: { - ...data.nested, - bigint: data.nested?.bigint, // Same bigint - }, - } - }, - } - - const proxy = multiplex(reference, { candidate }) - - const testData = { - address: TEST_ADDRESS.toLowerCase(), - nested: { - bigint: 123n, - array: [1, 2, 3], - uint8: TEST_UINT8ARRAY, - }, - undefined_field: undefined, - } - - await proxy.testMethod(testData) - - // Should detect that normalized values are equal (despite case differences) - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).not.toContain('warning: candidate testMethod does not match reference') - }) - }) - - describe('multiplex', () => { - interface MockInterface { - syncMethod(value: string): string - asyncMethod(value: number): Promise - throwingMethod(): Promise - property: string - } - - let reference: MockInterface - let candidate1: MockInterface - let candidate2: MockInterface - - beforeEach(() => { - reference = { - syncMethod: vi.fn((value: string) => `ref-${value}`), - asyncMethod: vi.fn(async (value: number) => value * 2), - throwingMethod: vi.fn(async () => { - throw new Error('Reference error') - }), - property: 'ref-property', - } - - candidate1 = { - syncMethod: vi.fn((value: string) => `cand1-${value}`), - asyncMethod: vi.fn(async (value: number) => value * 2), // Same as reference - throwingMethod: vi.fn(async () => { - throw new Error('Candidate1 error') - }), - property: 'cand1-property', - } - - candidate2 = { - syncMethod: vi.fn((value: string) => `cand2-${value}`), - asyncMethod: vi.fn(async (value: number) => value * 3), // Different from reference - throwingMethod: vi.fn(async () => { - /* doesn't throw */ - }), - property: 'cand2-property', - } - }) - - it('should proxy method calls to reference and return reference result', async () => { - const proxy = multiplex(reference, { candidate1, candidate2 }) - - const syncResult = await proxy.syncMethod('test') - const asyncResult = await proxy.asyncMethod(5) - - expect(syncResult).toBe('ref-test') - expect(asyncResult).toBe(10) - - expect(reference.syncMethod).toHaveBeenCalledWith('test') - expect(reference.asyncMethod).toHaveBeenCalledWith(5) - }) - - it('should call candidates in parallel and compare results', async () => { - const proxy = multiplex(reference, { candidate1, candidate2 }) - - await proxy.asyncMethod(5) - - expect(candidate1.asyncMethod).toHaveBeenCalledWith(5) - expect(candidate2.asyncMethod).toHaveBeenCalledWith(5) - - // Should log comparison results - expect(console.trace).toHaveBeenCalledTimes(2) // One for each candidate - }) - - it('should detect and log when candidate results match reference', async () => { - const proxy = multiplex(reference, { candidate1 }) - - await proxy.asyncMethod(5) - - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).toContain('candidate1 returned:') - expect(traceCall[0]).not.toContain('warning: candidate1 asyncMethod does not match reference') - }) - - it('should detect and log when candidate results differ from reference', async () => { - const proxy = multiplex(reference, { candidate2 }) - - await proxy.asyncMethod(5) - - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).toContain('warning: candidate2 asyncMethod does not match reference') - }) - - it('should handle when reference method throws', async () => { - const proxy = multiplex(reference, { candidate1 }) - - await expect(proxy.throwingMethod()).rejects.toThrow('Reference error') - - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).toContain('warning: reference throwingMethod threw:') - }) - - it('should handle when candidate method throws', async () => { - const proxy = multiplex(reference, { candidate1 }) - - const result = await proxy.syncMethod('test') - - expect(result).toBe('ref-test') - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).toContain('warning: candidate1 syncMethod does not match reference') - }) - - it('should handle when candidate method is missing', async () => { - const incompleteCandidate = { - property: 'incomplete', - // missing syncMethod - } as any - - const proxy = multiplex(reference, { incomplete: incompleteCandidate }) - - await proxy.syncMethod('test') - - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - expect(traceCall[0]).toContain('warning: incomplete has no syncMethod') - }) - - it('should passthrough non-method properties', () => { - const proxy = multiplex(reference, { candidate1 }) - - expect(proxy.property).toBe('ref-property') - }) - - it('should handle complex data types in logging', async () => { - interface ComplexInterface { - complexMethod(data: { bigint: bigint; uint8Array: Uint8Array; nested: { value: string } }): Promise - } - - const complexRef: ComplexInterface = { - async complexMethod() { - return 'complex-ref' - }, - } - - const complexCand: ComplexInterface = { - async complexMethod() { - return 'complex-cand' - }, - } - - const proxy = multiplex(complexRef, { complex: complexCand }) - - const complexData = { - bigint: 999999999999999999n, - uint8Array: TEST_UINT8ARRAY, - nested: { value: 'nested-test' }, - } - - await proxy.complexMethod(complexData) - - expect(console.trace).toHaveBeenCalled() - const traceCall = vi.mocked(console.trace).mock.calls[0] - - // Should properly stringify complex data in logs - expect(traceCall[0]).toContain('999999999999999999') - expect(traceCall[0]).toContain('0xabcdef123456') - expect(traceCall[0]).toContain('nested-test') - }) - - it('should generate unique IDs for different calls', async () => { - const proxy = multiplex(reference, { candidate1, candidate2 }) - - await proxy.syncMethod('test1') - await proxy.syncMethod('test2') - - expect(console.trace).toHaveBeenCalledTimes(4) // 2 calls * 2 candidates - - const traces = vi.mocked(console.trace).mock.calls - const ids = traces.map((call) => call[0].match(/\[(\d{6})\]/)?.[1]).filter(Boolean) - - // Should have generated unique IDs (though there's a small chance of collision) - expect(ids).toHaveLength(4) - expect(new Set(ids).size).toBeGreaterThan(1) // At least some should be different - }) - - it('should handle async candidates correctly', async () => { - const asyncCandidate = { - syncMethod: vi.fn((value: string) => `async-${value}`), // Return string directly, not Promise - asyncMethod: vi.fn(async (value: number) => value * 2), - throwingMethod: vi.fn(), - property: 'async-property', - } - - const proxy = multiplex(reference, { async: asyncCandidate }) - - await proxy.syncMethod('test') - - expect(asyncCandidate.syncMethod).toHaveBeenCalledWith('test') - expect(console.trace).toHaveBeenCalled() - }) - - it('should handle multiple candidates with mixed results', async () => { - const matching = { - syncMethod: vi.fn((value: string) => `ref-${value}`), // Matches reference - asyncMethod: vi.fn(), - throwingMethod: vi.fn(), - property: 'matching', - } - - const different = { - syncMethod: vi.fn((value: string) => `diff-${value}`), // Different from reference - asyncMethod: vi.fn(), - throwingMethod: vi.fn(), - property: 'different', - } - - const proxy = multiplex(reference, { matching, different }) - - await proxy.syncMethod('test') - - expect(console.trace).toHaveBeenCalledTimes(2) - - const traces = vi.mocked(console.trace).mock.calls - const matchingTrace = traces.find((call) => call[0].includes('matching')) - const differentTrace = traces.find((call) => call[0].includes('different')) - - expect(matchingTrace?.[0]).not.toContain('warning: matching syncMethod does not match reference') - expect(differentTrace?.[0]).toContain('warning: different syncMethod does not match reference') - }) - }) -}) diff --git a/packages/wallet/core/test/state/local/memory.test.ts b/packages/wallet/core/test/state/local/memory.test.ts deleted file mode 100644 index e01dbb5263..0000000000 --- a/packages/wallet/core/test/state/local/memory.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { Address, Hex } from 'ox' -import { describe, expect, it, beforeEach } from 'vitest' - -import { MemoryStore } from '../../../src/state/local/memory.js' -import { Network } from '@0xsequence/wallet-primitives' - -// Test addresses and data -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') -const TEST_SUBDIGEST = Hex.from('0xabcdef123456789012345678901234567890abcdef123456789012345678901234') - -describe('MemoryStore', () => { - let store: MemoryStore - - beforeEach(() => { - store = new MemoryStore() - }) - - describe('basic CRUD operations', () => { - it('should save and load configs', async () => { - const config = { test: 'data' } as any - - await store.saveConfig(TEST_IMAGE_HASH, config) - const retrieved = await store.loadConfig(TEST_IMAGE_HASH) - - expect(retrieved).toEqual(config) - }) - - it('should return undefined for non-existent config', async () => { - const retrieved = await store.loadConfig(TEST_IMAGE_HASH) - expect(retrieved).toBeUndefined() - }) - - it('should save and load counterfactual wallets', async () => { - const context = { test: 'context' } as any - - await store.saveCounterfactualWallet(TEST_ADDRESS, TEST_IMAGE_HASH, context) - const retrieved = await store.loadCounterfactualWallet(TEST_ADDRESS) - - expect(retrieved).toEqual({ - imageHash: TEST_IMAGE_HASH, - context, - }) - }) - - it('should save and load payloads', async () => { - const payload = { - content: { test: 'payload' } as any, - chainId: Network.ChainId.MAINNET, - wallet: TEST_ADDRESS, - } - - await store.savePayloadOfSubdigest(TEST_SUBDIGEST, payload) - const retrieved = await store.loadPayloadOfSubdigest(TEST_SUBDIGEST) - - expect(retrieved).toEqual(payload) - }) - - it('should save and load signatures', async () => { - const signature = { type: 'hash', r: 123n, s: 456n, yParity: 0 } as any - - await store.saveSignatureOfSubdigest(TEST_ADDRESS, TEST_SUBDIGEST, signature) - const retrieved = await store.loadSignatureOfSubdigest(TEST_ADDRESS, TEST_SUBDIGEST) - - expect(retrieved).toEqual(signature) - }) - - it('should save and load trees', async () => { - const tree = { test: 'tree' } as any - - await store.saveTree(TEST_IMAGE_HASH, tree) - const retrieved = await store.loadTree(TEST_IMAGE_HASH) - - expect(retrieved).toEqual(tree) - }) - }) - - describe('deep copy functionality', () => { - it('should create independent copies', async () => { - const originalData = { - content: { nested: { array: [1, 2, 3] } } as any, - chainId: Network.ChainId.MAINNET, - wallet: TEST_ADDRESS, - } - - await store.savePayloadOfSubdigest(TEST_SUBDIGEST, originalData) - const retrieved = await store.loadPayloadOfSubdigest(TEST_SUBDIGEST) - - // Should be equal but not the same reference - expect(retrieved).toEqual(originalData) - expect(retrieved).not.toBe(originalData) - }) - - it('should handle structuredClone fallback', async () => { - // Test the fallback when structuredClone is not available - const originalStructuredClone = global.structuredClone - delete (global as any).structuredClone - - const newStore = new MemoryStore() - const testData = { nested: { value: 'test' } } as any - - await newStore.saveConfig(TEST_IMAGE_HASH, testData) - const retrieved = await newStore.loadConfig(TEST_IMAGE_HASH) - - expect(retrieved).toEqual(testData) - expect(retrieved).not.toBe(testData) - - // Restore structuredClone - global.structuredClone = originalStructuredClone - }) - }) - - describe('key normalization', () => { - it('should normalize addresses to lowercase', async () => { - const upperAddress = TEST_ADDRESS.toUpperCase() as Address.Address - const context = { test: 'data' } as any - - await store.saveCounterfactualWallet(upperAddress, TEST_IMAGE_HASH, context) - const retrieved = await store.loadCounterfactualWallet(TEST_ADDRESS.toLowerCase() as Address.Address) - - expect(retrieved).toBeDefined() - expect(retrieved?.imageHash).toBe(TEST_IMAGE_HASH) - }) - - it('should normalize hex values to lowercase', async () => { - const upperHex = TEST_IMAGE_HASH.toUpperCase() as Hex.Hex - const config = { test: 'data' } as any - - await store.saveConfig(upperHex, config) - const retrieved = await store.loadConfig(TEST_IMAGE_HASH.toLowerCase() as Hex.Hex) - - expect(retrieved).toEqual(config) - }) - }) - - describe('signer subdigest tracking', () => { - it('should track subdigests for regular signers', async () => { - const signature = { type: 'hash', r: 123n, s: 456n, yParity: 0 } as any - const subdigest2 = Hex.from('0x1111111111111111111111111111111111111111111111111111111111111111') - - await store.saveSignatureOfSubdigest(TEST_ADDRESS, TEST_SUBDIGEST, signature) - await store.saveSignatureOfSubdigest(TEST_ADDRESS, subdigest2, signature) - - const subdigests = await store.loadSubdigestsOfSigner(TEST_ADDRESS) - - expect(subdigests).toHaveLength(2) - expect(subdigests).toContain(TEST_SUBDIGEST.toLowerCase()) - expect(subdigests).toContain(subdigest2.toLowerCase()) - }) - - it('should track subdigests for sapient signers', async () => { - const signature = { type: 'sapient', address: TEST_ADDRESS, data: '0x123' } as any - - await store.saveSapientSignatureOfSubdigest(TEST_ADDRESS, TEST_SUBDIGEST, TEST_IMAGE_HASH, signature) - - const subdigests = await store.loadSubdigestsOfSapientSigner(TEST_ADDRESS, TEST_IMAGE_HASH) - - expect(subdigests).toHaveLength(1) - expect(subdigests).toContain(TEST_SUBDIGEST.toLowerCase()) - }) - - it('should return empty arrays for non-existent signers', async () => { - const regularSubdigests = await store.loadSubdigestsOfSigner(TEST_ADDRESS) - const sapientSubdigests = await store.loadSubdigestsOfSapientSigner(TEST_ADDRESS, TEST_IMAGE_HASH) - - expect(regularSubdigests).toEqual([]) - expect(sapientSubdigests).toEqual([]) - }) - }) - - describe('edge cases', () => { - it('should handle overwriting data', async () => { - const config1 = { value: 1 } as any - const config2 = { value: 2 } as any - - await store.saveConfig(TEST_IMAGE_HASH, config1) - await store.saveConfig(TEST_IMAGE_HASH, config2) - - const retrieved = await store.loadConfig(TEST_IMAGE_HASH) - expect(retrieved).toEqual(config2) - }) - - it('should handle concurrent operations', async () => { - const promises: Promise[] = [] - - for (let i = 0; i < 10; i++) { - const imageHash = `0x${i.toString().padStart(64, '0')}` as Hex.Hex - const config = { value: i } as any - promises.push(store.saveConfig(imageHash, config)) - } - - await Promise.all(promises) - - // Verify all saves completed correctly - for (let i = 0; i < 10; i++) { - const imageHash = `0x${i.toString().padStart(64, '0')}` as Hex.Hex - const retrieved = await store.loadConfig(imageHash) - expect((retrieved as any)?.value).toBe(i) - } - }) - - it('should handle special characters and large values', async () => { - const specialData = { - content: { - emoji: '🎉📝✨', - large: 999999999999999999999999999999n, - null: null, - undefined: undefined, - } as any, - chainId: Network.ChainId.MAINNET, - wallet: TEST_ADDRESS, - } - - await store.savePayloadOfSubdigest(TEST_SUBDIGEST, specialData) - const retrieved = await store.loadPayloadOfSubdigest(TEST_SUBDIGEST) - - expect(retrieved).toEqual(specialData) - }) - }) -}) diff --git a/packages/wallet/core/test/state/utils.test.ts b/packages/wallet/core/test/state/utils.test.ts deleted file mode 100644 index 53771247ec..0000000000 --- a/packages/wallet/core/test/state/utils.test.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { Address, Hex } from 'ox' -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' - -import { getWalletsFor, normalizeAddressKeys } from '../../src/state/utils.js' -import type { Reader } from '../../src/state/index.js' -import type { Signer, SapientSigner } from '../../src/signers/index.js' -import { Network, Payload, Signature } from '@0xsequence/wallet-primitives' - -// Test addresses -const TEST_SIGNER_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_WALLET_ADDRESS_1 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') -const TEST_WALLET_ADDRESS_2 = Address.from('0x9876543210987654321098765432109876543210') -const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') - -// Mock data for testing -const mockPayload: Payload.Parented = { - type: 'call', - nonce: 1n, - space: 0n, - calls: [ - { - to: TEST_WALLET_ADDRESS_1, - value: 1000000000000000000n, - data: '0x12345678', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - parentWallets: [TEST_WALLET_ADDRESS_1], -} - -const mockRegularSignature: Signature.SignatureOfSignerLeaf = { - type: 'hash', - r: 123n, - s: 456n, - yParity: 0, -} - -const mockSapientSignature: Signature.SignatureOfSapientSignerLeaf = { - type: 'sapient', - address: TEST_SIGNER_ADDRESS, - data: '0xabcdef123456', -} - -describe('State Utils', () => { - // Mock console.warn to test warning messages - const originalWarn = console.warn - beforeEach(() => { - console.warn = vi.fn() - }) - afterEach(() => { - console.warn = originalWarn - }) - - describe('normalizeAddressKeys', () => { - it('should normalize lowercase addresses to checksum format', () => { - const input = { - '0x1234567890123456789012345678901234567890': 'signature1', - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd': 'signature2', - } - - const result = normalizeAddressKeys(input) - - // Check that addresses are properly checksummed - expect(result).toHaveProperty('0x1234567890123456789012345678901234567890', 'signature1') - expect(result).toHaveProperty('0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD', 'signature2') - }) - - it('should normalize uppercase addresses to checksum format', () => { - const input = { - '0x1234567890123456789012345678901234567890': 'signature1', - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd': 'signature2', - } - - const result = normalizeAddressKeys(input) - - expect(result).toHaveProperty('0x1234567890123456789012345678901234567890', 'signature1') - expect(result).toHaveProperty('0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD', 'signature2') - }) - - it('should handle mixed case addresses', () => { - const input = { - '0x1234567890aBcDeF1234567890123456789012Ab': 'signature1', - } - - const result = normalizeAddressKeys(input) - - // Should normalize to proper checksum - const normalizedKey = Object.keys(result)[0] - expect(normalizedKey).toMatch(/^0x[0-9a-fA-F]{40}$/) - expect(result[normalizedKey as Address.Address]).toBe('signature1') - }) - - it('should handle empty object', () => { - const input = {} - const result = normalizeAddressKeys(input) - expect(result).toEqual({}) - }) - - it('should preserve values for different value types', () => { - const input = { - '0x1234567890123456789012345678901234567890': { chainId: Network.ChainId.MAINNET, payload: mockPayload }, - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd': 'string-value', - '0x9876543210987654321098765432109876543210': 123, - } - - const result = normalizeAddressKeys(input) - - expect(Object.values(result)).toHaveLength(3) - expect(Object.values(result)).toContain(input['0x1234567890123456789012345678901234567890']) - expect(Object.values(result)).toContain('string-value') - expect(Object.values(result)).toContain(123) - }) - - it('should handle complex nested objects as values', () => { - const complexValue = { - chainId: 42, - payload: mockPayload, - signature: mockRegularSignature, - nested: { - deep: { - value: 'test', - }, - }, - } - - const input = { - '0x1234567890123456789012345678901234567890': complexValue, - } - - const result = normalizeAddressKeys(input) - - const normalizedAddress = Object.keys(result)[0] as Address.Address - expect(result[normalizedAddress]).toEqual(complexValue) - expect(result[normalizedAddress].nested.deep.value).toBe('test') - }) - }) - - describe('getWalletsFor', () => { - let mockStateReader: Reader - let mockSigner: Signer - let mockSapientSigner: SapientSigner - - beforeEach(() => { - // Mock isSapientSigner function - vi.mock('../../src/signers/index.js', async () => { - const actual = await vi.importActual('../../src/signers/index.js') - return { - ...actual, - isSapientSigner: vi.fn(), - } - }) - - // Create mock state reader - mockStateReader = { - getWallets: vi.fn(), - getWalletsForSapient: vi.fn(), - } as unknown as Reader - - // Create mock regular signer - mockSigner = { - address: Promise.resolve(TEST_SIGNER_ADDRESS), - sign: vi.fn(), - } as unknown as Signer - - // Create mock sapient signer - mockSapientSigner = { - address: Promise.resolve(TEST_SIGNER_ADDRESS), - imageHash: Promise.resolve(TEST_IMAGE_HASH), - signSapient: vi.fn(), - } as unknown as SapientSigner - }) - - afterEach(() => { - vi.clearAllMocks() - vi.resetModules() - }) - - it('should handle regular signer successfully', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(false) - - const mockWalletsData = { - [TEST_WALLET_ADDRESS_1]: { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockRegularSignature, - }, - [TEST_WALLET_ADDRESS_2]: { - chainId: 42, - payload: mockPayload, - signature: mockRegularSignature, - }, - } - - vi.mocked(mockStateReader.getWallets).mockResolvedValue(mockWalletsData) - - const result = await getWalletsFor(mockStateReader, mockSigner) - - expect(isSapientSigner).toHaveBeenCalledWith(mockSigner) - expect(mockStateReader.getWallets).toHaveBeenCalledWith(TEST_SIGNER_ADDRESS) - expect(result).toHaveLength(2) - - expect(result[0]).toEqual({ - wallet: TEST_WALLET_ADDRESS_1, - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockRegularSignature, - }) - - expect(result[1]).toEqual({ - wallet: TEST_WALLET_ADDRESS_2, - chainId: 42, - payload: mockPayload, - signature: mockRegularSignature, - }) - }) - - it('should handle sapient signer with imageHash successfully', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(true) - - const mockWalletsData = { - [TEST_WALLET_ADDRESS_1]: { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSapientSignature, - }, - } - - vi.mocked(mockStateReader.getWalletsForSapient).mockResolvedValue(mockWalletsData) - - const result = await getWalletsFor(mockStateReader, mockSapientSigner) - - expect(isSapientSigner).toHaveBeenCalledWith(mockSapientSigner) - expect(mockStateReader.getWalletsForSapient).toHaveBeenCalledWith(TEST_SIGNER_ADDRESS, TEST_IMAGE_HASH) - expect(result).toHaveLength(1) - - expect(result[0]).toEqual({ - wallet: TEST_WALLET_ADDRESS_1, - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSapientSignature, - }) - }) - - it('should handle sapient signer without imageHash (should warn and return empty)', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(true) - - const mockSapientSignerNoHash = { - address: Promise.resolve(TEST_SIGNER_ADDRESS), - imageHash: Promise.resolve(undefined), - signSapient: vi.fn(), - } as unknown as SapientSigner - - const result = await getWalletsFor(mockStateReader, mockSapientSignerNoHash) - - expect(isSapientSigner).toHaveBeenCalledWith(mockSapientSignerNoHash) - expect(console.warn).toHaveBeenCalledWith('Sapient signer has no imageHash') - expect(mockStateReader.getWalletsForSapient).not.toHaveBeenCalled() - expect(result).toEqual([]) - }) - - it('should handle empty wallets response', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(false) - - vi.mocked(mockStateReader.getWallets).mockResolvedValue({}) - - const result = await getWalletsFor(mockStateReader, mockSigner) - - expect(result).toEqual([]) - }) - - it('should handle promises for signer address properly', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(false) - - // Create a signer with delayed promise resolution - const delayedSigner = { - address: new Promise((resolve) => setTimeout(() => resolve(TEST_SIGNER_ADDRESS), 10)), - sign: vi.fn(), - } as unknown as Signer - - const mockWalletsData = { - [TEST_WALLET_ADDRESS_1]: { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockRegularSignature, - }, - } - - vi.mocked(mockStateReader.getWallets).mockResolvedValue(mockWalletsData) - - const result = await getWalletsFor(mockStateReader, delayedSigner) - - expect(mockStateReader.getWallets).toHaveBeenCalledWith(TEST_SIGNER_ADDRESS) - expect(result).toHaveLength(1) - }) - - it('should handle promises for sapient signer address and imageHash properly', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(true) - - // Create a sapient signer with delayed promise resolution - const delayedSapientSigner = { - address: new Promise((resolve) => setTimeout(() => resolve(TEST_SIGNER_ADDRESS), 10)), - imageHash: new Promise((resolve) => setTimeout(() => resolve(TEST_IMAGE_HASH), 15)), - signSapient: vi.fn(), - } as unknown as SapientSigner - - const mockWalletsData = { - [TEST_WALLET_ADDRESS_1]: { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockSapientSignature, - }, - } - - vi.mocked(mockStateReader.getWalletsForSapient).mockResolvedValue(mockWalletsData) - - const result = await getWalletsFor(mockStateReader, delayedSapientSigner) - - expect(mockStateReader.getWalletsForSapient).toHaveBeenCalledWith(TEST_SIGNER_ADDRESS, TEST_IMAGE_HASH) - expect(result).toHaveLength(1) - }) - - it('should validate wallet addresses with Hex.assert', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(false) - - // Mock data with invalid hex (this would normally cause Hex.assert to throw) - const mockWalletsDataWithInvalidHex = { - 'not-a-valid-hex-address': { - chainId: Network.ChainId.MAINNET, - payload: mockPayload, - signature: mockRegularSignature, - }, - } - - vi.mocked(mockStateReader.getWallets).mockResolvedValue(mockWalletsDataWithInvalidHex) - - // This should throw when Hex.assert is called on the invalid address - await expect(getWalletsFor(mockStateReader, mockSigner)).rejects.toThrow() - }) - - it('should preserve data types in transformation', async () => { - const { isSapientSigner } = await import('../../src/signers/index.js') - vi.mocked(isSapientSigner).mockReturnValue(false) - - const specificPayload: Payload.Parented = { - type: 'call', - nonce: 123n, - space: 456n, - calls: [ - { - to: TEST_WALLET_ADDRESS_2, - value: 999999999999999999n, - data: '0xabcdef123456789', - gasLimit: 50000n, - delegateCall: true, - onlyFallback: true, - behaviorOnError: 'ignore', - }, - ], - parentWallets: [TEST_WALLET_ADDRESS_1, TEST_WALLET_ADDRESS_2], - } - - const specificSignature: Signature.SignatureOfSignerLeaf = { - type: 'eth_sign', - r: 999n, - s: 888n, - yParity: 1, - } - - const mockWalletsData = { - [TEST_WALLET_ADDRESS_1]: { - chainId: Network.ChainId.ARBITRUM, - payload: specificPayload, - signature: specificSignature, - }, - } - - vi.mocked(mockStateReader.getWallets).mockResolvedValue(mockWalletsData) - - const result = await getWalletsFor(mockStateReader, mockSigner) - - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ - wallet: TEST_WALLET_ADDRESS_1, - chainId: Network.ChainId.ARBITRUM, - payload: specificPayload, - signature: specificSignature, - }) - - // Verify specific field preservation - if (result[0].payload.type === 'call') { - expect(result[0].payload.nonce).toBe(123n) - expect(result[0].payload.calls[0].delegateCall).toBe(true) - } - if (result[0].signature.type === 'eth_sign') { - expect(result[0].signature.r).toBe(999n) - expect(result[0].signature.yParity).toBe(1) - } - }) - }) -}) diff --git a/packages/wallet/core/test/utils/session/permission-builder.test.ts b/packages/wallet/core/test/utils/session/permission-builder.test.ts deleted file mode 100644 index ef03b89781..0000000000 --- a/packages/wallet/core/test/utils/session/permission-builder.test.ts +++ /dev/null @@ -1,767 +0,0 @@ -import { AbiFunction, Address, Bytes } from 'ox' -import { describe, expect, it } from 'vitest' - -import { Permission } from '../../../../primitives/src/index.js' -import { Utils } from '../../../src/index.js' -import { Constants } from '@0xsequence/wallet-primitives' - -const { PermissionBuilder } = Utils - -const TARGET = Address.from('0x1234567890123456789012345678901234567890') -const TARGET2 = Address.from('0x1234567890123456789012345678901234567891') -const UINT256_VALUE = 1000000000000000000n -const BYTES32_MAX = Bytes.fromHex('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') -const STRING_VALUE = - 'Chur bro, pack your togs and sunnies, we are heading to Taupo hot pools for a mean soak and a yarn, keen as' - -describe('PermissionBuilder', () => { - it('should build an unrestricted permission', () => { - expect(() => PermissionBuilder.for(TARGET).build()).toThrow() // Call allowAll() first - - const permission = PermissionBuilder.for(TARGET).allowAll().build() - expect(permission).toEqual({ - target: TARGET, - rules: [], - }) - }) - - it('should build an exact match permission', () => { - for (let i = 0; i < 10; i++) { - const calldata = Bytes.random(Math.floor(Math.random() * 100)) // Random calldata - console.log('random calldata', Bytes.toHex(calldata)) - const permission = PermissionBuilder.for(TARGET).exactCalldata(calldata).build() - for (let i = 0; i < permission.rules.length; i++) { - const rule = permission.rules[i] - expect(rule.cumulative).toEqual(false) - expect(rule.operation).toEqual(Permission.ParameterOperation.EQUAL) - expect(rule.offset).toEqual(BigInt(i * 32)) - if (i < permission.rules.length - 1) { - // Don't check the last rule as the mask may be different - expect(rule.mask).toEqual(Permission.MASK.BYTES32) - expect(rule.value).toEqual(calldata.slice(i * 32, (i + 1) * 32)) - } - } - // We should be able to decode the calldata from the rules - const decoded = Bytes.concat(...permission.rules.map((r) => r.value.map((b, i) => b & r.mask[i]!))) - expect(decoded).toEqual(Bytes.padRight(calldata, permission.rules.length * 32)) - } - }) - - it('should build a permission for transfer', () => { - const permission = PermissionBuilder.for(TARGET).forFunction('transfer(address to, uint256 value)').build() - expect(permission).toEqual({ - target: TARGET, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }) - }) - - it('should build a permission for transfer only allowed once', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('transfer(address to, uint256 value)') - .onlyOnce() - .build() - expect(permission).toEqual({ - target: TARGET, - rules: [ - { - cumulative: true, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - ], - }) - }) - - it('should build a permission for transfer with a uint256 param', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('transfer(address to, uint256 value)') - .withUintNParam('value', UINT256_VALUE, 256, Permission.ParameterOperation.LESS_THAN_OR_EQUAL) - .build() - // Check - expect(permission).toEqual({ - target: TARGET, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - { - cumulative: false, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromNumber(UINT256_VALUE, { size: 32 }), - offset: 4n + 32n, - mask: Permission.MASK.UINT256, - }, - ], - }) - // Check the offset matches the encoding by ox - const abi = AbiFunction.from('function transfer(address to, uint256 value)') - const encodedData = AbiFunction.encodeData(abi, [Constants.ZeroAddress, Bytes.toBigInt(BYTES32_MAX)]) - const encodedDataBytes = Bytes.fromHex(encodedData) - const maskedHex = encodedDataBytes - .slice(Number(permission.rules[1].offset), Number(permission.rules[1].offset) + 32) - .map((b, i) => b & permission.rules[1].mask[i]!) - expect(maskedHex).toEqual(BYTES32_MAX) - }) - - it('should build a permission for transfer with an address param', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('transfer(address to, uint256 value)') - .withAddressParam('to', TARGET2) - .build() - // Check - expect(permission).toEqual({ - target: TARGET, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.concat(Bytes.fromHex('0x000000000000000000000000'), Bytes.fromHex(TARGET2)), - offset: 4n, - mask: Permission.MASK.ADDRESS, - }, - ], - }) - // Check the offset matches the encoding by ox - const abi = AbiFunction.from('function transfer(address to, uint256 value)') - const encodedData = AbiFunction.encodeData(abi, ['0xffffffffffffffffffffffffffffffffffffffff', 0n]) - const encodedDataBytes = Bytes.fromHex(encodedData) - const maskedHex = encodedDataBytes - .slice(Number(permission.rules[1].offset), Number(permission.rules[1].offset) + 32) - .map((b, i) => b & permission.rules[1].mask[i]!) - expect(Bytes.toHex(maskedHex)).toEqual('0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff') - }) - - it('should build a permission on a signature with a bool param', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('function foo(bytes data, bool flag)') - .withBoolParam('flag', true) - .build() - // Check - expect(permission).toEqual({ - target: TARGET, - rules: [ - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa8889a95'), 32), // cast sig "function foo(bytes,bool)" - offset: 0n, - mask: Permission.MASK.SELECTOR, - }, - { - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(1n, { size: 32 }), - offset: 4n + 32n, - mask: Permission.MASK.BOOL, - }, - ], - }) - // Check the offset matches the encoding by ox - const abi = AbiFunction.from('function foo(bytes data, bool flag)') - const encodedData = AbiFunction.encodeData(abi, [Constants.ZeroAddress, true]) - const encodedDataBytes = Bytes.fromHex(encodedData) - const maskedHex = encodedDataBytes - .slice(Number(permission.rules[1].offset), Number(permission.rules[1].offset) + 32) - .map((b, i) => b & permission.rules[1].mask[i]!) - expect(Bytes.toBoolean(maskedHex, { size: 32 })).toEqual(true) - const encodedData2 = AbiFunction.encodeData(abi, [Constants.ZeroAddress, false]) - const encodedDataBytes2 = Bytes.fromHex(encodedData2) - const maskedHex2 = encodedDataBytes2 - .slice(Number(permission.rules[1].offset), Number(permission.rules[1].offset) + 32) - .map((b, i) => b & permission.rules[1].mask[i]!) - expect(Bytes.toBoolean(maskedHex2, { size: 32 })).toEqual(false) - }) - - it('should build a permission on a signature with a dynamic string param', () => { - const strLen = Bytes.fromString(STRING_VALUE).length - const permission = PermissionBuilder.for(TARGET) - .forFunction('function foo(string data, bool flag)') - .withStringParam('data', STRING_VALUE) - .build() - - // Selector - expect(permission.target).toEqual(TARGET) - expect(permission.rules.length).toEqual(Math.ceil(strLen / 32) + 3) // Selector, pointer, data size, data chunks - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xb91c339f'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - // Pointer - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(32n + 32n, { size: 32 }), // Pointer value excludes selector - offset: 4n, - mask: Permission.MASK.UINT256, - }) - // Data size - expect(permission.rules[2]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(BigInt(strLen), { size: 32 }), - offset: 4n + 32n + 32n, // Pointer offset includes selector - mask: Permission.MASK.UINT256, - }) - // We should be able to decode the required string from the rules - const dataSize = Bytes.toBigInt(permission.rules[2].value) - const ruleBytes = Bytes.concat(...permission.rules.slice(3).map((r) => r.value)).slice(0, Number(dataSize)) - const decoded = Bytes.toString(ruleBytes) - expect(decoded).toEqual(STRING_VALUE) - - // Check the offset matches the encoding by ox - const abi = AbiFunction.from('function foo(string data, bool flag)') - const encodedData = AbiFunction.encodeData(abi, [STRING_VALUE, true]) - const encodedDataBytes = Bytes.fromHex(encodedData) - for (let i = 0; i < permission.rules.length; i++) { - const maskedHex = encodedDataBytes - .slice(Number(permission.rules[i].offset), Number(permission.rules[i].offset) + 32) - .map((b, j) => b & permission.rules[i].mask[j]!) - expect(Bytes.toHex(maskedHex)).toEqual(Bytes.toHex(permission.rules[i].value)) - } - }) - - it('should not support encoding dynamic params with multiple in signature', () => { - expect(() => - PermissionBuilder.for(TARGET) - .forFunction('function foo(string data, bool flag, string data2)') - .withStringParam('data2', STRING_VALUE) - .build(), - ).toThrow() - }) - - it('should error when the param name or index is invalid', () => { - expect(() => - PermissionBuilder.for(TARGET) - .forFunction('function foo(bytes data, bool flag)') - .withBoolParam('flag2', true) - .build(), - ).toThrow() - expect(() => - PermissionBuilder.for(TARGET) - .forFunction('function foo(bytes data, bool flag)') - .withBoolParam('data', true) - .build(), - ).toThrow() - expect(() => - PermissionBuilder.for(TARGET).forFunction('function foo(bytes data, bool flag)').withBoolParam(0, true).build(), - ).toThrow() - expect(() => - PermissionBuilder.for(TARGET).forFunction('function foo(bytes data, bool flag)').withBoolParam(2, true).build(), - ).toThrow() - expect(() => - PermissionBuilder.for(TARGET).forFunction('function foo(bytes,bool)').withBoolParam('flag', true).build(), - ).toThrow() - const abiFunc = AbiFunction.from('function foo(bytes data, bool flag)') - expect(() => PermissionBuilder.for(TARGET).forFunction(abiFunc).withBoolParam('flag2', true).build()).toThrow() - expect(() => PermissionBuilder.for(TARGET).forFunction(abiFunc).withBoolParam('data', true).build()).toThrow() - expect(() => PermissionBuilder.for(TARGET).forFunction(abiFunc).withBoolParam(0, true).build()).toThrow() - expect(() => PermissionBuilder.for(TARGET).forFunction(abiFunc).withBoolParam(2, true).build()).toThrow() - }) - - // Additional tests for 100% coverage - - it('should build a permission with dynamic bytes param', () => { - const bytesValue = Bytes.fromHex('0x1234567890abcdef') - const permission = PermissionBuilder.for(TARGET) - .forFunction('function foo(bytes data, bool flag)') - .withBytesParam('data', bytesValue) - .build() - - expect(permission.target).toEqual(TARGET) - expect(permission.rules.length).toEqual(4) // Selector, pointer, data size, data chunk - - // Check selector - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa8889a95'), 32), - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check pointer - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(64n, { size: 32 }), // Points to start of dynamic data - offset: 4n, - mask: Permission.MASK.UINT256, - }) - - // Check data length - expect(permission.rules[2]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(BigInt(bytesValue.length), { size: 32 }), - offset: 4n + 64n, - mask: Permission.MASK.UINT256, - }) - - // Check data chunk - expect(permission.rules[3]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(bytesValue, 32), - offset: 4n + 64n + 32n, - mask: Permission.MASK.BYTES32, - }) - }) - - it('should test different uint bit sizes', () => { - const builder = PermissionBuilder.for(TARGET).forFunction( - 'function test(uint8 a, uint16 b, uint32 c, uint64 d, uint128 e)', - ) - - // Test uint8 - let permission = builder.withUintNParam('a', 255n, 8).build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.UINT8) - - // Test uint16 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint8 a, uint16 b, uint32 c, uint64 d, uint128 e)') - .withUintNParam('b', 65535n, 16) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.UINT16) - - // Test uint32 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint8 a, uint16 b, uint32 c, uint64 d, uint128 e)') - .withUintNParam('c', 4294967295n, 32) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.UINT32) - - // Test uint64 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint8 a, uint16 b, uint32 c, uint64 d, uint128 e)') - .withUintNParam('d', 18446744073709551615n, 64) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.UINT64) - - // Test uint128 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint8 a, uint16 b, uint32 c, uint64 d, uint128 e)') - .withUintNParam('e', 340282366920938463463374607431768211455n, 128) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.UINT128) - }) - - it('should test different int bit sizes', () => { - // Test int8 - use positive values since Bytes.fromNumber doesn't handle negative - let permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int8 a)') - .withIntNParam('a', 127n, 8) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT8) - - // Test int16 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int16 a)') - .withIntNParam('a', 32767n, 16) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT16) - - // Test int32 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int32 a)') - .withIntNParam('a', 2147483647n, 32) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT32) - - // Test int64 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int64 a)') - .withIntNParam('a', 9223372036854775807n, 64) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT64) - - // Test int128 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int128 a)') - .withIntNParam('a', 170141183460469231731687303715884105727n, 128) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT128) - - // Test int256 (default) - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(int256 a)') - .withIntNParam('a', 57896044618658097711785492504343953926634992332820282019728792003956564819967n) // Use positive value - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.INT256) - }) - - it('should test different bytesN sizes', () => { - // Test bytes1 - let permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes1 a)') - .withBytesNParam('a', Bytes.fromHex('0x12'), 1) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES1) - - // Test bytes2 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes2 a)') - .withBytesNParam('a', Bytes.fromHex('0x1234'), 2) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES2) - - // Test bytes4 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes4 a)') - .withBytesNParam('a', Bytes.fromHex('0x12345678'), 4) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES4) - - // Test bytes8 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes8 a)') - .withBytesNParam('a', Bytes.fromHex('0x1234567890abcdef'), 8) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES8) - - // Test bytes16 - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes16 a)') - .withBytesNParam('a', Bytes.fromHex('0x1234567890abcdef1234567890abcdef'), 16) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES16) - - // Test bytes32 (default) - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bytes32 a)') - .withBytesNParam('a', Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')) - .build() - expect(permission.rules[1].mask).toEqual(Permission.MASK.BYTES32) - }) - - it('should test cumulative parameter rules', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('function transfer(address to, uint256 value)') - .withUintNParam('value', UINT256_VALUE, 256, Permission.ParameterOperation.LESS_THAN_OR_EQUAL, true) - .build() - - expect(permission.rules[1].cumulative).toBe(true) - }) - - it('should test different parameter operations', () => { - // Test NOT_EQUAL - let permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint256 a)') - .withUintNParam('a', 100n, 256, Permission.ParameterOperation.NOT_EQUAL) - .build() - expect(permission.rules[1].operation).toEqual(Permission.ParameterOperation.NOT_EQUAL) - - // Test GREATER_THAN_OR_EQUAL - permission = PermissionBuilder.for(TARGET) - .forFunction('function test(uint256 a)') - .withUintNParam('a', 100n, 256, Permission.ParameterOperation.GREATER_THAN_OR_EQUAL) - .build() - expect(permission.rules[1].operation).toEqual(Permission.ParameterOperation.GREATER_THAN_OR_EQUAL) - }) - - it('should test bool param with false value', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('function test(bool flag)') - .withBoolParam('flag', false) - .build() - - expect(permission.rules[1].value).toEqual(Bytes.fromNumber(0n, { size: 32 })) - }) - - it('should test address param with different operations', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('function test(address addr)') - .withAddressParam('addr', TARGET2, Permission.ParameterOperation.NOT_EQUAL) - .build() - - expect(permission.rules[1].operation).toEqual(Permission.ParameterOperation.NOT_EQUAL) - }) - - it('should test parameter access by index', () => { - const permission = PermissionBuilder.for(TARGET) - .forFunction('function test(address to, uint256 value)') - .withUintNParam(1, UINT256_VALUE) // Access second parameter by index - .build() - - expect(permission.rules[1].offset).toEqual(4n + 32n) // Second parameter offset - }) - - it('should test AbiFunction input', () => { - const abiFunc = AbiFunction.from('function transfer(address to, uint256 value)') - const permission = PermissionBuilder.for(TARGET).forFunction(abiFunc).build() - - expect(permission.rules[0].value).toEqual(Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32)) - }) - - it('should test error cases', () => { - // Test calling allowAll after adding rules - expect(() => - PermissionBuilder.for(TARGET) - .forFunction('function test(uint256 a)') // Use valid function signature - .allowAll(), - ).toThrow('cannot call allowAll() after adding rules') - - // Test calling exactCalldata after allowAll - expect(() => PermissionBuilder.for(TARGET).allowAll().exactCalldata(Bytes.fromHex('0x1234'))).toThrow( - 'cannot call exactCalldata() after calling allowAll() or adding rules', - ) - - // Test calling forFunction after allowAll - expect(() => PermissionBuilder.for(TARGET).allowAll().forFunction('function test(uint256 a)')).toThrow( - 'cannot call forFunction(...) after calling allowAll() or exactCalldata()', - ) - - // Test calling forFunction after exactCalldata - expect(() => - PermissionBuilder.for(TARGET).exactCalldata(Bytes.fromHex('0x1234')).forFunction('function test(uint256 a)'), - ).toThrow('cannot call forFunction(...) after calling allowAll() or exactCalldata()') - - // Test calling onlyOnce without rules - expect(() => PermissionBuilder.for(TARGET).onlyOnce()).toThrow( - 'must call forFunction(...) before calling onlyOnce()', - ) - - // Test calling onlyOnce without selector rule - expect(() => PermissionBuilder.for(TARGET).exactCalldata(Bytes.fromHex('0x1234')).onlyOnce()).toThrow( - 'can call onlyOnce() after adding rules that match the selector', - ) - - // Test calling parameter methods before forFunction - expect(() => PermissionBuilder.for(TARGET).withUintNParam('value', 100n)).toThrow( - 'must call forFunction(...) first', - ) - - expect(() => PermissionBuilder.for(TARGET).withAddressParam('addr', TARGET2)).toThrow( - 'must call forFunction(...) first', - ) - - expect(() => PermissionBuilder.for(TARGET).withBoolParam('flag', true)).toThrow('must call forFunction(...) first') - }) - - it('should test parseSignature edge cases', () => { - // Test function with no parameters - should now work after bug fix - const permission = PermissionBuilder.for(TARGET).forFunction('function test()').build() - expect(permission.rules).toHaveLength(1) // Only selector rule - - // Test function with unnamed parameters - expect(() => - PermissionBuilder.for(TARGET).forFunction('function test(uint256)').withUintNParam('value', 100n), - ).toThrow() // Should fail because parameter has no name - }) -}) - -describe('ERC20PermissionBuilder', () => { - it('should build transfer permission', () => { - const limit = 1000000000000000000n // 1 token - const permission = Utils.ERC20PermissionBuilder.buildTransfer(TARGET, limit) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(2) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa9059cbb'), 32), // transfer selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check value limit rule - expect(permission.rules[1]).toEqual({ - cumulative: true, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromNumber(limit, { size: 32 }), - offset: 4n + 32n, // Second parameter (value) - mask: Permission.MASK.UINT256, - }) - }) - - it('should build approve permission', () => { - const spender = TARGET2 - const limit = 1000000000000000000n // 1 token - const permission = Utils.ERC20PermissionBuilder.buildApprove(TARGET, spender, limit) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(3) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0x095ea7b3'), 32), // approve selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check spender rule - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.concat(Bytes.fromHex('0x000000000000000000000000'), Bytes.fromHex(spender)), - offset: 4n, // First parameter (spender) - mask: Permission.MASK.ADDRESS, - }) - - // Check value limit rule - expect(permission.rules[2]).toEqual({ - cumulative: true, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromNumber(limit, { size: 32 }), - offset: 4n + 32n, // Second parameter (value) - mask: Permission.MASK.UINT256, - }) - }) -}) - -describe('ERC721PermissionBuilder', () => { - it('should build transfer permission', () => { - const tokenId = 123n - const permission = Utils.ERC721PermissionBuilder.buildTransfer(TARGET, tokenId) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(2) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0x23b872dd'), 32), // transferFrom selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check tokenId rule - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(tokenId, { size: 32 }), - offset: 4n + 64n, // Third parameter (tokenId) - mask: Permission.MASK.UINT256, - }) - }) - - it('should build approve permission', () => { - const spender = TARGET2 - const tokenId = 123n - const permission = Utils.ERC721PermissionBuilder.buildApprove(TARGET, spender, tokenId) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(3) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0x095ea7b3'), 32), // approve selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check spender rule - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.concat(Bytes.fromHex('0x000000000000000000000000'), Bytes.fromHex(spender)), - offset: 4n, // First parameter (spender) - mask: Permission.MASK.ADDRESS, - }) - - // Check tokenId rule - expect(permission.rules[2]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(tokenId, { size: 32 }), - offset: 4n + 32n, // Second parameter (tokenId) - mask: Permission.MASK.UINT256, - }) - }) -}) - -describe('ERC1155PermissionBuilder', () => { - it('should build transfer permission', () => { - // Bug is now fixed - should work correctly - const tokenId = 123n - const limit = 10n - const permission = Utils.ERC1155PermissionBuilder.buildTransfer(TARGET, tokenId, limit) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(3) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xf242432a'), 32), // safeTransferFrom selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check tokenId rule (now correctly uses 'id' parameter) - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromNumber(tokenId, { size: 32 }), - offset: 4n + 64n, // Third parameter (id) - mask: Permission.MASK.UINT256, - }) - - // Check amount rule - expect(permission.rules[2]).toEqual({ - cumulative: true, - operation: Permission.ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromNumber(limit, { size: 32 }), - offset: 4n + 96n, // Fourth parameter (amount) - mask: Permission.MASK.UINT256, - }) - }) - - it('should build approve all permission', () => { - const operator = TARGET2 - const permission = Utils.ERC1155PermissionBuilder.buildApproveAll(TARGET, operator) - - expect(permission.target).toEqual(TARGET) - expect(permission.rules).toHaveLength(2) - - // Check selector rule - expect(permission.rules[0]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.padRight(Bytes.fromHex('0xa22cb465'), 32), // setApprovalForAll selector - offset: 0n, - mask: Permission.MASK.SELECTOR, - }) - - // Check operator rule - expect(permission.rules[1]).toEqual({ - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.concat(Bytes.fromHex('0x000000000000000000000000'), Bytes.fromHex(operator)), - offset: 4n, // First parameter (operator) - mask: Permission.MASK.ADDRESS, - }) - }) -}) diff --git a/packages/wallet/core/test/wallet.test.ts b/packages/wallet/core/test/wallet.test.ts deleted file mode 100644 index 1a58b478b4..0000000000 --- a/packages/wallet/core/test/wallet.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { Address, Hash, Hex, Provider, RpcTransport, Secp256k1, TypedData } from 'ox' -import { describe, expect, it } from 'vitest' - -import { Constants, Config, Erc6492, Payload } from '../../primitives/src/index.js' -import { Envelope, State, Wallet } from '../src/index.js' -import { LOCAL_RPC_URL } from './constants.js' - -describe('Wallet', async () => { - const stateProvider = new State.Local.Provider() - - const createRandomSigner = () => { - const privateKey = Secp256k1.randomPrivateKey() - const address = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })) - return { address, privateKey } - } - - const getWallet = async (config: Config.Config, provider: Provider.Provider, deployed: boolean) => { - const wallet = await Wallet.fromConfiguration(config, { stateProvider }) - if (deployed && !(await wallet.isDeployed(provider))) { - // Deploy it - const deployTransaction = await wallet.buildDeployTransaction() - const deployResult = await provider.request({ - method: 'eth_sendTransaction', - params: [deployTransaction], - }) - await new Promise((resolve) => setTimeout(resolve, 3000)) - await provider.request({ - method: 'eth_getTransactionReceipt', - params: [deployResult], - }) - } - const isDeployed = await wallet.isDeployed(provider) - expect(isDeployed).toBe(deployed) - return wallet - } - - const types = ['deployed', 'not-deployed'] - - for (const type of types) { - describe(type, async () => { - it('should sign a message', async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const signer = createRandomSigner() - const wallet = await getWallet( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: signer.address, weight: 1n }, - }, - provider, - type === 'deployed', - ) - - const message = Hex.fromString('Hello, world!') - const encodedMessage = Hex.concat( - Hex.fromString(`${`\x19Ethereum Signed Message:\n${Hex.size(message)}`}`), - message, - ) - const messageHash = Hash.keccak256(encodedMessage) - - const envelope = await wallet.prepareMessageSignature(message, chainId) - const payloadHash = Payload.hash(wallet.address, chainId, envelope.payload) - - // Sign it - const signerSignature = Secp256k1.sign({ - payload: payloadHash, - privateKey: signer.privateKey, - }) - const signedEnvelope = Envelope.toSigned(envelope, [ - { - address: signer.address, - signature: { - type: 'hash', - ...signerSignature, - }, - }, - ]) - - // Encode it - const signature = await wallet.buildMessageSignature(signedEnvelope, provider) - - // Validate off chain with ERC-6492 - const isValid = await Erc6492.isValid(wallet.address, messageHash, signature, provider) - expect(isValid).toBe(true) - }, 30000) - - it('should sign a typed data message', async () => { - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - const chainId = Number(await provider.request({ method: 'eth_chainId' })) - - const signer = createRandomSigner() - const wallet = await getWallet( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: signer.address, weight: 1n }, - }, - provider, - type === 'deployed', - ) - - const message = { - domain: { - name: 'MyApp', - version: '1', - chainId: Number(chainId), - verifyingContract: Constants.ZeroAddress, - }, - types: { - Mail: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail' as const, - message: { - from: Constants.ZeroAddress, - to: Constants.ZeroAddress, - contents: 'Hello, Bob!', - }, - } - - const data = TypedData.encode(message) - const messageHash = Hash.keccak256(data) - - const envelope = await wallet.prepareMessageSignature(message, chainId) - const payloadHash = Payload.hash(wallet.address, chainId, envelope.payload) - - // Sign it - const signerSignature = Secp256k1.sign({ - payload: payloadHash, - privateKey: signer.privateKey, - }) - const signedEnvelope = Envelope.toSigned(envelope, [ - { - address: signer.address, - signature: { - type: 'hash', - ...signerSignature, - }, - }, - ]) - - // Encode it - const signature = await wallet.buildMessageSignature(signedEnvelope, provider) - - // Validate off chain with ERC-6492 - const isValid = await Erc6492.isValid(wallet.address, messageHash, signature, provider) - expect(isValid).toBe(true) - }, 30000) - }) - } - - it('Should reject unsafe wallet creation', async () => { - // Threshold 0 - const walletPromise1 = Wallet.fromConfiguration( - { - threshold: 0n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise1).rejects.toThrow('threshold-0') - - // Weight too high - const walletPromise2 = Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 256n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise2).rejects.toThrow('invalid-values') - - // Threshold too high - const walletPromise3 = Wallet.fromConfiguration( - { - threshold: 65536n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise3).rejects.toThrow('unsafe-invalid-values') - - // Checkpoint too high - const walletPromise4 = Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 72057594037927936n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise4).rejects.toThrow('unsafe-invalid-values') - - // Unreachable threshold - const walletPromise5 = Wallet.fromConfiguration( - { - threshold: 2n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise5).rejects.toThrow('unsafe-threshold') - - // Topology too deep (more than 32 levels) - let topology: Config.Topology = { - type: 'signer', - address: Constants.ZeroAddress, - weight: 1n, - } - - for (let i = 0; i < 33; i++) { - topology = [ - topology, - { - type: 'signer', - address: Constants.ZeroAddress, - weight: 1n, - }, - ] - } - - const walletPromise6 = Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise6).rejects.toThrow('unsafe-depth') - }) - - it('Should reject unsafe wallet update', async () => { - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - // Threshold 0 - const walletUpdatePromise1 = wallet.prepareUpdate({ - threshold: 0n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }) - - await expect(walletUpdatePromise1).rejects.toThrow('unsafe-threshold-0') - - // Weight too high - const walletUpdatePromise2 = wallet.prepareUpdate({ - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 256n }, - }) - - await expect(walletUpdatePromise2).rejects.toThrow('unsafe-invalid-values') - - // Threshold too high - const walletUpdatePromise3 = wallet.prepareUpdate({ - threshold: 65536n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }) - - await expect(walletUpdatePromise3).rejects.toThrow('unsafe-invalid-values') - - // Checkpoint too high - const walletUpdatePromise4 = wallet.prepareUpdate({ - threshold: 1n, - checkpoint: 72057594037927936n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }) - - await expect(walletUpdatePromise4).rejects.toThrow('unsafe-invalid-values') - - // Unreachable threshold - const walletPromise5 = Wallet.fromConfiguration( - { - threshold: 2n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - await expect(walletPromise5).rejects.toThrow('unsafe-threshold') - - // Topology too deep (more than 32 levels) - let topology: Config.Topology = { - type: 'signer', - address: Constants.ZeroAddress, - weight: 1n, - } - - for (let i = 0; i < 33; i++) { - topology = [ - topology, - { - type: 'signer', - address: Constants.ZeroAddress, - weight: 1n, - }, - ] - } - - const walletUpdatePromise6 = wallet.prepareUpdate({ - threshold: 1n, - checkpoint: 0n, - topology, - }) - - await expect(walletUpdatePromise6).rejects.toThrow('unsafe-depth') - }) - - it('Should accept unsafe wallet creation in unsafe mode', async () => { - const wallet = await Wallet.fromConfiguration( - { - threshold: 0n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - unsafe: true, - }, - ) - - expect(wallet).toBeDefined() - }) - - it('Should accept unsafe wallet update in unsafe mode', async () => { - const wallet = await Wallet.fromConfiguration( - { - threshold: 1n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - stateProvider, - }, - ) - - expect(wallet).toBeDefined() - - const walletUpdate = await wallet.prepareUpdate( - { - threshold: 0n, - checkpoint: 0n, - topology: { type: 'signer', address: Constants.ZeroAddress, weight: 1n }, - }, - { - unsafe: true, - }, - ) - - expect(walletUpdate).toBeDefined() - }) -}) diff --git a/packages/wallet/core/tsconfig.json b/packages/wallet/core/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/wallet/core/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/wallet/dapp-client/CHANGELOG.md b/packages/wallet/dapp-client/CHANGELOG.md deleted file mode 100644 index 2039d0c2d2..0000000000 --- a/packages/wallet/dapp-client/CHANGELOG.md +++ /dev/null @@ -1,314 +0,0 @@ -# @0xsequence/dapp-client - -## 3.0.5 - -### Patch Changes - -- Account federation support -- Updated dependencies - - @0xsequence/guard@3.0.5 - - @0xsequence/relayer@3.0.5 - - @0xsequence/wallet-core@3.0.5 - - @0xsequence/wallet-primitives@3.0.5 - -## 3.0.4 - -### Patch Changes - -- id-token login support -- Updated dependencies - - @0xsequence/guard@3.0.4 - - @0xsequence/relayer@3.0.4 - - @0xsequence/wallet-core@3.0.4 - - @0xsequence/wallet-primitives@3.0.4 - -## 3.0.3 - -### Patch Changes - -- 3.0.3 -- Updated dependencies - - @0xsequence/guard@3.0.3 - - @0xsequence/relayer@3.0.3 - - @0xsequence/wallet-core@3.0.3 - - @0xsequence/wallet-primitives@3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer -- Updated dependencies - - @0xsequence/guard@3.0.2 - - @0xsequence/relayer@3.0.2 - - @0xsequence/wallet-core@3.0.2 - - @0xsequence/wallet-primitives@3.0.2 - -## 3.0.1 - -### Patch Changes - -- Network and session fixes -- Updated dependencies - - @0xsequence/guard@3.0.1 - - @0xsequence/relayer@3.0.1 - - @0xsequence/wallet-core@3.0.1 - - @0xsequence/wallet-primitives@3.0.1 - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade -- Updated dependencies [f68be62] -- Updated dependencies [49d8a2f] -- Updated dependencies [3411232] -- Updated dependencies [23cb9e9] -- Updated dependencies [f5f6a7a] -- Updated dependencies [e7de3b1] -- Updated dependencies [493836f] -- Updated dependencies [30e1f1a] -- Updated dependencies [d5017e8] -- Updated dependencies [24a5fab] -- Updated dependencies [e5e1a03] -- Updated dependencies [0b63113] -- Updated dependencies [a89134a] -- Updated dependencies [7c6c811] -- Updated dependencies -- Updated dependencies [98ce38b] -- Updated dependencies [747e6b5] -- Updated dependencies [40c19ff] -- Updated dependencies [6d5de25] -- Updated dependencies [934acd1] - - @0xsequence/guard@3.0.0 - - @0xsequence/relayer@3.0.0 - - @0xsequence/wallet-core@3.0.0 - - @0xsequence/wallet-primitives@3.0.0 - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.19 - - @0xsequence/relayer@3.0.0-beta.19 - - @0xsequence/wallet-core@3.0.0-beta.19 - - @0xsequence/wallet-primitives@3.0.0-beta.19 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.18 - - @0xsequence/relayer@3.0.0-beta.18 - - @0xsequence/wallet-core@3.0.0-beta.18 - - @0xsequence/wallet-primitives@3.0.0-beta.18 - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.17 - - @0xsequence/relayer@3.0.0-beta.17 - - @0xsequence/wallet-core@3.0.0-beta.17 - - @0xsequence/wallet-primitives@3.0.0-beta.17 - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.16 - - @0xsequence/relayer@3.0.0-beta.16 - - @0xsequence/wallet-core@3.0.0-beta.16 - - @0xsequence/wallet-primitives@3.0.0-beta.16 - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.15 - - @0xsequence/relayer@3.0.0-beta.15 - - @0xsequence/wallet-core@3.0.0-beta.15 - - @0xsequence/wallet-primitives@3.0.0-beta.15 - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.14 - - @0xsequence/relayer@3.0.0-beta.14 - - @0xsequence/wallet-core@3.0.0-beta.14 - - @0xsequence/wallet-primitives@3.0.0-beta.14 - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.13 - - @0xsequence/relayer@3.0.0-beta.13 - - @0xsequence/wallet-core@3.0.0-beta.13 - - @0xsequence/wallet-primitives@3.0.0-beta.13 - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.12 - - @0xsequence/relayer@3.0.0-beta.12 - - @0xsequence/wallet-core@3.0.0-beta.12 - - @0xsequence/wallet-primitives@3.0.0-beta.12 - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.11 - - @0xsequence/relayer@3.0.0-beta.11 - - @0xsequence/wallet-core@3.0.0-beta.11 - - @0xsequence/wallet-primitives@3.0.0-beta.11 - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.10 - - @0xsequence/relayer@3.0.0-beta.10 - - @0xsequence/wallet-core@3.0.0-beta.10 - - @0xsequence/wallet-primitives@3.0.0-beta.10 - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.9 - - @0xsequence/relayer@3.0.0-beta.9 - - @0xsequence/wallet-core@3.0.0-beta.9 - - @0xsequence/wallet-primitives@3.0.0-beta.9 - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.8 - - @0xsequence/relayer@3.0.0-beta.8 - - @0xsequence/wallet-core@3.0.0-beta.8 - - @0xsequence/wallet-primitives@3.0.0-beta.8 - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.7 - - @0xsequence/relayer@3.0.0-beta.7 - - @0xsequence/wallet-core@3.0.0-beta.7 - - @0xsequence/wallet-primitives@3.0.0-beta.7 - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.6 - - @0xsequence/relayer@3.0.0-beta.6 - - @0xsequence/wallet-core@3.0.0-beta.6 - - @0xsequence/wallet-primitives@3.0.0-beta.6 - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.5 - - @0xsequence/relayer@3.0.0-beta.5 - - @0xsequence/wallet-core@3.0.0-beta.5 - - @0xsequence/wallet-primitives@3.0.0-beta.5 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.4 - - @0xsequence/relayer@3.0.0-beta.4 - - @0xsequence/wallet-core@3.0.0-beta.4 - - @0xsequence/wallet-primitives@3.0.0-beta.4 - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.3 - - @0xsequence/relayer@3.0.0-beta.3 - - @0xsequence/wallet-core@3.0.0-beta.3 - - @0xsequence/wallet-primitives@3.0.0-beta.3 - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.2 - - @0xsequence/relayer@3.0.0-beta.2 - - @0xsequence/wallet-core@3.0.0-beta.2 - - @0xsequence/wallet-primitives@3.0.0-beta.2 - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.1 - - @0xsequence/relayer@3.0.0-beta.1 - - @0xsequence/wallet-core@3.0.0-beta.1 - - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/dapp-client/README.md b/packages/wallet/dapp-client/README.md deleted file mode 100644 index b29776fd1c..0000000000 --- a/packages/wallet/dapp-client/README.md +++ /dev/null @@ -1,238 +0,0 @@ -# @0xsequence/dapp-client - -## 1. Overview - -The `DappClient` is the main entry point for interacting with the Sequence Wallet from any decentralized application (dapp). It provides a high-level, developer-friendly API to manage user sessions across multiple blockchains. - -This client simplifies complex wallet interactions such as connecting a user, sending transactions, and signing messages, while handling different communication methods (popup vs. redirect) and session types (implicit vs. explicit) under the hood. - -### Core Concepts - -- **Multichain by Design:** A single client instance manages connections to multiple blockchains simultaneously. -- **Implicit vs. Explicit Sessions:** - - **Implicit Session:** The primary session tied to a user's main login (e.g., social or email). It is designed for interacting with specific, pre-approved contracts within your dapp for a seamless UX. - - **Explicit Session:** A temporary, permissioned session key. Your dapp can request specific permissions (e.g., "allow this key to spend 10 USDC"), and once approved by the user, can perform those actions without further popups. -- **Event-Driven:** Asynchronous operations like signing are handled via an event emitter, creating a single, consistent API for both popup and redirect flows. - -## 2. Getting Started - -### Installation - -```bash -pnpm install @0xsequence/dapp-client -``` - -### Basic Usage - -It is recommended to create and manage a single, singleton instance of the `DappClient` throughout your application. - -```typescript -import { DappClient } from '@0xsequence/dapp-client' - -// 1. Create a single client instance for your app -const dappClient = new DappClient('https://my-wallet-url.com') - -// 2. Initialize the client when your application loads -async function initializeClient() { - try { - // The initialize method loads any existing session from storage - // and handles any pending redirect responses. - await dappClient.initialize() - console.log('Client initialized. User is connected:', dappClient.isInitialized) - } catch (error) { - console.error('Failed to initialize client:', error) - } -} - -initializeClient() -``` - -## 3. Class: `DappClient` - -The main entry point for interacting with the Wallet. This client manages user sessions across multiple chains, handles connection and disconnection, and provides methods for signing and sending transactions. - -### Constructor - -**`new DappClient(walletUrl, options?)`** - -Initializes a new instance of the DappClient. - -| Parameter | Type | Description | -| :------------------------------- | :-------------------------- | :------------------------------------------------------------------------------------------- | -| `walletUrl` | `string` | **(Required)** The URL of the Ecosystem Wallet Webapp. | -| `origin` | `string` | **(Required)** The origin of the dapp. | -| `options` | `object` | (Optional) An object containing configuration options for the client. | -| `options.transportMode` | `'popup' \| 'redirect'` | The communication mode to use with the wallet. Defaults to `'popup'`. | -| `options.keymachineUrl` | `string` | The URL of the key management service. Defaults to the production Sequence Key Machine. | -| `options.redirectPath` | `string` | The path to redirect back to after a redirect-based flow. Used as origin+path. | -| `options.sequenceStorage` | `SequenceStorage` | An object for persistent session data storage. Defaults to `WebStorage` (using IndexedDB). | -| `options.sequenceSessionStorage` | `SequenceSessionStorage` | An object for temporary data storage (e.g., pending requests). Defaults to `sessionStorage`. | -| `options.randomPrivateKeyFn` | `() => Hex \| Promise` | A function to generate random private keys for new sessions. | -| `options.redirectActionHandler` | `(url: string) => void` | A handler to manually control navigation for redirect flows. | -| `options.canUseIndexedDb` | `boolean` | A flag to enable or disable the use of IndexedDB for caching. Defaults to `true`. | - ---- - -## 4. API Reference - -### Properties - -| Property | Type | Description | -| :-------------- | :--------------- | :------------------------------------------------------------------------ | -| `isInitialized` | `boolean` | `true` if the client has an active and loaded session, `false` otherwise. | -| `loginMethod` | `string \| null` | The login method used for the current session (e.g., 'google', 'email'). | -| `userEmail` | `string \| null` | The email address associated with the session, if available. | -| `transportMode` | `TransportMode` | (Read-only) The transport mode the client was configured with. | - -### Methods - -#### **initialize()** - -Initializes the client by loading any existing session from storage. This should be called once when your application loads. - -- **Returns:** `Promise` -- **Throws:** `InitializationError` if the process fails. - ---- - -#### **connect()** - -Creates and initializes a new user session for a given chain. - -- **Parameters:** - - `chainId`: `ChainId` - The primary chain ID for the new session. - - `permissions?`: `Signers.Session.ExplicitParams` - (Optional) Permissions to request the user to approve for the new session (Unrestricted permissions if not provided). - - `options?`: `{ preferredLoginMethod?, email? }` - (Optional) Options for the new session. -- **Returns:** `Promise` -- **Throws:** `ConnectionError`, `InitializationError` - ---- - -#### **disconnect()** - -Disconnects the client and clears all session data from browser storage. Note: this does not revoke the sessions on-chain. - -- **Returns:** `Promise` - ---- - -#### **getWalletAddress()** - -Returns the wallet address of the current session. - -- **Returns:** `Address.Address | null` - ---- - -#### **getAllSessions()** - -Returns an array of all active session keys (both implicit and explicit). - -- **Returns:** `Session[]` - ---- - -#### **addExplicitSession()** - -Creates and initializes a new explicit session for a given chain. - -- **Parameters:** - - `chainId`: `ChainId` - The chain ID for the new session. - - `permissions`: `Signers.Session.ExplicitParams` - The permissions to request. -- **Returns:** `Promise` -- **Throws:** `AddExplicitSessionError`, `InitializationError` -- **Example:** - ```typescript - // Allow this session to transfer 1 USDC on Polygon - const USDC_ADDRESS = '0x...' - const permissions = { - permissions: [Utils.ERC20PermissionBuilder.buildTransfer(USDC_ADDRESS, '1000000')], - } - await dappClient.addExplicitSession(137, permissions) - ``` - ---- - -#### **sendTransaction()** - -Signs and sends a transaction using an active session signer. - -- **Parameters:** - - `chainId`: `ChainId` - The chain ID for the transaction. - - `transactions`: `Transaction[]` - An array of transactions to execute. - - `feeOption?`: `Relayer.FeeOption` - (Optional) A gas fee option for (ex: User could pay the gas in USDC). -- **Returns:** `Promise` - The transaction hash. -- **Throws:** `TransactionError`, `InitializationError` - ---- - -#### **getFeeOptions()** - -Gets available gas fee options for a transaction. - -- **Parameters:** - - `chainId`: `ChainId` - The chain ID for the transaction. - - `transactions`: `Transaction[]` - The transactions to get fee options for. -- **Returns:** `Promise` -- **Throws:** `FeeOptionError`, `InitializationError` - ---- - -#### **signMessage()** - -Signs a standard EIP-191 message. The signature is delivered via the `signatureResponse` event. - -- **Parameters:** - - `chainId`: `ChainId` - The chain ID for signing. - - `message`: `string` - The message to sign. -- **Returns:** `Promise` -- **Throws:** `SigningError`, `InitializationError` - ---- - -#### **signTypedData()** - -Signs an EIP-712 typed data object. The signature is delivered via the `signatureResponse` event. - -- **Parameters:** - - `chainId`: `ChainId` - The chain ID for signing. - - `typedData`: `unknown` - The typed data object to sign. -- **Returns:** `Promise` -- **Throws:** `SigningError`, `InitializationError` - ---- - -#### **on()** - -Registers an event listener for client-side events. - -- **Parameters:** - - `event`: `'sessionsUpdated' | 'signatureResponse'` - The event to listen for. - - `listener`: `DappClientEventListener` - The callback function. -- **Returns:** `() => void` - A function to unsubscribe the listener. -- **Example:** - - ```typescript - // The listener for `signatureResponse` receives the signing result. - dappClient.on('signatureResponse', (data) => { - // The `data` object includes the chainId where the signing occurred. - console.log('Signature response from chain:', data.chainId) - - if (data.error) { - console.error('Signing failed:', data.error) - return - } - - // The `data.response` object contains the signature and other details. - console.log('Action:', data.action) // 'signMessage' or 'signTypedData' - console.log('Signature:', data.response.signature) - console.log('Signed by wallet:', data.response.walletAddress) - }) - - // The listener for `sessionsUpdated` is useful for syncing UI state. - dappClient.on('sessionsUpdated', () => { - console.log('Sessions updated!') - console.log('Is initialized:', dappClient.isInitialized) - console.log('Wallet address:', dappClient.getWalletAddress()) - }) - ``` diff --git a/packages/wallet/dapp-client/eslint.config.js b/packages/wallet/dapp-client/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/wallet/dapp-client/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/wallet/dapp-client/package.json b/packages/wallet/dapp-client/package.json deleted file mode 100644 index 7ed1867e74..0000000000 --- a/packages/wallet/dapp-client/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@0xsequence/dapp-client", - "version": "3.0.5", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@vitest/coverage-v8": "^4.0.18", - "dotenv": "^17.3.1", - "fake-indexeddb": "^6.2.5", - "happy-dom": "^20.8.9", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "@0xsequence/guard": "workspace:^", - "@0xsequence/relayer": "workspace:^", - "@0xsequence/wallet-core": "workspace:^", - "@0xsequence/wallet-primitives": "workspace:^", - "ox": "^0.9.17" - } -} diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts deleted file mode 100644 index a57e2c2324..0000000000 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ /dev/null @@ -1,1163 +0,0 @@ -import * as Guard from '@0xsequence/guard' -import { AbiFunction, Address, Hex, Provider, RpcTransport, Secp256k1 } from 'ox' - -import { - Envelope, - Signers, - State, - Wallet, - Attestation, - Constants, - Extensions, - Payload, - SessionConfig, -} from './index.js' - -import { DappTransport } from './DappTransport.js' - -import { - AddExplicitSessionError, - FeeOptionError, - InitializationError, - ModifyExplicitSessionError, - SessionConfigError, - TransactionError, - WalletRedirectError, -} from './utils/errors.js' -import { SequenceStorage } from './utils/storage.js' -import { getRelayerUrl, getRpcUrl } from './utils/index.js' - -import { - CreateNewSessionResponse, - ExplicitSessionEventListener, - LoginMethod, - RandomPrivateKeyFn, - RequestActionType, - Transaction, - TransportMode, - GuardConfig, - CreateNewSessionPayload, - EthAuthSettings, - ModifyExplicitSessionPayload, - SessionResponse, - AddExplicitSessionPayload, - FeeOption, - OperationFailedStatus, - OperationStatus, - ETHAuthProof, -} from './types/index.js' -import { CACHE_DB_NAME, VALUE_FORWARDER_ADDRESS } from './utils/constants.js' -import { ExplicitSession, ImplicitSession, ExplicitSessionConfig } from './index.js' -import { Relayer } from '@0xsequence/relayer' - -interface ChainSessionManagerEventMap { - explicitSessionResponse: ExplicitSessionEventListener -} - -/** - * Manages sessions and wallet interactions for a single blockchain. - * This class is used internally by the DappClient to handle chain-specific logic. - */ -export class ChainSessionManager { - private readonly instanceId: string - - private stateProvider: State.Provider - - private readonly redirectUrl: string - private readonly randomPrivateKeyFn: RandomPrivateKeyFn - - private eventListeners: { - [K in keyof ChainSessionManagerEventMap]?: Set - } = {} - - private explicitSessions: ExplicitSession[] = [] - private implicitSession: ImplicitSession | null = null - - private walletAddress: Address.Address | null = null - private sessionManager: Signers.SessionManager | null = null - private wallet: Wallet | null = null - private provider: Provider.Provider | null = null - private relayer: Relayer.RpcRelayer - private readonly chainId: number - public transport: DappTransport | null = null - private sequenceStorage: SequenceStorage - public isInitialized: boolean = false - private isInitializing: boolean = false - public loginMethod: LoginMethod | null = null - public userEmail: string | null = null - private guard?: GuardConfig - private lastSignedCallCache?: { - fingerprint: string - signedCall: { to: Address.Address; data: Hex.Hex } - createdAtMs: number - } - - /** - * @param chainId The ID of the chain this manager is responsible for. - * @param keyMachineUrl The URL of the key management service. - * @param transport The transport mechanism for communicating with the wallet. - * @param sequenceStorage The storage implementation for persistent session data. - * @param redirectUrl (Optional) The URL to redirect back to after a redirect-based flow. - * @param guard (Optional) The guard config to use for the session. - * @param randomPrivateKeyFn (Optional) A function to generate random private keys. - * @param canUseIndexedDb (Optional) A flag to enable or disable IndexedDB for caching. - */ - constructor( - chainId: number, - transport: DappTransport, - projectAccessKey: string, - keyMachineUrl: string, - nodesUrl: string, - relayerUrl: string, - sequenceStorage: SequenceStorage, - redirectUrl: string, - guard?: GuardConfig, - randomPrivateKeyFn?: RandomPrivateKeyFn, - canUseIndexedDb: boolean = true, - ) { - this.instanceId = `manager-${Math.random().toString(36).substring(2, 9)}` - console.log(`ChainSessionManager instance created: ${this.instanceId} for chain ${chainId}`) - - const rpcUrl = getRpcUrl(chainId, nodesUrl, projectAccessKey) - this.chainId = chainId - - const canUseIndexedDbInEnv = canUseIndexedDb && typeof indexedDB !== 'undefined' - if (canUseIndexedDbInEnv) { - this.stateProvider = new State.Cached({ - source: new State.Sequence.Provider(keyMachineUrl), - cache: new State.Local.Provider(new State.Local.IndexedDbStore(CACHE_DB_NAME)), - }) - } else { - this.stateProvider = new State.Sequence.Provider(keyMachineUrl) - } - this.guard = guard - this.provider = Provider.from(RpcTransport.fromHttp(rpcUrl)) - this.relayer = new Relayer.RpcRelayer( - getRelayerUrl(chainId, relayerUrl), - this.chainId, - getRpcUrl(chainId, nodesUrl, projectAccessKey), - undefined, - projectAccessKey, - ) - - this.transport = transport - this.sequenceStorage = sequenceStorage - - this.redirectUrl = redirectUrl - this.randomPrivateKeyFn = randomPrivateKeyFn ?? Secp256k1.randomPrivateKey - } - - /** - * Registers an event listener for a specific event within this chain manager. - * @param event The event to listen for ChainSessionManagerEvent events. - * @param listener The function to call when the event occurs. - * @returns A function to unsubscribe the listener. - */ - public on( - event: K, - listener: ChainSessionManagerEventMap[K], - ): () => void { - if (!this.eventListeners[event]) { - this.eventListeners[event] = new Set() - } - this.eventListeners[event].add(listener) - return () => { - this.eventListeners[event]?.delete(listener) - } - } - - /** - * @private Emits an event to all registered listeners for this chain manager. - * @param event The event to emit. - * @param data The data to pass to the listener. - */ - private emit( - event: K, - data: Parameters[0], - ): void { - const listeners = this.eventListeners[event] - if (listeners) { - listeners.forEach((listener) => (listener as (d: typeof data) => void)(data)) - } - } - - /** - * Initializes the manager by loading sessions from storage for this specific chain. - * @returns A promise resolving to the login method and email if an implicit session is found, or void. - * @throws {InitializationError} If initialization fails. - */ - async initialize(): Promise<{ - loginMethod: LoginMethod | null - userEmail: string | null - } | void> { - if (this.isInitializing) return - this.isInitializing = true - - this._resetState() - - try { - const implicitSession = await this.sequenceStorage.getImplicitSession() - const explicitSessions = await this.sequenceStorage.getExplicitSessions() - const walletAddress = implicitSession?.walletAddress || explicitSessions[0]?.walletAddress - - if (walletAddress) { - this.walletAddress = walletAddress - this.loginMethod = implicitSession?.loginMethod || explicitSessions[0]?.loginMethod || null - this.userEmail = implicitSession?.userEmail || explicitSessions[0]?.userEmail || null - await this._loadSessionFromStorage(walletAddress) - } - } catch (err) { - await this._resetStateAndClearCredentials() - throw new InitializationError(`Initialization failed: ${err}`) - } finally { - this.isInitializing = false - this.isInitialized = !!this.walletAddress - } - return { loginMethod: this.loginMethod, userEmail: this.userEmail } - } - - /** - * Initializes the manager with a known wallet address, without loading sessions from storage. - * This is used when a wallet address is known but the session manager for this chain hasn't been instantiated yet. - * @param walletAddress The address of the wallet to initialize with. - */ - public initializeWithWallet(walletAddress: Address.Address) { - if (this.isInitialized) return - - this.walletAddress = walletAddress - this.wallet = new Wallet(this.walletAddress, { - stateProvider: this.stateProvider, - }) - this.sessionManager = new Signers.SessionManager(this.wallet, { - sessionManagerAddress: Extensions.Rc5.sessions, - provider: this.provider!, - }) - this.isInitialized = true - } - - /** - * @private Loads implicit and explicit sessions from storage for the current wallet address. - * @param walletAddress The walletAddress for all sessions. - */ - private async _loadSessionFromStorage(walletAddress: Address.Address) { - this.initializeWithWallet(walletAddress) - - const implicitSession = await this.sequenceStorage.getImplicitSession() - - if (implicitSession) { - await this._initializeImplicitSessionInternal( - implicitSession.pk, - walletAddress, - implicitSession.attestation, - implicitSession.identitySignature, - false, - implicitSession.loginMethod, - implicitSession.userEmail, - implicitSession.guard, - ) - } - - const allExplicitSessions = await this.sequenceStorage.getExplicitSessions() - const walletExplicitSessions = allExplicitSessions.filter( - (s) => Address.isEqual(Address.from(s.walletAddress), walletAddress) && s.chainId === this.chainId, - ) - - for (const sessionData of walletExplicitSessions) { - await this._initializeExplicitSessionInternal( - sessionData.pk, - sessionData.loginMethod, - sessionData.userEmail, - sessionData.guard, - true, - ) - } - } - - /** - * Initiates the creation of a new session by sending a request to the wallet. - * @param origin The origin of the session. - * @param sessionConfig (Optional) Session configuration for an initial explicit session. - * @param options (Optional) Additional options like preferred login method. - * @throws {InitializationError} If a session already exists or the transport fails to initialize. - */ - async createNewSession( - origin: string, - sessionConfig?: ExplicitSessionConfig, - options: { - preferredLoginMethod?: LoginMethod - email?: string - includeImplicitSession?: boolean - ethAuth?: EthAuthSettings - } = {}, - ): Promise { - if (this.isInitialized) { - throw new InitializationError('A session already exists. Disconnect first.') - } - - const shouldCreateSession = !!sessionConfig || (options.includeImplicitSession ?? false) - - const newPk = shouldCreateSession ? await this.randomPrivateKeyFn() : null - const newSignerAddress = - shouldCreateSession && newPk ? Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: newPk })) : null - const completeSession = - shouldCreateSession && newSignerAddress - ? { - sessionAddress: newSignerAddress, - ...sessionConfig, - } - : undefined - - try { - if (!this.transport) throw new InitializationError('Transport failed to initialize.') - - const payload: CreateNewSessionPayload = { - origin, - session: completeSession as ExplicitSession | undefined, - includeImplicitSession: options.includeImplicitSession ?? false, - ethAuth: options.ethAuth, - preferredLoginMethod: options.preferredLoginMethod, - email: options.preferredLoginMethod === 'email' ? options.email : undefined, - } - - if (this.transport.mode === TransportMode.REDIRECT) { - if (shouldCreateSession && newPk) { - await this.sequenceStorage.saveTempSessionPk(newPk) - } - await this.sequenceStorage.savePendingRequest({ - chainId: this.chainId, - action: RequestActionType.CREATE_NEW_SESSION, - payload, - }) - await this.sequenceStorage.setPendingRedirectRequest(true) - } - - const connectResponse = await this.transport.sendRequest( - RequestActionType.CREATE_NEW_SESSION, - this.redirectUrl, - payload, - { path: '/request/connect' }, - ) - - const receivedAddress = Address.from(connectResponse.walletAddress) - const { attestation, signature, userEmail, loginMethod, guard } = connectResponse - - if (shouldCreateSession) { - await this._resetStateAndClearCredentials() - - this.loginMethod = null - this.userEmail = null - - this.initializeWithWallet(receivedAddress) - - if (attestation && signature && newPk) { - await this._initializeImplicitSessionInternal( - newPk, - receivedAddress, - attestation, - signature, - true, - loginMethod, - userEmail, - guard, - ) - } - - if (sessionConfig && newPk) { - await this._initializeExplicitSessionInternal(newPk, loginMethod, userEmail, guard, true) - await this.sequenceStorage.saveExplicitSession({ - pk: newPk, - walletAddress: receivedAddress, - chainId: this.chainId, - guard, - loginMethod, - userEmail, - }) - } - } else { - await this._resetStateAndClearCredentials() - this.initializeWithWallet(receivedAddress) - this.loginMethod = loginMethod ?? null - this.userEmail = userEmail ?? null - this.guard = guard - } - - if (payload.ethAuth) { - await this._saveEthAuthProofIfProvided(connectResponse.ethAuthProof) - } - - if (this.transport.mode === TransportMode.POPUP) { - this.transport.closeWallet() - } - } catch (err) { - this._resetState() - if (this.transport?.mode === TransportMode.POPUP) this.transport.closeWallet() - throw new InitializationError(`Session creation failed: ${err}`) - } - } - - /** - * Initiates the addition of a new explicit session by sending a request to the wallet. - * @param explicitSessionConfig The explicit session configuration for the new explicit session. - * @throws {InitializationError} If the manager is not initialized. - * @throws {AddExplicitSessionError} If adding the session fails. - */ - async addExplicitSession(explicitSessionConfig: ExplicitSessionConfig): Promise { - if (!this.walletAddress) { - throw new InitializationError( - 'Cannot add an explicit session without a wallet address. Initialize the manager with a wallet address first.', - ) - } - - const newPk = await this.randomPrivateKeyFn() - - try { - if (!this.transport) throw new InitializationError('Transport failed to initialize.') - - const newSignerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: newPk })) - - const payload: AddExplicitSessionPayload = { - session: { ...explicitSessionConfig, sessionAddress: newSignerAddress, type: 'explicit' }, - } - - if (this.transport.mode === TransportMode.REDIRECT) { - await this.sequenceStorage.saveTempSessionPk(newPk) - await this.sequenceStorage.savePendingRequest({ - chainId: this.chainId, - action: RequestActionType.ADD_EXPLICIT_SESSION, - payload, - }) - await this.sequenceStorage.setPendingRedirectRequest(true) - } - - const response = await this.transport.sendRequest( - RequestActionType.ADD_EXPLICIT_SESSION, - this.redirectUrl, - payload, - { path: '/request/connect' }, - ) - - if (!Address.isEqual(Address.from(response.walletAddress), this.walletAddress)) { - throw new AddExplicitSessionError('Wallet address mismatch.') - } - - if (this.transport?.mode === TransportMode.POPUP) { - this.transport?.closeWallet() - } - - await this._initializeExplicitSessionInternal( - newPk, - response.loginMethod, - response.userEmail, - response.guard, - true, - ) - await this.sequenceStorage.saveExplicitSession({ - pk: newPk, - walletAddress: this.walletAddress, - chainId: this.chainId, - loginMethod: response.loginMethod, - userEmail: response.userEmail, - guard: response.guard, - }) - await this.sequenceStorage.clearSessionlessConnection() - } catch (err) { - if (this.transport?.mode === TransportMode.POPUP) this.transport.closeWallet() - throw new AddExplicitSessionError(`Adding explicit session failed: ${err}`) - } - } - - /** - * Initiates the modification of an existing explicit session by sending a request to the wallet. - * @param modifiedExplicitSession The modified explicit session. - * @throws {InitializationError} If the manager is not initialized. - * @throws {ModifyExplicitSessionError} If modifying the session fails. - */ - async modifyExplicitSession(modifiedExplicitSession: ExplicitSession): Promise { - if (!this.walletAddress) { - throw new InitializationError( - 'Cannot modify an explicit session without a wallet address. Initialize the manager with a wallet address first.', - ) - } - - try { - if (!this.transport) throw new InitializationError('Transport failed to initialize.') - - if (!modifiedExplicitSession.sessionAddress) { - throw new ModifyExplicitSessionError('Session address is required.') - } - - const existingExplicitSession = this.explicitSessions.find((s) => - Address.isEqual(s.sessionAddress!, modifiedExplicitSession.sessionAddress!), - ) - if (!existingExplicitSession) { - throw new ModifyExplicitSessionError('Session not found.') - } - - const payload: ModifyExplicitSessionPayload = { - walletAddress: this.walletAddress, - session: { - ...modifiedExplicitSession, - }, - } - - if (this.transport.mode === TransportMode.REDIRECT) { - await this.sequenceStorage.savePendingRequest({ - chainId: this.chainId, - action: RequestActionType.MODIFY_EXPLICIT_SESSION, - payload, - }) - await this.sequenceStorage.setPendingRedirectRequest(true) - } - - const response = await this.transport.sendRequest( - RequestActionType.MODIFY_EXPLICIT_SESSION, - this.redirectUrl, - payload, - { path: '/request/modify' }, - ) - - if ( - !Address.isEqual(Address.from(response.walletAddress), this.walletAddress) && - !Address.isEqual(Address.from(response.sessionAddress), modifiedExplicitSession.sessionAddress) - ) { - throw new ModifyExplicitSessionError('Wallet or session address mismatch.') - } - - existingExplicitSession.permissions = modifiedExplicitSession.permissions - existingExplicitSession.deadline = modifiedExplicitSession.deadline - existingExplicitSession.valueLimit = modifiedExplicitSession.valueLimit - - if (this.transport?.mode === TransportMode.POPUP) { - this.transport?.closeWallet() - } - } catch (err) { - if (this.transport?.mode === TransportMode.POPUP) this.transport.closeWallet() - throw new ModifyExplicitSessionError(`Modifying explicit session failed: ${err}`) - } - } - - /** - * @private Handles the connection-related part of a redirect response, initializing sessions. - * @param response The response payload from the redirect. - * @returns A promise resolving to true on success. - */ - private async _handleRedirectConnectionResponse(response: { - payload: CreateNewSessionResponse - action: string - }): Promise { - try { - const connectResponse = response.payload - const receivedAddress = Address.from(connectResponse.walletAddress) - const { userEmail, loginMethod, guard } = connectResponse - const savedRequest = await this.sequenceStorage.peekPendingRequest() - const savedPayload = savedRequest?.payload as CreateNewSessionPayload | undefined - const explicitSessionRequested = (savedPayload?.session?.permissions?.length ?? 0) > 0 - const implicitSessionRequested = savedPayload?.includeImplicitSession ?? false - const needsTempPk = explicitSessionRequested || implicitSessionRequested - const tempPk = needsTempPk ? await this.sequenceStorage.getAndClearTempSessionPk() : null - - if (needsTempPk && !tempPk) { - throw new InitializationError('Failed to retrieve temporary session key after redirect.') - } - - if (response.action === RequestActionType.CREATE_NEW_SESSION) { - const { attestation, signature } = connectResponse - - await this._resetStateAndClearCredentials() - - this.loginMethod = null - this.userEmail = null - - this.initializeWithWallet(receivedAddress) - - if (implicitSessionRequested) { - if (!attestation || !signature || !tempPk) { - throw new InitializationError('Missing implicit session data in redirect response.') - } - await this._initializeImplicitSessionInternal( - tempPk, - receivedAddress, - attestation, - signature, - true, - loginMethod, - userEmail, - guard, - ) - } - - if (explicitSessionRequested && savedPayload?.session && tempPk) { - await this._initializeExplicitSessionInternal(tempPk, loginMethod, userEmail, guard, true) - await this.sequenceStorage.saveExplicitSession({ - pk: tempPk, - walletAddress: receivedAddress, - chainId: this.chainId, - loginMethod, - userEmail, - guard, - }) - await this.sequenceStorage.clearSessionlessConnection() - } - - if (!explicitSessionRequested && !implicitSessionRequested) { - this.loginMethod = loginMethod ?? null - this.userEmail = userEmail ?? null - this.guard = guard - } - - if (savedPayload?.ethAuth) { - await this._saveEthAuthProofIfProvided(connectResponse.ethAuthProof) - } - } else if (response.action === RequestActionType.ADD_EXPLICIT_SESSION) { - if (!this.walletAddress || !Address.isEqual(receivedAddress, this.walletAddress)) { - throw new InitializationError('Received an explicit session for a wallet that is not active.') - } - - const explicitSessionPk = tempPk ?? (await this.sequenceStorage.getAndClearTempSessionPk()) - if (!explicitSessionPk) { - throw new InitializationError('Failed to retrieve temporary session key for explicit session.') - } - - await this._initializeExplicitSessionInternal( - explicitSessionPk, - this.loginMethod ?? undefined, - this.userEmail ?? undefined, - this.guard ?? undefined, - true, - ) - await this.sequenceStorage.saveExplicitSession({ - pk: explicitSessionPk, - walletAddress: receivedAddress, - chainId: this.chainId, - loginMethod: this.loginMethod ?? undefined, - userEmail: this.userEmail ?? undefined, - guard: this.guard ?? undefined, - }) - await this.sequenceStorage.clearSessionlessConnection() - - const newSignerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: explicitSessionPk })) - - this.emit('explicitSessionResponse', { - action: RequestActionType.ADD_EXPLICIT_SESSION, - response: { - walletAddress: receivedAddress, - sessionAddress: newSignerAddress, - }, - }) - } else { - throw new WalletRedirectError(`Received unhandled redirect action: ${response.action}`) - } - this.isInitialized = true - return true - } catch (err) { - throw new InitializationError(`Failed to initialize session from redirect: ${err}`) - } - } - - /** - * Resets the manager state and clears all credentials from storage. - */ - async disconnect(): Promise { - await this._resetStateAndClearCredentials() - if (this.transport) { - this.transport.destroy() - this.transport = null - } - this.loginMethod = null - this.userEmail = null - this.isInitialized = false - } - - /** - * @private Initializes an implicit session signer and adds it to the session manager. - * @param pk The private key of the session. - * @param address The wallet address. - * @param attestation The attestation from the wallet. - * @param identitySignature The identity signature from the wallet. - * @param saveSession Whether to persist the session in storage. - * @param loginMethod The login method used. - * @param userEmail The email associated with the session. - * @param guard The guard configuration. - */ - private async _initializeImplicitSessionInternal( - pk: Hex.Hex, - address: Address.Address, - attestation: Attestation.Attestation, - identitySignature: Hex.Hex, - saveSession: boolean = false, - loginMethod?: LoginMethod, - userEmail?: string, - guard?: GuardConfig, - ): Promise { - if (!this.sessionManager) throw new InitializationError('Manager not instantiated for implicit session.') - try { - const implicitSigner = new Signers.Session.Implicit( - pk, - attestation, - identitySignature, - this.sessionManager.address, - ) - this.sessionManager = this.sessionManager.withImplicitSigner(implicitSigner) - - this.implicitSession = { - sessionAddress: implicitSigner.address, - type: 'implicit', - } - - this.walletAddress = address - if (saveSession) - await this.sequenceStorage.saveImplicitSession({ - pk, - walletAddress: address, - attestation, - identitySignature, - chainId: this.chainId, - loginMethod, - userEmail, - guard, - }) - if (loginMethod) this.loginMethod = loginMethod - if (userEmail) this.userEmail = userEmail - if (guard) this.guard = guard - } catch (err) { - throw new InitializationError(`Implicit session init failed: ${err}`) - } - } - - /** - * @private Initializes an explicit session signer and adds it to the session manager. - * It retries fetching permissions from the network if allowed. - * @param pk The private key of the session. - * @param loginMethod The login method used for the session. - * @param userEmail The email associated with the session. - * @param allowRetries Whether to retry fetching permissions on failure. - */ - private async _initializeExplicitSessionInternal( - pk: Hex.Hex, - loginMethod?: LoginMethod, - userEmail?: string, - guard?: GuardConfig, - allowRetries: boolean = false, - ): Promise { - if (!this.provider || !this.wallet) - throw new InitializationError('Manager core components not ready for explicit session.') - - const maxRetries = allowRetries ? 3 : 1 - let lastError: Error | null = null - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const tempManager = new Signers.SessionManager(this.wallet, { - sessionManagerAddress: Extensions.Rc5.sessions, - provider: this.provider, - }) - const topology = await tempManager.getTopology() - - const signerAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: pk })) - const permissions = SessionConfig.getSessionPermissions(topology, signerAddress) - - if (!permissions) { - throw new InitializationError(`Permissions not found for session key.`) - } - - if (!this.sessionManager) throw new InitializationError('Main session manager is not initialized.') - - const explicitSigner = new Signers.Session.Explicit(pk, permissions) - this.sessionManager = this.sessionManager.withExplicitSigner(explicitSigner) - - this.explicitSessions.push({ - sessionAddress: explicitSigner.address, - chainId: this.chainId, - permissions: permissions.permissions, - valueLimit: permissions.valueLimit, - deadline: permissions.deadline, - type: 'explicit', - }) - - if (guard && !this.guard) this.guard = guard - - return - } catch (err) { - lastError = err instanceof Error ? err : new Error(String(err)) - if (attempt < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) - } - } - } - if (lastError) - throw new InitializationError(`Explicit session init failed after ${maxRetries} attempts: ${lastError.message}`) - } - - private async _refreshExplicitSession(expiredSignerAddress: Address.Address): Promise { - if (!this.wallet || !this.sessionManager || !this.provider || !this.isInitialized) - throw new InitializationError('Session is not initialized.') - // Find current explicit session - const explicitSigner = this.explicitSessions.find((s) => Address.isEqual(s.sessionAddress, expiredSignerAddress)) - if (!explicitSigner) throw new ModifyExplicitSessionError('Explicit session not found.') - // Update the deadline - const newExplicitSession = { - ...explicitSigner, - deadline: BigInt(Math.floor(Date.now() / 1000)) + BigInt(24 * 60 * 60), - } - await this.modifyExplicitSession(newExplicitSession) - } - - /** - * Checks if the current session has permission to execute a set of transactions. - * @param transactions The transactions to check permissions for. - * @returns A promise that resolves to true if the session has permission, false otherwise. - */ - async hasPermission(transactions: Transaction[]): Promise { - if (!this.wallet || !this.sessionManager || !this.provider || !this.isInitialized) { - return false - } - - try { - const calls: Payload.Call[] = transactions.map((tx) => ({ - to: tx.to, - value: tx.value ?? 0n, - data: tx.data ?? '0x', - gasLimit: tx.gasLimit ?? 0n, - delegateCall: tx.delegateCall ?? false, - onlyFallback: tx.onlyFallback ?? false, - behaviorOnError: tx.behaviorOnError ?? ('revert' as const), - })) - - // Directly check if there are signers with the necessary permissions for all calls. - // This will throw an error if any call is not supported. - await this.sessionManager.findSignersForCalls(this.wallet.address, this.chainId, calls) - return true - } catch (error) { - // An error from findSignersForCalls indicates a permission failure. - console.warn( - `Permission check failed for chain ${this.chainId}:`, - error instanceof Error ? error.message : String(error), - ) - return false - } - } - - /** - * Fetches fee options for a set of transactions. - * @param calls The transactions to estimate fees for. - * @returns A promise that resolves with an array of fee options. - * @throws {FeeOptionError} If fetching fee options fails. - */ - async getFeeOptions(calls: Transaction[]): Promise { - const callsToSend = calls.map((tx) => ({ - to: tx.to, - value: tx.value, - data: tx.data, - gasLimit: tx.gasLimit ?? BigInt(0), - delegateCall: tx.delegateCall ?? false, - onlyFallback: tx.onlyFallback ?? false, - behaviorOnError: tx.behaviorOnError ?? ('revert' as const), - })) - try { - const signedCall = await this._buildAndSignCalls(callsToSend) - const fingerprint = this._fingerprintCalls(callsToSend) - if (fingerprint) { - this.lastSignedCallCache = { - fingerprint, - signedCall, - createdAtMs: Date.now(), - } - } - const walletAddress = this.walletAddress - if (!walletAddress) throw new InitializationError('Wallet is not initialized.') - const feeOptions = await this.relayer.feeOptions(walletAddress, this.chainId, signedCall.to, callsToSend) - return feeOptions.options - } catch (err) { - throw new FeeOptionError(`Failed to get fee options: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Builds, signs, and sends a batch of transactions. - * @param transactions The transactions to be sent. - * @param feeOption (Optional) The fee option to use for sponsoring the transaction. If provided, a token transfer call will be prepended. - * @returns A promise that resolves with the transaction hash. - * @throws {InitializationError} If the session is not initialized. - * @throws {TransactionError} If the transaction fails at any stage. - */ - async buildSignAndSendTransactions(transactions: Transaction[], feeOption?: FeeOption): Promise { - if (!this.wallet || !this.sessionManager || !this.provider || !this.isInitialized) - throw new InitializationError('Session is not initialized.') - try { - const calls: Payload.Call[] = transactions.map((tx) => ({ - to: tx.to, - value: tx.value, - data: tx.data, - gasLimit: tx.gasLimit ?? BigInt(0), - delegateCall: tx.delegateCall ?? false, - onlyFallback: tx.onlyFallback ?? false, - behaviorOnError: tx.behaviorOnError ?? ('revert' as const), - })) - - const callsToSend = calls - if (feeOption) { - if (feeOption.token.contractAddress === Constants.ZeroAddress) { - const forwardValue = AbiFunction.from(['function forwardValue(address to, uint256 value)']) - callsToSend.unshift({ - to: VALUE_FORWARDER_ADDRESS, - value: BigInt(feeOption.value), - data: AbiFunction.encodeData(forwardValue, [feeOption.to as Address.Address, BigInt(feeOption.value)]), - gasLimit: BigInt(feeOption.gasLimit), - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert' as const, - }) - } else { - const transfer = AbiFunction.from(['function transfer(address to, uint256 value)']) - const transferCall: Payload.Call = { - to: feeOption.token.contractAddress as `0x${string}`, - value: BigInt(0), - data: AbiFunction.encodeData(transfer, [feeOption.to as Address.Address, BigInt(feeOption.value)]), - gasLimit: BigInt(feeOption.gasLimit), - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert' as const, - } - callsToSend.unshift(transferCall) - } - } - const signedCalls = this._getCachedSignedCall(callsToSend) ?? (await this._buildAndSignCalls(callsToSend)) - const hash = await this.relayer.relay(signedCalls.to, signedCalls.data, this.chainId) - const status = await this._waitForTransactionReceipt(hash.opHash, this.chainId) - if (status.status === 'confirmed') { - return status.transactionHash - } else { - const failedStatus = status as OperationFailedStatus - const reason = failedStatus.reason || `unexpected status ${status.status}` - throw new TransactionError(`Transaction failed: ${reason}`) - } - } catch (err) { - throw new TransactionError(`Transaction failed: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Handles a redirect response from the wallet for this specific chain. - * @param response The pre-parsed response from the transport. - * @returns A promise that resolves to true if the response was handled successfully. - * @throws {WalletRedirectError} If the response is invalid or causes an error. - * @throws {InitializationError} If the session cannot be initialized from the response. - */ - public async handleRedirectResponse( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - response: { payload: any; action: string } | { error: any; action: string }, - ): Promise { - if (!response) return false - - if ('error' in response && response.error) { - const { action } = response - - if (action === RequestActionType.ADD_EXPLICIT_SESSION || action === RequestActionType.MODIFY_EXPLICIT_SESSION) { - this.emit('explicitSessionResponse', { action, error: response.error }) - return true - } - } - - if ('payload' in response && response.payload) { - if ( - response.action === RequestActionType.CREATE_NEW_SESSION || - response.action === RequestActionType.ADD_EXPLICIT_SESSION - ) { - return this._handleRedirectConnectionResponse(response) - } else if (response.action === RequestActionType.MODIFY_EXPLICIT_SESSION) { - const modifyResponse = response.payload as SessionResponse - if (!Address.isEqual(Address.from(modifyResponse.walletAddress), this.walletAddress!)) { - throw new ModifyExplicitSessionError('Wallet address mismatch on redirect response.') - } - - this.emit('explicitSessionResponse', { - action: RequestActionType.MODIFY_EXPLICIT_SESSION, - response: modifyResponse, - }) - - return true - } else { - throw new WalletRedirectError(`Received unhandled redirect action: ${response.action}`) - } - } - - throw new WalletRedirectError('Received an invalid redirect response from the wallet.') - } - - /** - * Gets the wallet address associated with this manager. - * @returns The wallet address, or null if not initialized. - */ - getWalletAddress(): Address.Address | null { - return this.walletAddress - } - - getGuard(): GuardConfig | undefined { - return this.guard - } - - /** - * Gets the sessions (signers) managed by this session manager. - * @returns An array of session objects. - */ - getExplicitSessions(): ExplicitSession[] { - return this.explicitSessions - } - - /** - * Gets the implicit session managed by this session manager. - * @returns An implicit session object or null if no implicit session is found. - */ - getImplicitSession(): ImplicitSession | null { - return this.implicitSession - } - - /** - * @private Prepares, signs, and builds a transaction envelope. - * @param calls The payload calls to include in the transaction. - * @returns The signed transaction data ready for relaying. - */ - private async _buildAndSignCalls(calls: Payload.Call[]): Promise<{ to: Address.Address; data: Hex.Hex }> { - if (!this.wallet || !this.sessionManager || !this.provider) - throw new InitializationError('Session not fully initialized.') - - try { - const preparedIncrement = await this.sessionManager.prepareIncrement(this.wallet.address, this.chainId, calls) - if (preparedIncrement) { - if ( - Address.isEqual(this.sessionManager.address, Extensions.Dev1.sessions) || - Address.isEqual(this.sessionManager.address, Extensions.Dev2.sessions) - ) { - // Last call - calls.push(preparedIncrement) - //FIXME Maybe this should throw since it's exploitable..? - } else { - // First call - calls.unshift(preparedIncrement) - } - } - - const envelope = await this.wallet.prepareTransaction(this.provider, calls, { - noConfigUpdate: true, - }) - const parentedEnvelope: Payload.Parented = { - ...envelope.payload, - parentWallets: [this.wallet.address], - } - const imageHash = await this.sessionManager.imageHash - if (imageHash === undefined) throw new SessionConfigError('Session manager image hash is undefined') - - const signature = await this.sessionManager.signSapient( - this.wallet.address, - this.chainId, - parentedEnvelope, - imageHash, - ) - const sapientSignature: Envelope.SapientSignature = { - imageHash, - signature, - } - const signedEnvelope = Envelope.toSigned(envelope, [sapientSignature]) - - if (!Envelope.reachedThreshold(signedEnvelope) && this.guard?.moduleAddresses.has(signature.address)) { - const guard = new Signers.Guard( - new Guard.Sequence.Guard(this.guard.url, this.guard.moduleAddresses.get(signature.address)!), - ) - const guardSignature = await guard.signEnvelope(signedEnvelope) - signedEnvelope.signatures.push(guardSignature) - } - - return await this.wallet.buildTransaction(this.provider, signedEnvelope) - } catch (err) { - throw new TransactionError(`Transaction failed building: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * @private Polls the relayer for the status of a transaction until it is confirmed or fails. - * @param opHash The operation hash of the relayed transaction. - * @param chainId The chain ID of the transaction. - * @returns The final status of the transaction. - */ - private async _waitForTransactionReceipt(opHash: `0x${string}`, chainId: number): Promise { - try { - while (true) { - const currentStatus = await this.relayer.status(opHash, chainId) - if (currentStatus.status === 'confirmed' || currentStatus.status === 'failed') return currentStatus - await new Promise((resolve) => setTimeout(resolve, 1500)) - } - } catch (err) { - throw new TransactionError( - `Transaction failed waiting for receipt: ${err instanceof Error ? err.message : String(err)}`, - ) - } - } - - /** - * @private Resets the internal state of the manager without clearing stored credentials. - */ - private _resetState(): void { - this.explicitSessions = [] - this.implicitSession = null - this.walletAddress = null - this.wallet = null - this.sessionManager = null - this.isInitialized = false - this.guard = undefined - } - - /** - * @private Resets the internal state and clears all persisted session data from storage. - */ - private async _resetStateAndClearCredentials(): Promise { - this._resetState() - await this.sequenceStorage.clearImplicitSession() - await this.sequenceStorage.clearExplicitSessions() - await this.sequenceStorage.clearSessionlessConnection() - } - - private async _saveEthAuthProofIfProvided(ethAuthProof?: ETHAuthProof): Promise { - if (!ethAuthProof) { - return - } - await this.sequenceStorage.saveEthAuthProof(ethAuthProof) - } - - private _getCachedSignedCall(calls: Payload.Call[]): { to: Address.Address; data: Hex.Hex } | null { - if (!this.lastSignedCallCache) { - return null - } - const ttlMs = 30_000 - if (Date.now() - this.lastSignedCallCache.createdAtMs > ttlMs) { - this.lastSignedCallCache = undefined - return null - } - const fingerprint = this._fingerprintCalls(calls) - if (!fingerprint) { - return null - } - if (fingerprint !== this.lastSignedCallCache.fingerprint) { - return null - } - return this.lastSignedCallCache.signedCall - } - - private _fingerprintCalls(calls: Payload.Call[]): string | null { - try { - return JSON.stringify( - calls.map((call) => ({ - to: call.to, - value: call.value?.toString() ?? '0', - data: call.data ?? '0x', - gasLimit: call.gasLimit?.toString() ?? '0', - delegateCall: call.delegateCall ?? false, - onlyFallback: call.onlyFallback ?? false, - behaviorOnError: call.behaviorOnError ?? 'revert', - })), - ) - } catch (error) { - console.warn('ChainSessionManager._fingerprintCalls failed:', error) - return null - } - } -} diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts deleted file mode 100644 index e580bd9127..0000000000 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ /dev/null @@ -1,1163 +0,0 @@ -import { Address, Hex } from 'ox' - -import { type ExplicitSession, type ExplicitSessionConfig, type ImplicitSession, type Session } from './index.js' - -import { ChainSessionManager } from './ChainSessionManager.js' -import { DappTransport } from './DappTransport.js' -import { ConnectionError, InitializationError, SigningError, TransactionError } from './utils/errors.js' -import { SequenceStorage, WebStorage, type SessionlessConnectionData } from './utils/storage.js' -import { - CreateNewSessionResponse, - DappClientExplicitSessionEventListener, - DappClientWalletActionEventListener, - FeeOption, - GetFeeTokensResponse, - GuardConfig, - LoginMethod, - EthAuthSettings, - RandomPrivateKeyFn, - RequestActionType, - ETHAuthProof, - SendWalletTransactionPayload, - SequenceSessionStorage, - SignMessagePayload, - SignTypedDataPayload, - Transaction, - TransactionRequest, - TransportMode, - WalletActionResponse, -} from './types/index.js' -import { TypedData } from 'ox/TypedData' -import { KEYMACHINE_URL, NODES_URL, RELAYER_URL } from './utils/constants.js' -import { getRelayerUrl, getRpcUrl } from './utils/index.js' -import { Relayer } from '@0xsequence/relayer' - -export type DappClientEventListener = (data?: unknown) => void - -interface DappClientEventMap { - sessionsUpdated: () => void - walletActionResponse: DappClientWalletActionEventListener - explicitSessionResponse: DappClientExplicitSessionEventListener -} - -/** - * The main entry point for interacting with the Wallet. - * This client manages user sessions across multiple chains, handles connection - * and disconnection, and provides methods for signing and sending transactions. - * - * @example - * // It is recommended to manage a singleton instance of this client. - * const dappClient = new DappClient('http://localhost:5173'); - * - * async function main() { - * // Initialize the client on page load to restore existing sessions. - * await dappClient.initialize(); - * - * // If not connected, prompt the user to connect. - * if (!dappClient.isInitialized) { - * await client.connect(137, window.location.origin); - * } - * } - */ -export class DappClient { - public isInitialized = false - - public loginMethod: LoginMethod | null = null - public userEmail: string | null = null - public guard?: GuardConfig - - public readonly origin: string - - private chainSessionManagers: Map = new Map() - - private walletUrl: string - private transport: DappTransport | null = null - private transportModeSetting: TransportMode - private projectAccessKey: string - private nodesUrl: string - private relayerUrl: string - private keymachineUrl: string - private sequenceStorage: SequenceStorage - private redirectPath?: string - private sequenceSessionStorage?: SequenceSessionStorage - private randomPrivateKeyFn?: RandomPrivateKeyFn - private redirectActionHandler?: (url: string) => void - private canUseIndexedDb: boolean - - private isInitializing = false - - private walletAddress: Address.Address | null = null - private hasSessionlessConnection = false - private cachedSessionlessConnection: SessionlessConnectionData | null = null - private eventListeners: { - [K in keyof DappClientEventMap]?: Set - } = {} - - private get isBrowser(): boolean { - return typeof window !== 'undefined' && typeof document !== 'undefined' - } - - /** - * @param walletUrl The URL of the Wallet Webapp. - * @param origin The origin of the dapp - * @param projectAccessKey Your project access key from sequence.build. Used for services like relayer and nodes. - * @param options Configuration options for the client. - * @param options.transportMode The communication mode to use with the wallet. Defaults to 'popup'. - * @param options.redirectPath The path to redirect back to after a redirect-based flow. Constructed with origin + redirectPath. - * @param options.nodesUrl The URL template for the nodes service. Use `{network}` as a placeholder for the network name. Defaults to the Sequence nodes ('https://nodes.sequence.app/{network}'). - * @param options.relayerUrl The URL template for the relayer service. Use `{network}` as a placeholder for the network name. Defaults to the Sequence relayer ('https://dev-{network}-relayer.sequence.app'). - * @param options.keymachineUrl The URL of the key management service. - * @param options.sequenceStorage The storage implementation for persistent session data. Defaults to WebStorage using IndexedDB. - * @param options.sequenceSessionStorage The storage implementation for temporary data (e.g., pending requests). Defaults to sessionStorage. - * @param options.randomPrivateKeyFn A function to generate random private keys for new sessions. - * @param options.redirectActionHandler A handler to manually control navigation for redirect flows. - * @param options.canUseIndexedDb A flag to enable or disable the use of IndexedDB for caching. - */ - constructor( - walletUrl: string, - origin: string, - projectAccessKey: string, - options?: { - transportMode?: TransportMode - redirectPath?: string - keymachineUrl?: string - nodesUrl?: string - relayerUrl?: string - sequenceStorage?: SequenceStorage - sequenceSessionStorage?: SequenceSessionStorage - randomPrivateKeyFn?: RandomPrivateKeyFn - redirectActionHandler?: (url: string) => void - canUseIndexedDb?: boolean - }, - ) { - const { - transportMode = TransportMode.POPUP, - keymachineUrl = KEYMACHINE_URL, - redirectPath, - sequenceStorage = new WebStorage(), - sequenceSessionStorage, - randomPrivateKeyFn, - redirectActionHandler, - canUseIndexedDb = true, - } = options || {} - - this.walletUrl = walletUrl - this.transportModeSetting = transportMode - this.projectAccessKey = projectAccessKey - this.nodesUrl = options?.nodesUrl || NODES_URL - this.relayerUrl = options?.relayerUrl || RELAYER_URL - this.origin = origin - this.keymachineUrl = keymachineUrl - this.sequenceStorage = sequenceStorage - this.redirectPath = redirectPath - this.sequenceSessionStorage = sequenceSessionStorage - this.randomPrivateKeyFn = randomPrivateKeyFn - this.redirectActionHandler = redirectActionHandler - this.canUseIndexedDb = canUseIndexedDb - } - - /** - * @returns The transport mode of the client. {@link TransportMode} - */ - public get transportMode(): TransportMode { - return this.transport?.mode ?? this.transportModeSetting - } - - /** - * Registers an event listener for a specific event. - * @param event The event to listen for. - * @param listener The listener to call when the event occurs. - * @returns A function to remove the listener. - * - * @example - * useEffect(() => { - * const handleWalletAction = (response) => { - * console.log('Received wallet action response:', response); - * }; - * - * const unsubscribe = dappClient.on("walletActionResponse", handleWalletAction); - * - * return () => unsubscribe(); - * }, [dappClient]); - */ - public on(event: K, listener: DappClientEventMap[K]): () => void { - if (!this.eventListeners[event]) { - // @ts-expect-error - indexing into evenListeners will improperly create a union of all the possible types - this.eventListeners[event] = new Set() - } - this.eventListeners[event].add(listener) - return () => { - this.eventListeners[event]?.delete(listener) - } - } - - /** - * Retrieves the wallet address of the current session. - * @returns The wallet address of the current session, or null if not initialized. {@link Address.Address} - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const walletAddress = dappClient.getWalletAddress(); - * console.log('Wallet address:', walletAddress); - * } - */ - public getWalletAddress(): Address.Address | null { - return this.walletAddress - } - - /** - * Retrieves a list of all active explicit sessions (signers) associated with the current wallet. - * @returns An array of all the active explicit sessions. {@link ExplicitSession[]} - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const explicitSessions = dappClient.getAllExplicitSessions(); - * console.log('Sessions:', explicitSessions); - * } - */ - public getAllExplicitSessions(): ExplicitSession[] { - const allExplicitSessions = new Map() - Array.from(this.chainSessionManagers.values()).forEach((chainSessionManager) => { - chainSessionManager.getExplicitSessions().forEach((session) => { - const uniqueKey = session.sessionAddress?.toLowerCase() - if (!allExplicitSessions.has(uniqueKey)) { - allExplicitSessions.set(uniqueKey, session) - } - }) - }) - return Array.from(allExplicitSessions.values()) - } - - /** - * Retrieves a list of all active implicit sessions (signers) associated with the current wallet. - * @note There can only be one implicit session per chain. - * @returns An array of all the active implicit sessions. {@link ImplicitSession[]} - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const implicitSessions = dappClient.getAllImplicitSessions(); - * console.log('Sessions:', implicitSessions); - * } - */ - public getAllImplicitSessions(): ImplicitSession[] { - const allImplicitSessions = new Map() - Array.from(this.chainSessionManagers.values()).forEach((chainSessionManager) => { - const session = chainSessionManager.getImplicitSession() - if (!session) return - const uniqueKey = session?.sessionAddress?.toLowerCase() - if (uniqueKey && !allImplicitSessions.has(uniqueKey)) { - allImplicitSessions.set(uniqueKey, session) - } - }) - return Array.from(allImplicitSessions.values()) - } - - /** - * Gets all the sessions (explicit and implicit) managed by the client. - * @returns An array of session objects. {@link Session[]} - */ - public getAllSessions(): Session[] { - return [...this.getAllImplicitSessions(), ...this.getAllExplicitSessions()] - } - - /** - * @private Loads the client's state from storage, initializing all chain managers - * for previously established sessions. - */ - private async _loadStateFromStorage(): Promise { - const implicitSession = await this.sequenceStorage.getImplicitSession() - - const [explicitSessions, sessionlessConnection, sessionlessSnapshot] = await Promise.all([ - this.sequenceStorage.getExplicitSessions(), - this.sequenceStorage.getSessionlessConnection(), - this.sequenceStorage.getSessionlessConnectionSnapshot - ? this.sequenceStorage.getSessionlessConnectionSnapshot() - : Promise.resolve(null), - ]) - this.cachedSessionlessConnection = sessionlessSnapshot ?? null - const chainIdsToInitialize = new Set([ - ...(implicitSession?.chainId !== undefined ? [implicitSession.chainId] : []), - ...explicitSessions.map((s) => s.chainId), - ]) - - if (chainIdsToInitialize.size === 0) { - if (sessionlessConnection) { - await this.applySessionlessConnectionState( - sessionlessConnection.walletAddress, - sessionlessConnection.loginMethod, - sessionlessConnection.userEmail, - sessionlessConnection.guard, - false, - ) - } else { - this.isInitialized = false - this.hasSessionlessConnection = false - this.walletAddress = null - this.loginMethod = null - this.userEmail = null - this.guard = undefined - this.emit('sessionsUpdated') - } - return - } - - this.hasSessionlessConnection = false - - const initPromises = Array.from(chainIdsToInitialize).map((chainId) => - this.getChainSessionManager(chainId).initialize(), - ) - - const result = await Promise.all(initPromises) - - this.walletAddress = implicitSession?.walletAddress || explicitSessions[0]?.walletAddress || null - this.loginMethod = result[0]?.loginMethod || null - this.userEmail = result[0]?.userEmail || null - this.guard = implicitSession?.guard || explicitSessions.find((s) => !!s.guard)?.guard - await this.sequenceStorage.clearSessionlessConnection() - if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { - await this.sequenceStorage.clearSessionlessConnectionSnapshot() - } - this.cachedSessionlessConnection = null - - this.isInitialized = true - this.emit('sessionsUpdated') - } - - /** - * Initializes the client by loading any existing session from storage and handling any pending redirect responses. - * This should be called once when your application loads. - * - * @remarks - * An `Implicit` session is a session that can interact only with specific, Dapp-defined contracts. - * An `Explicit` session is a session that can interact with any contract as long as the user has granted the necessary permissions. - * - * @throws If the initialization process fails. {@link InitializationError} - * - * @returns A promise that resolves when initialization is complete. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - */ - async initialize(): Promise { - if (this.isInitializing) return - this.isInitializing = true - - try { - // First, load any existing session from storage. This is crucial so that - // when we process a redirect for an explicit session, we know the wallet address. - await this._loadStateFromStorage() - - // Now, check if there's a response from a redirect flow. - if (await this.sequenceStorage.isRedirectRequestPending()) { - try { - // Attempt to handle any response from the wallet redirect. - await this.handleRedirectResponse() - } finally { - // We have to clear pending redirect data here as well in case we received an error from the wallet. - await this.sequenceStorage.setPendingRedirectRequest(false) - await this.sequenceStorage.getAndClearTempSessionPk() - } - - // After handling the redirect, the session state will have changed, - // so we must load it again. - await this._loadStateFromStorage() - } - } catch (e) { - await this.disconnect() - throw e - } finally { - this.isInitializing = false - } - } - - /** - * Indicates if there is cached sessionless connection data that can be restored. - */ - public async hasRestorableSessionlessConnection(): Promise { - if (this.cachedSessionlessConnection) return true - this.cachedSessionlessConnection = this.sequenceStorage.getSessionlessConnectionSnapshot - ? await this.sequenceStorage.getSessionlessConnectionSnapshot() - : null - return this.cachedSessionlessConnection !== null - } - - /** - * Returns the cached sessionless connection metadata without altering client state. - * @returns The cached sessionless connection or null if none is available. - */ - public async getSessionlessConnectionInfo(): Promise { - if (!this.cachedSessionlessConnection) { - this.cachedSessionlessConnection = this.sequenceStorage.getSessionlessConnectionSnapshot - ? await this.sequenceStorage.getSessionlessConnectionSnapshot() - : null - } - if (!this.cachedSessionlessConnection) return null - return { - walletAddress: this.cachedSessionlessConnection.walletAddress, - loginMethod: this.cachedSessionlessConnection.loginMethod, - userEmail: this.cachedSessionlessConnection.userEmail, - guard: this.cachedSessionlessConnection.guard, - } - } - - /** - * Returns the latest persisted ETHAuth proof, if one has been received from the wallet. - */ - public async getEthAuthProof(): Promise { - return this.sequenceStorage.getEthAuthProof() - } - - /** - * Restores a sessionless connection that was previously persisted via {@link disconnect} or a connect flow. - * @returns A promise that resolves to true if a sessionless connection was applied. - */ - public async restoreSessionlessConnection(): Promise { - const sessionlessConnection = - this.cachedSessionlessConnection ?? - (this.sequenceStorage.getSessionlessConnectionSnapshot - ? await this.sequenceStorage.getSessionlessConnectionSnapshot() - : null) - if (!sessionlessConnection) { - return false - } - - await this.applySessionlessConnectionState( - sessionlessConnection.walletAddress, - sessionlessConnection.loginMethod, - sessionlessConnection.userEmail, - sessionlessConnection.guard, - ) - if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { - await this.sequenceStorage.clearSessionlessConnectionSnapshot() - } - this.cachedSessionlessConnection = null - return true - } - - /** - * Handles the redirect response from the Wallet. - * This is called automatically on `initialize()` for web environments but can be called manually - * with a URL in environments like React Native. - * @param url The full redirect URL from the wallet. If not provided, it will be read from the browser's current location. - * @returns A promise that resolves when the redirect has been handled. - */ - public async handleRedirectResponse(url?: string): Promise { - const pendingRequest = await this.sequenceStorage.peekPendingRequest() - - if (!this.transport && this.transportMode === TransportMode.POPUP && !this.isBrowser) { - return - } - - const response = await this.ensureTransport().getRedirectResponse(true, url) - if (!response) { - return - } - - const { action } = response - const chainId = pendingRequest?.chainId - - if ( - action === RequestActionType.SIGN_MESSAGE || - action === RequestActionType.SIGN_TYPED_DATA || - action === RequestActionType.SEND_WALLET_TRANSACTION - ) { - if (chainId === undefined) { - throw new InitializationError('Could not find a chainId for the pending signature request.') - } - const eventPayload = { - action, - response: 'payload' in response ? response.payload : undefined, - error: 'error' in response ? response.error : undefined, - chainId, - } - this.emit('walletActionResponse', eventPayload) - } else if (chainId !== undefined) { - if ('error' in response && response.error && action === RequestActionType.CREATE_NEW_SESSION) { - await this.sequenceStorage.setPendingRedirectRequest(false) - await this.sequenceStorage.getAndClearTempSessionPk() - await this.sequenceStorage.getAndClearPendingRequest() - - if (this.hasSessionlessConnection) { - const sessionlessConnection = await this.sequenceStorage.getSessionlessConnection() - if (sessionlessConnection) { - await this.applySessionlessConnectionState( - sessionlessConnection.walletAddress, - sessionlessConnection.loginMethod, - sessionlessConnection.userEmail, - sessionlessConnection.guard, - false, - ) - } else if (this.walletAddress) { - await this.applySessionlessConnectionState( - this.walletAddress, - this.loginMethod, - this.userEmail, - this.guard, - false, - ) - } - } - return - } - - const chainSessionManager = this.getChainSessionManager(chainId) - if (!chainSessionManager.isInitialized && this.walletAddress) { - chainSessionManager.initializeWithWallet(this.walletAddress) - } - const handled = await chainSessionManager.handleRedirectResponse(response) - if (handled && action === RequestActionType.CREATE_NEW_SESSION) { - const hasImplicit = !!chainSessionManager.getImplicitSession() - const hasExplicit = chainSessionManager.getExplicitSessions().length > 0 - if (hasImplicit || hasExplicit) { - this.hasSessionlessConnection = false - await this._loadStateFromStorage() - } else if ('payload' in response && response.payload) { - const payload = response.payload as CreateNewSessionResponse - const walletAddress = chainSessionManager.getWalletAddress() ?? Address.from(payload.walletAddress) - await this.applySessionlessConnectionState( - walletAddress, - chainSessionManager.loginMethod, - chainSessionManager.userEmail, - chainSessionManager.getGuard(), - ) - } - } else if (handled && action === RequestActionType.ADD_EXPLICIT_SESSION) { - this.hasSessionlessConnection = false - await this._loadStateFromStorage() - } - } else { - throw new InitializationError(`Could not find a pending request context for the redirect action: ${action}`) - } - } - - /** - * Initiates a connection with the wallet and creates a new session. - * @param chainId The primary chain ID for the new session. - * @param sessionConfig Session configuration {@link ExplicitSessionConfig} to request for an initial session. - * @param options (Optional) Connection options, such as a preferred login method or email for social or email logins. - * @throws If the connection process fails. {@link ConnectionError} - * @throws If a session already exists. {@link InitializationError} - * - * @returns A promise that resolves when the connection is established. - * - * @example - * // Connect with an explicit session configuration - * const explicitSessionConfig: ExplicitSessionConfig = { - * valueLimit: 0n, - * deadline: BigInt(Date.now() + 1000 * 60 * 60), // 1 hour - * permissions: [...], - * chainId: 137 - * }; - * await dappClient.connect(137, explicitSessionConfig, { - * preferredLoginMethod: 'google', - * }); - */ - async connect( - chainId: number, - sessionConfig?: ExplicitSessionConfig, - options: { - preferredLoginMethod?: LoginMethod - email?: string - includeImplicitSession?: boolean - ethAuth?: EthAuthSettings - } = {}, - ): Promise { - if (this.isInitialized) { - throw new InitializationError('A session already exists. Disconnect first.') - } - - try { - const chainSessionManager = this.getChainSessionManager(chainId) - const shouldCreateSession = !!sessionConfig || (options.includeImplicitSession ?? false) - this.hasSessionlessConnection = false - await chainSessionManager.createNewSession(this.origin, sessionConfig, options) - - // For popup mode, we need to manually update the state and emit an event. - // For redirect mode, this code won't be reached; the page will navigate away. - if (this.transportMode === TransportMode.POPUP) { - const hasImplicitSession = !!chainSessionManager.getImplicitSession() - const hasExplicitSessions = chainSessionManager.getExplicitSessions().length > 0 - if (shouldCreateSession && (hasImplicitSession || hasExplicitSessions)) { - await this._loadStateFromStorage() - } else { - const walletAddress = chainSessionManager.getWalletAddress() - if (!walletAddress) { - throw new InitializationError('Wallet address missing after connect.') - } - await this.applySessionlessConnectionState( - walletAddress, - chainSessionManager.loginMethod, - chainSessionManager.userEmail, - chainSessionManager.getGuard(), - ) - } - } - } catch (err) { - await this.disconnect() - throw new ConnectionError(`Connection failed: ${err}`) - } - } - - /** - * Upgrades an existing sessionless connection by creating implicit and/or explicit sessions. - * @param chainId The chain ID to target for the new sessions. - * @param sessionConfig The explicit session configuration to request. {@link ExplicitSessionConfig} - * @param options Connection options such as preferred login method or email for social/email logins. - * @throws If no sessionless connection is available or the session upgrade fails. {@link InitializationError} - * @throws If neither an implicit nor explicit session is requested. {@link InitializationError} - * - * @returns A promise that resolves once the session upgrade completes. - */ - async upgradeSessionlessConnection( - chainId: number, - sessionConfig?: ExplicitSessionConfig, - options: { - preferredLoginMethod?: LoginMethod - email?: string - includeImplicitSession?: boolean - ethAuth?: EthAuthSettings - } = {}, - ): Promise { - if (!this.isInitialized || !this.hasSessionlessConnection || !this.walletAddress) { - throw new InitializationError('A sessionless connection is required before requesting new sessions.') - } - - const shouldCreateSession = !!sessionConfig || (options.includeImplicitSession ?? false) - if (!shouldCreateSession) { - throw new InitializationError( - 'Cannot upgrade a sessionless connection without requesting an implicit or explicit session.', - ) - } - - const sessionlessSnapshot = { - walletAddress: this.walletAddress, - loginMethod: this.loginMethod, - userEmail: this.userEmail, - guard: this.guard, - } - - try { - let chainSessionManager = this.chainSessionManagers.get(chainId) - if ( - chainSessionManager && - chainSessionManager.isInitialized && - !chainSessionManager.getImplicitSession() && - chainSessionManager.getExplicitSessions().length === 0 - ) { - this.chainSessionManagers.delete(chainId) - chainSessionManager = undefined - } - chainSessionManager = chainSessionManager ?? this.getChainSessionManager(chainId) - await chainSessionManager.createNewSession(this.origin, sessionConfig, options) - - if (this.transportMode === TransportMode.POPUP) { - const hasImplicitSession = !!chainSessionManager.getImplicitSession() - const hasExplicitSessions = chainSessionManager.getExplicitSessions().length > 0 - - if (shouldCreateSession && (hasImplicitSession || hasExplicitSessions)) { - await this._loadStateFromStorage() - } else { - const walletAddress = chainSessionManager.getWalletAddress() - if (!walletAddress) { - throw new InitializationError('Wallet address missing after connect.') - } - await this.applySessionlessConnectionState( - walletAddress, - chainSessionManager.loginMethod, - chainSessionManager.userEmail, - chainSessionManager.getGuard(), - ) - } - } - } catch (err) { - await this.applySessionlessConnectionState( - sessionlessSnapshot.walletAddress, - sessionlessSnapshot.loginMethod, - sessionlessSnapshot.userEmail, - sessionlessSnapshot.guard, - ) - throw new ConnectionError(`Connection failed: ${err}`) - } - } - - /** - * Adds a new explicit session for a given chain to an existing wallet. - * @remarks - * An `explicit session` is a session that can interact with any contract, subject to user-approved permissions. - * @param session The explicit session to add. {@link ExplicitSession} - * - * @throws If the session cannot be added. {@link AddExplicitSessionError} - * @throws If the client or relevant chain is not initialized. {@link InitializationError} - * - * @returns A promise that resolves when the session is added. - * - * @example - * ... - * import { ExplicitSession, Utils } from "@0xsequence/wallet-core"; - * import { DappClient } from "@0xsequence/sessions"; - * ... - * - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * const amount = 1000000; - * const USDC_ADDRESS = '0x...'; - * - * if (dappClient.isInitialized) { - * // Allow Dapp (Session Signer) to transfer "amount" of USDC - * const explicitSession: ExplicitSession = { - * chainId: Number(chainId), - * valueLimit: 0n, // Not allowed to transfer native tokens (ETH, etc) - * deadline: BigInt(Date.now() + 1000 * 60 * 5000), // 5000 minutes from now - * permissions: [Utils.ERC20PermissionBuilder.buildTransfer(USDC_ADDRESS, amount)] - * }; - * await dappClient.addExplicitSession(explicitSession); - * } - */ - async addExplicitSession(explicitSessionConfig: ExplicitSessionConfig): Promise { - if (!this.isInitialized || !this.walletAddress) - throw new InitializationError('Cannot add an explicit session without an existing wallet.') - - const chainSessionManager = this.getChainSessionManager(explicitSessionConfig.chainId) - if (!chainSessionManager.isInitialized) { - chainSessionManager.initializeWithWallet(this.walletAddress) - } - await chainSessionManager.addExplicitSession(explicitSessionConfig) - - if (this.transportMode === TransportMode.POPUP) { - await this._loadStateFromStorage() - } - } - - /** - * Modifies an explicit session for a given chain - * @param explicitSession The explicit session to modify. {@link ExplicitSession} - * - * @throws If the client or relevant chain is not initialized. {@link InitializationError} - * @throws If something goes wrong while modifying the session. {@link ModifyExplicitSessionError} - * - * @returns A promise that resolves when the session permissions are updated. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * // Increase the deadline of the current session by 24 hours - * const currentExplicitSession = {...} - * const newExplicitSession = {...currentExplicitSession, deadline: currentExplicitSession.deadline + 24 * 60 * 60} - * await dappClient.modifyExplicitSession(newExplicitSession); - * } - */ - async modifyExplicitSession(explicitSession: ExplicitSession): Promise { - if (!this.isInitialized || !this.walletAddress) - throw new InitializationError('Cannot modify an explicit session without an existing wallet.') - - const chainSessionManager = this.getChainSessionManager(explicitSession.chainId) - if (!chainSessionManager.isInitialized) { - chainSessionManager.initializeWithWallet(this.walletAddress) - } - await chainSessionManager.modifyExplicitSession(explicitSession) - - if (this.transportMode === TransportMode.POPUP) { - await this._loadStateFromStorage() - } - } - - /** - * Gets the gas fee options for an array of transactions. - * @param chainId The chain ID on which to get the fee options. - * @param transactions An array of transactions to get fee options for. These transactions will not be sent. - * @throws If the fee options cannot be fetched. {@link FeeOptionError} - * @throws If the client or relevant chain is not initialized. {@link InitializationError} - * - * @returns A promise that resolves with the fee options. {@link FeeOption[]} - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const transactions: Transaction[] = [ - * { - * to: '0x...', - * value: 0n, - * data: '0x...' - * } - * ]; - * const feeOptions = await dappClient.getFeeOptions(1, transactions); - * const feeOption = feeOptions[0]; - * // use the fee option to pay the gas - * const txHash = await dappClient.sendTransaction(1, transactions, feeOption); - * } - */ - async getFeeOptions(chainId: number, transactions: Transaction[]): Promise { - const chainSessionManager = await this.getOrInitializeChainManager(chainId) - return await chainSessionManager.getFeeOptions(transactions) - } - - /** - * Fetches fee tokens for a chain. - * @returns A promise that resolves with the fee tokens response. {@link GetFeeTokensResponse} - * @throws If the fee tokens cannot be fetched. {@link InitializationError} - */ - async getFeeTokens(chainId: number): Promise { - const relayer = new Relayer.RpcRelayer( - getRelayerUrl(chainId, this.relayerUrl), - chainId, - getRpcUrl(chainId, this.nodesUrl, this.projectAccessKey), - ) - return await relayer.feeTokens() - } - - /** - * Checks if the current session has permission to execute a set of transactions on a specific chain. - * @param chainId The chain ID on which to check the permissions. - * @param transactions An array of transactions to check permissions for. - * @returns A promise that resolves to true if the session has permission, otherwise false. - */ - async hasPermission(chainId: number, transactions: Transaction[]): Promise { - if (!this.isInitialized) { - return false - } - try { - const chainSessionManager = await this.getOrInitializeChainManager(chainId) - return await chainSessionManager.hasPermission(transactions) - } catch (error) { - console.warn( - `hasPermission check failed for chain ${chainId}:`, - error instanceof Error ? error.message : String(error), - ) - return false - } - } - - /** - * Signs and sends a transaction using an available session signer. - * @param chainId The chain ID on which to send the transaction. - * @param transactions An array of transactions to be executed atomically in a single batch. {@link Transaction} - * @param feeOption (Optional) The selected fee option to sponsor the transaction. {@link FeeOption} - * @throws {TransactionError} If the transaction fails to send or confirm. - * @throws {InitializationError} If the client or relevant chain is not initialized. - * - * @returns A promise that resolves with the transaction hash. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const transaction = { - * to: '0x...', - * value: 0n, - * data: '0x...' - * }; - * - * const txHash = await dappClient.sendTransaction(1, [transaction]); - */ - async sendTransaction(chainId: number, transactions: Transaction[], feeOption?: FeeOption): Promise { - const chainSessionManager = await this.getOrInitializeChainManager(chainId) - return await chainSessionManager.buildSignAndSendTransactions(transactions, feeOption) - } - - /** - * Signs a standard message (EIP-191) using an available session signer. - * @param chainId The chain ID on which to sign the message. - * @param message The message to sign. - * @throws If the message cannot be signed. {@link SigningError} - * @throws If the client is not initialized. {@link InitializationError} - * - * @returns A promise that resolves when the signing process is initiated. The signature is delivered via the `walletActionResponse` event listener. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const message = 'Hello, world!'; - * await dappClient.signMessage(1, message); - * } - */ - async signMessage(chainId: number, message: string): Promise { - if (!this.isInitialized || !this.walletAddress) throw new InitializationError('Not initialized') - const payload: SignMessagePayload = { - address: this.walletAddress, - message, - chainId: chainId, - } - try { - await this._requestWalletAction(RequestActionType.SIGN_MESSAGE, payload, chainId) - } catch (err) { - throw new SigningError(`Signing message failed: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Signs a typed data object (EIP-712) using an available session signer. - * @param chainId The chain ID on which to sign the typed data. - * @param typedData The typed data object to sign. - * @throws If the typed data cannot be signed. {@link SigningError} - * @throws If the client is not initialized. {@link InitializationError} - * - * @returns A promise that resolves when the signing process is initiated. The signature is returned in the `walletActionResponse` event listener. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * const typedData = {...} - * await dappClient.signTypedData(1, typedData); - * } - */ - async signTypedData(chainId: number, typedData: TypedData): Promise { - if (!this.isInitialized || !this.walletAddress) throw new InitializationError('Not initialized') - const payload: SignTypedDataPayload = { - address: this.walletAddress, - typedData, - chainId: chainId, - } - try { - await this._requestWalletAction(RequestActionType.SIGN_TYPED_DATA, payload, chainId) - } catch (err) { - throw new SigningError(`Signing typed data failed: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Sends transaction data to be signed and submitted by the wallet. - * @param chainId The chain ID on which to send the transaction. - * @param transactionRequest The transaction request object. - * @throws If the transaction cannot be sent. {@link TransactionError} - * @throws If the client is not initialized. {@link InitializationError} - * - * @returns A promise that resolves when the sending process is initiated. The transaction hash is delivered via the `walletActionResponse` event listener. - */ - async sendWalletTransaction(chainId: number, transactionRequest: TransactionRequest): Promise { - if (!this.isInitialized || !this.walletAddress) throw new InitializationError('Not initialized') - const payload: SendWalletTransactionPayload = { - address: this.walletAddress, - transactionRequest, - chainId: chainId, - } - try { - await this._requestWalletAction(RequestActionType.SEND_WALLET_TRANSACTION, payload, chainId) - } catch (err) { - throw new TransactionError( - `Sending transaction data to wallet failed: ${err instanceof Error ? err.message : String(err)}`, - ) - } - } - - /** - * Disconnects the client, clearing all session data from browser storage. - * @remarks This action does not revoke the sessions on-chain. Sessions remain active until they expire or are manually revoked by the user in their wallet. - * @param options Options to control the disconnection behavior. - * @param options.keepSessionlessConnection When true, retains the latest wallet metadata so it can be restored later as a sessionless connection. Defaults to true. - * @returns A promise that resolves when disconnection is complete. - * - * @example - * const dappClient = new DappClient('http://localhost:5173'); - * await dappClient.initialize(); - * - * if (dappClient.isInitialized) { - * await dappClient.disconnect({ keepSessionlessConnection: true }); - * } - */ - async disconnect(options?: { keepSessionlessConnection?: boolean }): Promise { - const keepSessionlessConnection = options?.keepSessionlessConnection ?? true - - if (this.transport) { - this.transport.destroy() - } - this.transport = null - - this.chainSessionManagers.clear() - const sessionlessSnapshot = - keepSessionlessConnection && this.walletAddress - ? { - walletAddress: this.walletAddress, - loginMethod: this.loginMethod ?? undefined, - userEmail: this.userEmail ?? undefined, - guard: this.guard, - } - : undefined - - await this.sequenceStorage.clearAllData() - - if (sessionlessSnapshot) { - if (this.sequenceStorage.saveSessionlessConnectionSnapshot) { - await this.sequenceStorage.saveSessionlessConnectionSnapshot(sessionlessSnapshot) - } - this.cachedSessionlessConnection = sessionlessSnapshot - } else { - if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { - await this.sequenceStorage.clearSessionlessConnectionSnapshot() - } - this.cachedSessionlessConnection = null - } - - this.isInitialized = false - this.walletAddress = null - this.loginMethod = null - this.userEmail = null - this.guard = undefined - this.hasSessionlessConnection = false - this.emit('sessionsUpdated') - } - - /** - * @private Emits an event to all registered listeners. - * @param event The event to emit. - * @param args The data to emit with the event. - */ - private emit(event: K, ...args: Parameters): void { - const listeners = this.eventListeners[event] - if (listeners) { - listeners.forEach((listener) => (listener as (...a: typeof args) => void)(...args)) - } - } - - private ensureTransport(): DappTransport { - if (!this.transport) { - if (this.transportModeSetting === TransportMode.POPUP && !this.isBrowser) { - throw new InitializationError('Popup transport requires a browser environment.') - } - this.transport = new DappTransport( - this.walletUrl, - this.transportModeSetting, - undefined, - this.sequenceSessionStorage, - this.redirectActionHandler, - ) - } - return this.transport - } - - private async applySessionlessConnectionState( - walletAddress: Address.Address, - loginMethod?: LoginMethod | null, - userEmail?: string | null, - guard?: GuardConfig, - persist: boolean = true, - ): Promise { - this.walletAddress = walletAddress - this.loginMethod = loginMethod ?? null - this.userEmail = userEmail ?? null - this.guard = guard - this.hasSessionlessConnection = true - this.isInitialized = true - this.cachedSessionlessConnection = null - this.emit('sessionsUpdated') - if (persist) { - await this.sequenceStorage.saveSessionlessConnection({ - walletAddress, - loginMethod: this.loginMethod ?? undefined, - userEmail: this.userEmail ?? undefined, - guard: this.guard, - }) - } - } - - private async _requestWalletAction( - action: (typeof RequestActionType)['SIGN_MESSAGE' | 'SIGN_TYPED_DATA' | 'SEND_WALLET_TRANSACTION'], - payload: SignMessagePayload | SignTypedDataPayload | SendWalletTransactionPayload, - chainId: number, - ): Promise { - if (!this.isInitialized || !this.walletAddress) { - throw new InitializationError('Session not initialized. Cannot request wallet action.') - } - - try { - const redirectUrl = this.origin + (this.redirectPath ? this.redirectPath : '') - const path = action === RequestActionType.SEND_WALLET_TRANSACTION ? '/request/transaction' : '/request/sign' - const transport = this.ensureTransport() - - if (transport.mode === TransportMode.REDIRECT) { - await this.sequenceStorage.savePendingRequest({ - action, - payload, - chainId: chainId, - }) - await this.sequenceStorage.setPendingRedirectRequest(true) - await transport.sendRequest(action, redirectUrl, payload, { path }) - } else { - const response = await transport.sendRequest(action, redirectUrl, payload, { - path, - }) - this.emit('walletActionResponse', { action, response, chainId }) - } - } catch (err) { - const error = new SigningError(err instanceof Error ? err.message : String(err)) - this.emit('walletActionResponse', { action, error, chainId }) - throw error - } finally { - if (this.transportMode === TransportMode.POPUP && this.transport) { - this.transport.closeWallet() - } - } - } - - /** - * @private Retrieves or creates and initializes a ChainSessionManager for a given chain ID. - * @param chainId The chain ID to get the ChainSessionManager for. - * @returns The initialized ChainSessionManager for the given chain ID. - */ - private async getOrInitializeChainManager(chainId: number): Promise { - if (!this.isInitialized || !this.walletAddress) { - throw new InitializationError('DappClient is not initialized.') - } - const manager = this.getChainSessionManager(chainId) - if (!manager.isInitialized) { - await manager.initialize() - } - if (!manager.isInitialized) { - throw new InitializationError(`ChainSessionManager for chain ${chainId} could not be initialized.`) - } - if (!manager.getImplicitSession() && manager.getExplicitSessions().length === 0) { - throw new InitializationError('No sessions are available for the requested action.') - } - return manager - } - - /** - * @private Retrieves or creates a ChainSessionManager for a given chain ID. - * @param chainId The chain ID to get the ChainSessionManager for. - * @returns The ChainSessionManager for the given chain ID. {@link ChainSessionManager} - */ - private getChainSessionManager(chainId: number): ChainSessionManager { - let chainSessionManager = this.chainSessionManagers.get(chainId) - if (!chainSessionManager) { - const transport = this.ensureTransport() - chainSessionManager = new ChainSessionManager( - chainId, - transport, - this.projectAccessKey, - this.keymachineUrl, - this.nodesUrl, - this.relayerUrl, - this.sequenceStorage, - this.origin + (this.redirectPath ? this.redirectPath : ''), - this.guard, - this.randomPrivateKeyFn, - this.canUseIndexedDb, - ) - this.chainSessionManagers.set(chainId, chainSessionManager) - - chainSessionManager.on('explicitSessionResponse', (data) => { - this.emit('explicitSessionResponse', { ...data, chainId }) - }) - } - return chainSessionManager - } -} diff --git a/packages/wallet/dapp-client/src/DappTransport.ts b/packages/wallet/dapp-client/src/DappTransport.ts deleted file mode 100644 index 090b1070b8..0000000000 --- a/packages/wallet/dapp-client/src/DappTransport.ts +++ /dev/null @@ -1,565 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { jsonReplacers, jsonRevivers } from './utils/index.js' -import { - MessageType, - PendingRequest, - PopupModeOptions, - SendRequestOptions, - SequenceSessionStorage, - TransportMessage, - TransportMode, - WalletSize, -} from './types/index.js' - -const isBrowserEnvironment = typeof window !== 'undefined' && typeof document !== 'undefined' - -const base64Encode = (value: string) => { - if (typeof btoa !== 'undefined') { - return btoa(value) - } - if (typeof Buffer !== 'undefined') { - return Buffer.from(value, 'utf-8').toString('base64') - } - throw new Error('Base64 encoding is not supported in this environment.') -} - -const base64Decode = (value: string) => { - if (typeof atob !== 'undefined') { - return atob(value) - } - if (typeof Buffer !== 'undefined') { - return Buffer.from(value, 'base64').toString('utf-8') - } - throw new Error('Base64 decoding is not supported in this environment.') -} - -enum ConnectionState { - DISCONNECTED = 'DISCONNECTED', - CONNECTING = 'CONNECTING', - CONNECTED = 'CONNECTED', -} - -const REDIRECT_REQUEST_KEY = 'dapp-redirect-request' - -export class DappTransport { - private walletWindow: Window | undefined = undefined - private connectionState: ConnectionState = ConnectionState.DISCONNECTED - private readyPromise: Promise | undefined = undefined - private readyPromiseResolve: (() => void) | undefined = undefined - private readyPromiseReject: ((reason?: any) => void) | undefined = undefined - private initId: string | undefined = undefined - private handshakeTimeoutId: number | undefined = undefined - private closeCheckIntervalId: number | undefined = undefined - private sessionId: string | undefined = undefined - private pendingRequests = new Map() - private messageQueue: TransportMessage[] = [] - private readonly requestTimeoutMs: number - private readonly handshakeTimeoutMs: number - private readonly sequenceSessionStorage: SequenceSessionStorage - private readonly redirectActionHandler?: (url: string) => void - private readonly isBrowser: boolean - - public readonly walletOrigin: string - - constructor( - public readonly walletUrl: string, - readonly mode: TransportMode = TransportMode.POPUP, - popupModeOptions: PopupModeOptions = {}, - sequenceSessionStorage?: SequenceSessionStorage, - redirectActionHandler?: (url: string) => void, - ) { - this.isBrowser = isBrowserEnvironment - try { - this.walletOrigin = new URL(walletUrl).origin - } catch (e) { - console.error('[DApp] Invalid walletUrl provided:', walletUrl, e) - throw new Error(`Invalid walletUrl: ${walletUrl}`) - } - if (!this.walletOrigin || this.walletOrigin === 'null' || this.walletOrigin === '*') { - console.error('[DApp] Could not determine a valid wallet origin from the URL:', walletUrl) - throw new Error('Invalid wallet origin derived from walletUrl.') - } - - this.sequenceSessionStorage = - sequenceSessionStorage || - ({ - getItem: (key: string) => (this.isBrowser && window.sessionStorage ? window.sessionStorage.getItem(key) : null), - setItem: (key: string, value: string) => { - if (this.isBrowser && window.sessionStorage) { - window.sessionStorage.setItem(key, value) - } - }, - removeItem: (key: string) => { - if (this.isBrowser && window.sessionStorage) { - window.sessionStorage.removeItem(key) - } - }, - } satisfies SequenceSessionStorage) - - this.requestTimeoutMs = popupModeOptions.requestTimeoutMs ?? 300000 - this.handshakeTimeoutMs = popupModeOptions.handshakeTimeoutMs ?? 15000 - - if (this.mode === TransportMode.POPUP && this.isBrowser) { - window.addEventListener('message', this.handleMessage) - } - - this.redirectActionHandler = redirectActionHandler - } - - get isWalletOpen(): boolean { - if (this.mode === TransportMode.REDIRECT) return false - return !!this.walletWindow && !this.walletWindow.closed - } - - get isReady(): boolean { - if (this.mode === TransportMode.REDIRECT) return false - return this.connectionState === ConnectionState.CONNECTED - } - - async sendRequest( - action: string, - redirectUrl: string, - payload?: TRequest, - options: SendRequestOptions = {}, - ): Promise { - if (!this.isBrowser && this.mode === TransportMode.POPUP) { - throw new Error( - 'Popup transport requires a browser environment. Use redirect mode or provide a redirect handler.', - ) - } - - if (this.mode === TransportMode.REDIRECT) { - const url = await this.getRequestRedirectUrl(action, payload, redirectUrl, options.path) - if (this.redirectActionHandler) { - this.redirectActionHandler(url) - } else if (this.isBrowser) { - console.info('[DappTransport] No redirectActionHandler provided. Using window.location.href to navigate.') - window.location.href = url - } else { - throw new Error( - 'Redirect navigation is not possible outside the browser without a redirectActionHandler. Provide a handler to perform navigation.', - ) - } - return new Promise(() => {}) - } - - if (this.connectionState !== ConnectionState.CONNECTED) { - await this.openWallet(options.path) - } - - if (!this.isWalletOpen || this.connectionState !== ConnectionState.CONNECTED) { - throw new Error('Wallet connection is not available or failed to establish.') - } - - const id = this.generateId() - const message: TransportMessage = { - id, - type: MessageType.REQUEST, - action, - payload, - } - - return new Promise((resolve, reject) => { - const timeout = options.timeout ?? this.requestTimeoutMs - const timer = window.setTimeout(() => { - if (this.pendingRequests.has(id)) { - this.pendingRequests.delete(id) - reject(new Error(`Request '${action}' (ID: ${id}) timed out after ${timeout}ms.`)) - } - }, timeout) - - this.pendingRequests.set(id, { resolve, reject, timer, action }) - this.postMessageToWallet(message) - }) - } - - public async getRequestRedirectUrl( - action: string, - payload: any, - redirectUrl: string, - path?: string, - ): Promise { - const id = this.generateId() - const request = { id, action, timestamp: Date.now() } - - try { - await this.sequenceSessionStorage.setItem(REDIRECT_REQUEST_KEY, JSON.stringify(request, jsonReplacers)) - } catch (e) { - console.error('Failed to set redirect request in storage', e) - throw new Error('Could not save redirect state to storage. Redirect flow is unavailable.') - } - - const serializedPayload = base64Encode(JSON.stringify(payload || {}, jsonReplacers)) - const fullWalletUrl = path ? `${this.walletUrl}${path}` : this.walletUrl - const url = new URL(fullWalletUrl) - url.searchParams.set('action', action) - url.searchParams.set('payload', serializedPayload) - url.searchParams.set('id', id) - url.searchParams.set('redirectUrl', redirectUrl) - url.searchParams.set('mode', 'redirect') - - return url.toString() - } - - public async getRedirectResponse( - cleanState: boolean = true, - url?: string, - ): Promise<{ payload: TResponse; action: string } | { error: any; action: string } | null> { - if (!url && !this.isBrowser) { - throw new Error('A URL must be provided when handling redirect responses outside of a browser environment.') - } - - const search = url ? new URL(url).search : this.isBrowser ? window.location.search : '' - const params = new URLSearchParams(search) - const responseId = params.get('id') - if (!responseId) return null - - let originalRequest: { id: string; action: string; timestamp: number } - try { - const storedRequest = await this.sequenceSessionStorage.getItem(REDIRECT_REQUEST_KEY) - if (!storedRequest) { - return null - } - originalRequest = JSON.parse(storedRequest, jsonRevivers) - } catch (e) { - console.error('Failed to parse redirect request from storage', e) - return null - } - - if (originalRequest.id !== responseId) { - console.error(`Mismatched ID in redirect response. Expected ${originalRequest.id}, got ${responseId}.`) - if (cleanState) { - await this.sequenceSessionStorage.removeItem(REDIRECT_REQUEST_KEY) - } - return null - } - - const responsePayloadB64 = params.get('payload') - const responseErrorB64 = params.get('error') - - if (cleanState) { - await this.sequenceSessionStorage.removeItem(REDIRECT_REQUEST_KEY) - if (this.isBrowser && !url && window.history) { - const cleanUrl = new URL(window.location.href) - ;['id', 'payload', 'error', 'mode'].forEach((p) => cleanUrl.searchParams.delete(p)) - history.replaceState({}, document.title, cleanUrl.toString()) - } - } - - if (responseErrorB64) { - try { - return { - error: JSON.parse(base64Decode(responseErrorB64), jsonRevivers), - action: originalRequest.action, - } - } catch (e) { - console.error('Failed to parse error from redirect response', e) - return { - error: 'Failed to parse error from redirect', - action: originalRequest.action, - } - } - } - if (responsePayloadB64) { - try { - return { - payload: JSON.parse(base64Decode(responsePayloadB64), jsonRevivers), - action: originalRequest.action, - } - } catch (e) { - console.error('Failed to parse payload from redirect response', e) - return { - error: 'Failed to parse payload from redirect', - action: originalRequest.action, - } - } - } - return { - error: "Redirect response missing 'payload' or 'error'", - action: originalRequest.action, - } - } - - public openWallet(path?: string): Promise { - if (this.mode === TransportMode.REDIRECT) { - throw new Error("`openWallet` is not available in 'redirect' mode.") - } - if (!this.isBrowser) { - throw new Error('Popup transport requires a browser environment.') - } - if (this.connectionState !== ConnectionState.DISCONNECTED) { - if (this.isWalletOpen) this.walletWindow?.focus() - return this.readyPromise || Promise.resolve() - } - this.connectionState = ConnectionState.CONNECTING - this.clearPendingRequests(new Error('Wallet connection reset during open.')) - this.messageQueue = [] - this.clearTimeouts() - this.readyPromise = new Promise((resolve, reject) => { - this.readyPromiseResolve = resolve - this.readyPromiseReject = reject - }) - this.readyPromise.catch(() => {}) - this.initId = this.generateId() - const fullWalletUrl = path ? `${this.walletUrl}${path}` : this.walletUrl - this.sessionId = this.generateId() - const urlWithParams = new URL(fullWalletUrl) - urlWithParams.searchParams.set('dappOrigin', window.location.origin) - urlWithParams.searchParams.set('sessionId', this.sessionId) - - try { - const openedWindow = window.open( - urlWithParams.toString(), - 'Wallet', - `width=${WalletSize.width},height=${WalletSize.height},scrollbars=yes,resizable=yes`, - ) - this.walletWindow = openedWindow || undefined - } catch (error) { - const openError = new Error( - `Failed to open wallet window: ${error instanceof Error ? error.message : String(error)}`, - ) - this._handlePreConnectionFailure(openError) - return Promise.reject(openError) - } - if (!this.walletWindow) { - const error = new Error('Failed to open wallet window. Please check your pop-up blocker settings.') - this._handlePreConnectionFailure(error) - return Promise.reject(error) - } - - this.handshakeTimeoutId = window.setTimeout(() => { - if (this.connectionState === ConnectionState.CONNECTING) { - const timeoutError = new Error(`Wallet handshake timed out after ${this.handshakeTimeoutMs}ms.`) - this._handlePreConnectionFailure(timeoutError) - } - }, this.handshakeTimeoutMs) - - this.closeCheckIntervalId = window.setInterval(() => { - if (!this.isWalletOpen) { - if (this.connectionState === ConnectionState.CONNECTING) - this._handlePreConnectionFailure(new Error('Wallet window was closed before becoming ready.')) - else if (this.connectionState === ConnectionState.CONNECTED) this._handleDetectedClosure() - } - }, 500) - return this.readyPromise - } - - public closeWallet(): void { - if (this.mode === TransportMode.REDIRECT) { - console.warn( - "[DApp] `closeWallet` is not available in 'redirect' mode. Use window.location.href to navigate away.", - ) - return - } - if (this.connectionState === ConnectionState.DISCONNECTED) return - if (this.isWalletOpen) this.walletWindow?.close() - this.connectionState = ConnectionState.DISCONNECTED - this.readyPromise = undefined - this.readyPromiseResolve = undefined - this.readyPromiseReject = undefined - this._resetConnection(new Error('Wallet closed intentionally by DApp.'), 'Wallet closed intentionally by DApp.') - } - - destroy(): void { - if (this.mode === TransportMode.POPUP && this.isBrowser) { - window.removeEventListener('message', this.handleMessage) - if (this.isWalletOpen) { - this.walletWindow?.close() - } - this._resetConnection(new Error('Transport destroyed.'), 'Destroying transport...') - } else { - this._resetConnection(new Error('Transport destroyed.'), 'Destroying transport...') - } - } - - private handleMessage = (event: MessageEvent): void => { - if (event.origin !== this.walletOrigin) { - return - } - - if (!this.walletWindow || event.source !== this.walletWindow) { - return - } - - const message = event.data as TransportMessage - if ( - !message || - typeof message !== 'object' || - !message.id || - !message.type || - (message.type === MessageType.WALLET_OPENED && !message.sessionId) - ) { - return - } - - try { - switch (message.type) { - case MessageType.WALLET_OPENED: - this.handleWalletReadyMessage(message) - break - case MessageType.RESPONSE: - this.handleResponseMessage(message) - break - case MessageType.REQUEST: - case MessageType.INIT: - default: - break - } - } catch (error) { - console.error(`[DApp] Error processing received message (Type: ${message.type}, ID: ${message.id}):`, error) - } - } - - private handleWalletReadyMessage(message: TransportMessage): void { - if (this.connectionState !== ConnectionState.CONNECTING) { - return - } - - if (message.sessionId !== this.sessionId) { - return - } - - if (this.handshakeTimeoutId !== undefined) { - window.clearTimeout(this.handshakeTimeoutId) - this.handshakeTimeoutId = undefined - } - - const initMessage: TransportMessage = { - id: this.initId!, - type: MessageType.INIT, - sessionId: this.sessionId, - } - this.postMessageToWallet(initMessage) - - this.connectionState = ConnectionState.CONNECTED - - if (this.readyPromiseResolve) { - this.readyPromiseResolve() - } - - this.messageQueue.forEach((queuedMsg) => { - this.postMessageToWallet(queuedMsg) - }) - this.messageQueue = [] - } - - private handleResponseMessage(message: TransportMessage): void { - const pending = this.pendingRequests.get(message.id) - if (pending) { - window.clearTimeout(pending.timer) - this.pendingRequests.delete(message.id) - if (message.error) { - const error = new Error(`Wallet responded with error: ${JSON.stringify(message.error)}`) - pending.reject(error) - } else { - pending.resolve(message.payload) - } - } - } - - private postMessageToWallet(message: TransportMessage): void { - if (!this.isWalletOpen) { - if ( - message.type === MessageType.INIT && - this.connectionState === ConnectionState.CONNECTING && - message.id === this.initId - ) { - this._handlePreConnectionFailure(new Error('Failed to send INIT: Wallet window closed unexpectedly.')) - } else if (message.type === MessageType.REQUEST) { - const pendingReq = this.pendingRequests.get(message.id) - if (pendingReq) { - window.clearTimeout(pendingReq.timer) - this.pendingRequests.delete(message.id) - pendingReq.reject(new Error(`Failed to send request '${pendingReq.action}': Wallet window closed.`)) - } - } - return - } - - if (this.connectionState !== ConnectionState.CONNECTED && message.type !== MessageType.INIT) { - this.messageQueue.push(message) - return - } - - try { - this.walletWindow?.postMessage(message, this.walletOrigin) - } catch (error) { - const rejectionError = - error instanceof Error ? error : new Error('Failed to send message to wallet due to unknown error') - - if ( - message.type === MessageType.INIT && - this.connectionState === ConnectionState.CONNECTING && - message.id === this.initId - ) { - this._handlePreConnectionFailure(rejectionError) - } else if (message.type === MessageType.REQUEST) { - const pendingReq = this.pendingRequests.get(message.id) - if (pendingReq) { - window.clearTimeout(pendingReq.timer) - this.pendingRequests.delete(message.id) - pendingReq.reject(rejectionError) - } - this._handleDetectedClosure() - } else { - this._handleDetectedClosure() - } - } - } - - private _resetConnection(reason: Error, logMessage: string): void { - console.log(`[DApp] ${logMessage}`) - if (this.readyPromiseReject) { - this.readyPromiseReject(reason) - } - this.clearTimeouts() - this.clearPendingRequests(reason) - this.connectionState = ConnectionState.DISCONNECTED - this.walletWindow = undefined - this.readyPromise = undefined - this.readyPromiseResolve = undefined - this.readyPromiseReject = undefined - this.initId = undefined - this.sessionId = undefined - this.messageQueue = [] - } - - private _handlePreConnectionFailure(error: Error): void { - this._resetConnection(error, `Connection failure: ${error.message}`) - } - - private _handleDetectedClosure(): void { - if (this.connectionState === ConnectionState.CONNECTED) { - const reason = new Error('Wallet connection terminated unexpectedly.') - this._resetConnection(reason, 'Wallet connection terminated unexpectedly after ready.') - } - } - - private clearPendingRequests(reason: Error): void { - if (this.pendingRequests.size > 0) { - const requestsToClear = new Map(this.pendingRequests) - this.pendingRequests.clear() - requestsToClear.forEach((pending) => { - clearTimeout(pending.timer) - const errorToSend = reason instanceof Error ? reason : new Error(`Operation failed: ${reason}`) - pending.reject(errorToSend) - }) - } - } - - private clearTimeouts(): void { - if (this.handshakeTimeoutId !== undefined) { - clearTimeout(this.handshakeTimeoutId) - this.handshakeTimeoutId = undefined - } - if (this.closeCheckIntervalId !== undefined) { - clearInterval(this.closeCheckIntervalId) - this.closeCheckIntervalId = undefined - } - } - - private generateId(): string { - return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}` - } -} diff --git a/packages/wallet/dapp-client/src/index.ts b/packages/wallet/dapp-client/src/index.ts deleted file mode 100644 index ce976c13d4..0000000000 --- a/packages/wallet/dapp-client/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -export { DappClient } from './DappClient.js' -export type { DappClientEventListener } from './DappClient.js' -export type { - LoginMethod, - GuardConfig, - Transaction, - SignatureResponse, - SequenceSessionStorage, - RandomPrivateKeyFn, - SignMessagePayload, - SessionResponse, - AddExplicitSessionPayload, - CreateNewSessionPayload, - CreateNewSessionResponse, - SignTypedDataPayload, - ModifyExplicitSessionPayload, - DappClientWalletActionEventListener, - DappClientExplicitSessionEventListener, - TransactionRequest, - SendWalletTransactionPayload, - SendWalletTransactionResponse, - WalletActionResponse, - GetFeeTokensResponse, - FeeToken, - FeeOption, - TransportMessage, - EthAuthSettings, - ETHAuthProof, -} from './types/index.js' -export { RequestActionType, TransportMode, MessageType } from './types/index.js' -export { - FeeOptionError, - TransactionError, - AddExplicitSessionError, - ConnectionError, - InitializationError, - SigningError, - ModifyExplicitSessionError, -} from './utils/errors.js' -export { - createExplicitSessionConfig, - getExplorerUrl, - getNetwork, - getRelayerUrl, - getRpcUrl, - jsonReplacers, - jsonRevivers, - VALUE_FORWARDER_ADDRESS, -} from './utils/index.js' -export type { ExplicitSessionParams, NativeTokenSpending, SessionDuration } from './utils/index.js' -export type { - SequenceStorage, - ExplicitSessionData, - ImplicitSessionData, - SessionlessConnectionData, - PendingRequestContext, - PendingPayload, -} from './utils/storage.js' -export { WebStorage } from './utils/storage.js' - -export { - Attestation, - Permission, - Extensions, - SessionConfig, - Constants, - Payload, - Network, -} from '@0xsequence/wallet-primitives' -export type { ExplicitSessionConfig, ExplicitSession, ImplicitSession, Session } from '@0xsequence/wallet-core' -export { Signers, Wallet, Utils, Envelope, State } from '@0xsequence/wallet-core' diff --git a/packages/wallet/dapp-client/src/types/index.ts b/packages/wallet/dapp-client/src/types/index.ts deleted file mode 100644 index 0f023c2bb8..0000000000 --- a/packages/wallet/dapp-client/src/types/index.ts +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Relayer } from '@0xsequence/relayer' -import { ExplicitSession } from '@0xsequence/wallet-core' -import { Attestation, Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import type { TypedData } from 'ox/TypedData' - -// --- Public Interfaces and Constants --- - -export type FeeToken = Relayer.FeeToken -export type FeeOption = Relayer.FeeOption -export type OperationFailedStatus = Relayer.OperationFailedStatus -export type OperationStatus = Relayer.OperationStatus - -export const RequestActionType = { - CREATE_NEW_SESSION: 'createNewSession', - ADD_EXPLICIT_SESSION: 'addExplicitSession', - MODIFY_EXPLICIT_SESSION: 'modifyExplicitSession', - SIGN_MESSAGE: 'signMessage', - SIGN_TYPED_DATA: 'signTypedData', - SEND_WALLET_TRANSACTION: 'sendWalletTransaction', -} as const - -export type LoginMethod = 'google' | 'apple' | 'email' | 'passkey' | 'mnemonic' | 'eoa' - -export interface GuardConfig { - url: string - moduleAddresses: Map -} - -export interface EthAuthSettings { - app?: string - /** expiry number (in seconds) that is used for ETHAuth proof. Default is 1 week in seconds. */ - expiry?: number - /** origin hint of the dapp's host opening the wallet. This value will automatically - * be determined and verified for integrity, and can be omitted. */ - origin?: string - /** authorizeNonce is an optional number to be passed as ETHAuth's nonce claim for replay protection. **/ - nonce?: number -} - -export interface ETHAuthProof { - // eip712 typed-data payload for ETHAuth domain as input - typedData: Payload.TypedDataToSign - - // signature encoded in an ETHAuth proof string - ewtString: string -} - -// --- Payloads for Transport --- - -export interface CreateNewSessionPayload { - origin?: string - session?: ExplicitSession - includeImplicitSession?: boolean - ethAuth?: EthAuthSettings - preferredLoginMethod?: LoginMethod - email?: string -} - -export interface AddExplicitSessionPayload { - session: ExplicitSession - preferredLoginMethod?: LoginMethod - email?: string -} - -export interface ModifyExplicitSessionPayload { - walletAddress: Address.Address - session: ExplicitSession -} - -export interface SignMessagePayload { - address: Address.Address - message: string - chainId: number -} - -export interface SignTypedDataPayload { - address: Address.Address - typedData: TypedData - chainId: number -} - -export interface SendWalletTransactionPayload { - address: Address.Address - transactionRequest: TransactionRequest - chainId: number -} - -export type TransactionRequest = { - to: Address.Address - value?: bigint - data?: Hex.Hex - gasLimit?: bigint -} - -export interface CreateNewSessionResponse { - walletAddress: string - attestation?: Attestation.Attestation - signature?: Hex.Hex - userEmail?: string - loginMethod?: LoginMethod - guard?: GuardConfig - ethAuthProof?: ETHAuthProof -} - -export interface SignatureResponse { - signature: Hex.Hex - walletAddress: string -} - -export interface SendWalletTransactionResponse { - transactionHash: Hex.Hex - walletAddress: string -} - -export type WalletActionResponse = SignatureResponse | SendWalletTransactionResponse - -export interface SessionResponse { - walletAddress: string - sessionAddress: string -} - -// --- Dapp-facing Types --- - -export type RandomPrivateKeyFn = () => Hex.Hex | Promise - -type RequiredKeys = 'to' | 'data' | 'value' - -export type Transaction = - // Required properties from Payload.Call - Pick & - // All other properties from Payload.Call, but optional - Partial> - -// --- Event Types --- - -export type ExplicitSessionEventListener = (data: { - action: (typeof RequestActionType)['ADD_EXPLICIT_SESSION' | 'MODIFY_EXPLICIT_SESSION'] - response?: SessionResponse - error?: any -}) => void - -// A generic listener for events from the DappClient -export type DappClientEventListener = (data?: any) => void - -export type DappClientWalletActionEventListener = (data: { - action: (typeof RequestActionType)['SIGN_MESSAGE' | 'SIGN_TYPED_DATA' | 'SEND_WALLET_TRANSACTION'] - response?: WalletActionResponse - error?: any - chainId: number -}) => void - -export type DappClientExplicitSessionEventListener = (data: { - action: (typeof RequestActionType)['ADD_EXPLICIT_SESSION' | 'MODIFY_EXPLICIT_SESSION'] - response?: SessionResponse - error?: any - chainId: number -}) => void - -// --- DappTransport Types --- - -export interface SequenceSessionStorage { - getItem(key: string): string | null | Promise - setItem(key: string, value: string): void | Promise - removeItem(key: string): void | Promise -} - -export enum MessageType { - WALLET_OPENED = 'WALLET_OPENED', - INIT = 'INIT', - REQUEST = 'REQUEST', - RESPONSE = 'RESPONSE', -} - -export enum TransportMode { - POPUP = 'popup', - REDIRECT = 'redirect', -} - -export interface PopupModeOptions { - requestTimeoutMs?: number - handshakeTimeoutMs?: number -} - -export interface TransportMessage { - id: string - type: MessageType - sessionId?: string - action?: string - payload?: T - error?: any -} - -export const WalletSize = { - width: 380, - height: 600, -} - -export interface PendingRequest { - resolve: (payload: any) => void - reject: (error: any) => void - timer: number - action: string -} -export interface SendRequestOptions { - timeout?: number - path?: string -} - -export type GetFeeTokensResponse = { - isFeeRequired: boolean - tokens?: FeeToken[] - paymentAddress?: Address.Address -} diff --git a/packages/wallet/dapp-client/src/utils/constants.ts b/packages/wallet/dapp-client/src/utils/constants.ts deleted file mode 100644 index 7d382d41c3..0000000000 --- a/packages/wallet/dapp-client/src/utils/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const CACHE_DB_NAME = 'sequence-cache' -export const NODES_URL = 'https://nodes.sequence.app/{network}' -export const RELAYER_URL = 'https://{network}-relayer.sequence.app' -export const KEYMACHINE_URL = 'https://keymachine.sequence.app' -export const VALUE_FORWARDER_ADDRESS = '0xABAAd93EeE2a569cF0632f39B10A9f5D734777ca' diff --git a/packages/wallet/dapp-client/src/utils/errors.ts b/packages/wallet/dapp-client/src/utils/errors.ts deleted file mode 100644 index a378a07d74..0000000000 --- a/packages/wallet/dapp-client/src/utils/errors.ts +++ /dev/null @@ -1,62 +0,0 @@ -export class InitializationError extends Error { - constructor(message: string) { - super(message) - this.name = 'InitializationError' - } -} - -export class SigningError extends Error { - constructor(message: string) { - super(message) - this.name = 'SigningError' - } -} - -export class TransactionError extends Error { - constructor(message: string) { - super(message) - this.name = 'TransactionError' - } -} - -export class ModifyExplicitSessionError extends Error { - constructor(message: string) { - super(message) - this.name = 'ModifyExplicitSessionError' - } -} - -export class ConnectionError extends Error { - constructor(message: string) { - super(message) - this.name = 'ConnectionError' - } -} - -export class AddExplicitSessionError extends Error { - constructor(message: string) { - super(message) - this.name = 'AddExplicitSessionError' - } -} - -export class FeeOptionError extends Error { - constructor(message: string) { - super(message) - this.name = 'FeeOptionError' - } -} - -export class WalletRedirectError extends Error { - constructor(message: string) { - super(message) - this.name = 'WalletRedirectError' - } -} - -export class SessionConfigError extends Error { - constructor(message: string) { - super(message) - this.name = 'SessionConfigError' - } -} diff --git a/packages/wallet/dapp-client/src/utils/index.ts b/packages/wallet/dapp-client/src/utils/index.ts deleted file mode 100644 index 12bf312c33..0000000000 --- a/packages/wallet/dapp-client/src/utils/index.ts +++ /dev/null @@ -1,232 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { ExplicitSessionConfig } from '@0xsequence/wallet-core' -import { Network, Permission } from '@0xsequence/wallet-primitives' -import { Bytes, Hex, type Address } from 'ox' -export { VALUE_FORWARDER_ADDRESS } from './constants.js' - -type JsonReplacer = (key: string, value: any) => any -type JsonReviver = (key: string, value: any) => any - -/** - * Creates a single JSON replacer by chaining multiple replacers. - * The first replacer to transform a value wins. - */ -function chainReplacers(replacers: JsonReplacer[]): JsonReplacer { - return function (key: string, value: any): any { - for (const replacer of replacers) { - const replacedValue = replacer(key, value) - if (replacedValue !== value) { - return replacedValue - } - } - return value - } -} - -/** - * Creates a single JSON reviver by chaining multiple revivers. - * The output of one reviver becomes the input for the next. - */ -function chainRevivers(revivers: JsonReviver[]): JsonReviver { - return function (key: string, value: any): any { - let currentValue = value - for (const reviver of revivers) { - currentValue = reviver(key, currentValue) - } - return currentValue - } -} - -/** - * A JSON replacer that serializes Map objects into a structured object. - */ -const mapReplacer: JsonReplacer = (key, value) => { - if (value instanceof Map) { - return { - _isMap: true, - data: Array.from(value.entries()), - } - } - return value -} - -/** - * A JSON replacer that serializes BigInt values into a structured object. - */ -const bigIntReplacer: JsonReplacer = (key, value) => { - if (typeof value === 'bigint') { - return { - _isBigInt: true, - data: value.toString(), - } - } - return value -} - -/** - * A JSON replacer that serializes Uint8Array values into a structured object. - */ -const uint8ArrayReplacer: JsonReplacer = (key, value) => { - if (value instanceof Uint8Array) { - return { - _isUint8Array: true, - data: Hex.from(value), - } - } - return value -} - -/** - * A JSON reviver that deserializes a structured object back into a Map. - */ -const mapReviver: JsonReviver = (key, value) => { - if (value !== null && typeof value === 'object' && value._isMap === true && Array.isArray(value.data)) { - try { - // The key-value pairs in value.data will have already been processed - // by other revivers in the chain because JSON.parse works bottom-up. - return new Map(value.data) - } catch (e) { - console.error(`Failed to revive Map for key "${key}":`, e) - return value // Return original object if revival fails - } - } - return value -} - -/** - * A JSON reviver that deserializes a structured object back into a BigInt. - */ -const bigIntReviver: JsonReviver = (key, value) => { - if (value !== null && typeof value === 'object' && value._isBigInt === true && typeof value.data === 'string') { - try { - return BigInt(value.data) - } catch (e) { - console.error(`Failed to revive BigInt for key "${key}":`, e) - return value // Return original object if revival fails - } - } - return value -} - -/** - * A JSON reviver that deserializes a structured object back into a Uint8Array. - */ -const uint8ArrayReviver: JsonReviver = (key, value) => { - if (value !== null && typeof value === 'object' && value._isUint8Array === true && typeof value.data === 'string') { - try { - return Bytes.from(value.data) - } catch (e) { - console.error(`Failed to revive Uint8Array for key "${key}":`, e) - return value // Return original object if revival fails - } - } - return value -} - -export const jsonRevivers = chainRevivers([mapReviver, bigIntReviver, uint8ArrayReviver]) -export const jsonReplacers = chainReplacers([mapReplacer, bigIntReplacer, uint8ArrayReplacer]) - -export type SessionDuration = { - days?: number - hours?: number - minutes?: number -} - -export type NativeTokenSpending = { - valueLimit: bigint - allowedRecipients?: Address.Address[] -} - -export type ExplicitSessionParams = { - chainId: number - expiresIn: SessionDuration - permissions: Permission.Permission[] - nativeTokenSpending?: NativeTokenSpending -} - -export const createExplicitSessionConfig = (params: ExplicitSessionParams): ExplicitSessionConfig => { - const nowInSeconds = BigInt(Math.floor(Date.now() / 1000)) - const { days = 0, hours = 0, minutes = 0 } = params.expiresIn - const sessionLifetimeSeconds = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 - const deadline = nowInSeconds + BigInt(sessionLifetimeSeconds) - - if (params.permissions.length === 0) { - throw new Error('createExplicitSessionConfig: At least one permission is required.') - } - - const nativeTokenSpending = params.nativeTokenSpending - const valueLimit = nativeTokenSpending?.valueLimit ?? 0n - const nativeTokenReceivers = [...(nativeTokenSpending?.allowedRecipients || [])] - const nativeTokenSpendingPermissions = nativeTokenReceivers.map((receiver) => ({ - target: receiver, - rules: [], - })) - - return { - chainId: params.chainId, - valueLimit, - deadline, - permissions: [...params.permissions, ...nativeTokenSpendingPermissions], - } -} - -/** - * Apply a template to a string. - * - * Example: - * applyTemplate('https://v3-{network}-relayer.sequence.app', { network: 'arbitrum' }) - * returns 'https://v3-arbitrum-relayer.sequence.app' - * - * @param template - The template to apply. - * @param values - The values to apply to the template. - * @returns The template with the values applied. - */ -function applyTemplate(template: string, values: Record) { - return template.replace(/{(.*?)}/g, (_, key) => { - const value = values[key] - if (value === undefined) { - throw new Error(`Missing template value for ${template}: ${key}`) - } - return value - }) -} - -export const getNetwork = (chainId: Network.ChainId | bigint | number) => { - const network = Network.getNetworkFromChainId(chainId) - - if (!network) { - throw new Error(`Network with chainId ${chainId} not found`) - } - - return network -} - -export const getRpcUrl = (chainId: Network.ChainId | bigint | number, nodesUrl: string, projectAccessKey: string) => { - const network = getNetwork(chainId) - - let url = applyTemplate(nodesUrl, { network: network.name }) - - if (nodesUrl.includes('sequence')) { - url = `${url}/${projectAccessKey}` - } - - return url -} - -export const getRelayerUrl = (chainId: Network.ChainId | bigint | number, relayerUrl: string) => { - const network = getNetwork(chainId) - - const url = applyTemplate(relayerUrl, { network: network.name }) - - return url -} - -export const getExplorerUrl = (chainId: Network.ChainId | bigint | number, txHash: string) => { - const network = getNetwork(chainId) - const explorerUrl = network.blockExplorer?.url - if (!explorerUrl) { - throw new Error(`Explorer URL not found for chainId ${chainId}`) - } - - return `${explorerUrl}/tx/${txHash}` -} diff --git a/packages/wallet/dapp-client/src/utils/storage.ts b/packages/wallet/dapp-client/src/utils/storage.ts deleted file mode 100644 index 8a56015ba9..0000000000 --- a/packages/wallet/dapp-client/src/utils/storage.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { Address, Hex } from 'ox' -import { jsonReplacers, jsonRevivers } from './index.js' -import { - LoginMethod, - SignMessagePayload, - SignTypedDataPayload, - GuardConfig, - ETHAuthProof, - SendWalletTransactionPayload, - ModifyExplicitSessionPayload, - CreateNewSessionPayload, - AddExplicitSessionPayload, -} from '../types/index.js' - -import { Attestation } from '../index.js' - -const isBrowser = typeof window !== 'undefined' -const hasSessionStorage = isBrowser && typeof sessionStorage !== 'undefined' -const hasIndexedDb = typeof indexedDB !== 'undefined' - -export interface ExplicitSessionData { - pk: Hex.Hex - walletAddress: Address.Address - chainId: number - loginMethod?: LoginMethod - userEmail?: string - guard?: GuardConfig -} - -export interface ImplicitSessionData { - pk: Hex.Hex - walletAddress: Address.Address - attestation: Attestation.Attestation - identitySignature: Hex.Hex - chainId: number - loginMethod?: LoginMethod - userEmail?: string - guard?: GuardConfig -} - -export interface SessionlessConnectionData { - walletAddress: Address.Address - loginMethod?: LoginMethod - userEmail?: string - guard?: GuardConfig -} - -export type PendingPayload = - | CreateNewSessionPayload - | AddExplicitSessionPayload - | ModifyExplicitSessionPayload - | SignMessagePayload - | SignTypedDataPayload - | SendWalletTransactionPayload - -export interface PendingRequestContext { - chainId: number - action: string - payload: PendingPayload -} - -export interface SequenceStorage { - setPendingRedirectRequest(isPending: boolean): Promise - isRedirectRequestPending(): Promise - - saveTempSessionPk(pk: Hex.Hex): Promise - getAndClearTempSessionPk(): Promise - - savePendingRequest(context: PendingRequestContext): Promise - getAndClearPendingRequest(): Promise - peekPendingRequest(): Promise - - saveExplicitSession(sessionData: ExplicitSessionData): Promise - getExplicitSessions(): Promise - clearExplicitSessions(): Promise - - saveImplicitSession(sessionData: ImplicitSessionData): Promise - getImplicitSession(): Promise - clearImplicitSession(): Promise - - saveSessionlessConnection(sessionData: SessionlessConnectionData): Promise - getSessionlessConnection(): Promise - clearSessionlessConnection(): Promise - - saveEthAuthProof(proof: ETHAuthProof): Promise - getEthAuthProof(): Promise - clearEthAuthProof(): Promise - - saveSessionlessConnectionSnapshot?(sessionData: SessionlessConnectionData): Promise - getSessionlessConnectionSnapshot?(): Promise - clearSessionlessConnectionSnapshot?(): Promise - - clearAllData(): Promise -} - -const DB_NAME = 'SequenceDappStorage' -const DB_VERSION = 1 -const STORE_NAME = 'userKeys' -const IMPLICIT_SESSIONS_IDB_KEY = 'SequenceImplicitSession' -const EXPLICIT_SESSIONS_IDB_KEY = 'SequenceExplicitSession' -const SESSIONLESS_CONNECTION_IDB_KEY = 'SequenceSessionlessConnection' -const ETH_AUTH_PROOF_IDB_KEY = 'SequenceEthAuthProof' -const SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY = 'SequenceSessionlessConnectionSnapshot' - -const PENDING_REDIRECT_REQUEST_KEY = 'SequencePendingRedirect' -const TEMP_SESSION_PK_KEY = 'SequencePendingTempSessionPk' -const PENDING_REQUEST_CONTEXT_KEY = 'SequencePendingRequestContext' - -export class WebStorage implements SequenceStorage { - private inMemoryDb = new Map() - - private openDB(): Promise { - if (!hasIndexedDb) { - return Promise.reject(new Error('IndexedDB is not available in this environment.')) - } - return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, DB_VERSION) - request.onerror = (event) => reject(`IndexedDB error: ${(event.target as IDBRequest).error}`) - request.onsuccess = (event) => resolve((event.target as IDBRequest).result as IDBDatabase) - request.onupgradeneeded = (event) => { - const db = (event.target as IDBRequest).result as IDBDatabase - if (!db.objectStoreNames.contains(STORE_NAME)) { - db.createObjectStore(STORE_NAME) - } - } - }) - } - - private async getIDBItem(key: IDBValidKey): Promise { - if (!hasIndexedDb) { - return this.inMemoryDb.get(key) as T | undefined - } - const db = await this.openDB() - return new Promise((resolve, reject) => { - const request = db.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).get(key) - request.onerror = (event) => reject(`Failed to retrieve item: ${(event.target as IDBRequest).error}`) - request.onsuccess = (event) => resolve((event.target as IDBRequest).result as T | undefined) - }) - } - - private async setIDBItem(key: IDBValidKey, value: unknown): Promise { - if (!hasIndexedDb) { - this.inMemoryDb.set(key, value) - return - } - const db = await this.openDB() - return new Promise((resolve, reject) => { - const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put(value, key) - request.onerror = (event) => reject(`Failed to save item: ${(event.target as IDBRequest).error}`) - request.onsuccess = () => resolve() - }) - } - - private async deleteIDBItem(key: IDBValidKey): Promise { - if (!hasIndexedDb) { - this.inMemoryDb.delete(key) - return - } - const db = await this.openDB() - return new Promise((resolve, reject) => { - const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).delete(key) - request.onerror = (event) => reject(`Failed to delete item: ${(event.target as IDBRequest).error}`) - request.onsuccess = () => resolve() - }) - } - - async setPendingRedirectRequest(isPending: boolean): Promise { - try { - if (!hasSessionStorage) return - if (isPending) sessionStorage.setItem(PENDING_REDIRECT_REQUEST_KEY, 'true') - else sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) - } catch (error) { - console.error('Failed to set pending redirect flag:', error) - } - } - - async isRedirectRequestPending(): Promise { - try { - if (!hasSessionStorage) return false - return sessionStorage.getItem(PENDING_REDIRECT_REQUEST_KEY) === 'true' - } catch (error) { - console.error('Failed to check pending redirect flag:', error) - return false - } - } - - async saveTempSessionPk(pk: Hex.Hex): Promise { - try { - if (!hasSessionStorage) return - sessionStorage.setItem(TEMP_SESSION_PK_KEY, pk) - } catch (error) { - console.error('Failed to save temp session PK:', error) - } - } - - async getAndClearTempSessionPk(): Promise { - try { - if (!hasSessionStorage) return null - const pk = sessionStorage.getItem(TEMP_SESSION_PK_KEY) - sessionStorage.removeItem(TEMP_SESSION_PK_KEY) - return pk as Hex.Hex | null - } catch (error) { - console.error('Failed to retrieve temp session PK:', error) - return null - } - } - - async savePendingRequest(context: PendingRequestContext): Promise { - try { - if (!hasSessionStorage) return - sessionStorage.setItem(PENDING_REQUEST_CONTEXT_KEY, JSON.stringify(context, jsonReplacers)) - } catch (error) { - console.error('Failed to save pending request context:', error) - } - } - - async getAndClearPendingRequest(): Promise { - try { - if (!hasSessionStorage) return null - const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY) - if (!context) return null - sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY) - return JSON.parse(context, jsonRevivers) - } catch (error) { - console.error('Failed to retrieve pending request context:', error) - return null - } - } - - async peekPendingRequest(): Promise { - try { - if (!hasSessionStorage) return null - const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY) - if (!context) return null - return JSON.parse(context, jsonRevivers) - } catch (error) { - console.error('Failed to peek at pending request context:', error) - return null - } - } - - async saveExplicitSession(sessionData: ExplicitSessionData): Promise { - try { - const existingSessions = (await this.getExplicitSessions()).filter( - (s) => - !( - Address.isEqual(s.walletAddress, sessionData.walletAddress) && - s.pk === sessionData.pk && - s.chainId === sessionData.chainId - ), - ) - await this.setIDBItem(EXPLICIT_SESSIONS_IDB_KEY, [...existingSessions, sessionData]) - } catch (error) { - console.error('Failed to save explicit session:', error) - throw error - } - } - - async getExplicitSessions(): Promise { - try { - const sessions = await this.getIDBItem(EXPLICIT_SESSIONS_IDB_KEY) - return sessions && Array.isArray(sessions) ? sessions : [] - } catch (error) { - console.error('Failed to retrieve explicit sessions:', error) - return [] - } - } - - async clearExplicitSessions(): Promise { - try { - await this.deleteIDBItem(EXPLICIT_SESSIONS_IDB_KEY) - } catch (error) { - console.error('Failed to clear explicit sessions:', error) - throw error - } - } - - async saveImplicitSession(sessionData: ImplicitSessionData): Promise { - try { - await this.setIDBItem(IMPLICIT_SESSIONS_IDB_KEY, sessionData) - } catch (error) { - console.error('Failed to save implicit session:', error) - throw error - } - } - - async getImplicitSession(): Promise { - try { - return (await this.getIDBItem(IMPLICIT_SESSIONS_IDB_KEY)) ?? null - } catch (error) { - console.error('Failed to retrieve implicit session:', error) - return null - } - } - - async clearImplicitSession(): Promise { - try { - await this.deleteIDBItem(IMPLICIT_SESSIONS_IDB_KEY) - } catch (error) { - console.error('Failed to clear implicit session:', error) - throw error - } - } - - async saveSessionlessConnection(sessionData: SessionlessConnectionData): Promise { - try { - await this.setIDBItem(SESSIONLESS_CONNECTION_IDB_KEY, sessionData) - } catch (error) { - console.error('Failed to save sessionless connection:', error) - throw error - } - } - - async saveEthAuthProof(proof: ETHAuthProof): Promise { - try { - await this.setIDBItem(ETH_AUTH_PROOF_IDB_KEY, proof) - } catch (error) { - console.error('Failed to save ETHAuth proof:', error) - throw error - } - } - - async getSessionlessConnection(): Promise { - try { - return (await this.getIDBItem(SESSIONLESS_CONNECTION_IDB_KEY)) ?? null - } catch (error) { - console.error('Failed to retrieve sessionless connection:', error) - return null - } - } - - async getEthAuthProof(): Promise { - try { - return (await this.getIDBItem(ETH_AUTH_PROOF_IDB_KEY)) ?? null - } catch (error) { - console.error('Failed to retrieve ETHAuth proof:', error) - return null - } - } - - async clearSessionlessConnection(): Promise { - try { - await this.deleteIDBItem(SESSIONLESS_CONNECTION_IDB_KEY) - } catch (error) { - console.error('Failed to clear sessionless connection:', error) - throw error - } - } - - async clearEthAuthProof(): Promise { - try { - await this.deleteIDBItem(ETH_AUTH_PROOF_IDB_KEY) - } catch (error) { - console.error('Failed to clear ETHAuth proof:', error) - throw error - } - } - - async saveSessionlessConnectionSnapshot(sessionData: SessionlessConnectionData): Promise { - try { - await this.setIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY, sessionData) - } catch (error) { - console.error('Failed to save sessionless connection snapshot:', error) - throw error - } - } - - async getSessionlessConnectionSnapshot(): Promise { - try { - return (await this.getIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY)) ?? null - } catch (error) { - console.error('Failed to retrieve sessionless connection snapshot:', error) - return null - } - } - - async clearSessionlessConnectionSnapshot(): Promise { - try { - await this.deleteIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY) - } catch (error) { - console.error('Failed to clear sessionless connection snapshot:', error) - throw error - } - } - - async clearAllData(): Promise { - try { - // Clear all session storage items - if (hasSessionStorage) { - sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) - sessionStorage.removeItem(TEMP_SESSION_PK_KEY) - sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY) - } - - // Clear all IndexedDB items - await this.clearExplicitSessions() - await this.clearImplicitSession() - await this.clearSessionlessConnection() - await this.clearEthAuthProof() - await this.clearSessionlessConnectionSnapshot() - } catch (error) { - console.error('Failed to clear all data:', error) - throw error - } - } -} diff --git a/packages/wallet/dapp-client/test/ethauth-proof.test.ts b/packages/wallet/dapp-client/test/ethauth-proof.test.ts deleted file mode 100644 index 93273f0d6a..0000000000 --- a/packages/wallet/dapp-client/test/ethauth-proof.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' - -import { DappClient } from '../src/DappClient.js' -import { DappTransport } from '../src/DappTransport.js' -import { RequestActionType, TransportMode } from '../src/types/index.js' -import { WebStorage } from '../src/utils/storage.js' - -describe('ETHAuth proof persistence', () => { - afterEach(() => { - vi.restoreAllMocks() - vi.unstubAllGlobals() - }) - - const createSequenceStorageMock = () => ({ - setPendingRedirectRequest: vi.fn().mockResolvedValue(undefined), - isRedirectRequestPending: vi.fn().mockResolvedValue(false), - saveTempSessionPk: vi.fn().mockResolvedValue(undefined), - getAndClearTempSessionPk: vi.fn().mockResolvedValue(null), - savePendingRequest: vi.fn().mockResolvedValue(undefined), - getAndClearPendingRequest: vi.fn().mockResolvedValue(null), - peekPendingRequest: vi.fn().mockResolvedValue(null), - saveExplicitSession: vi.fn().mockResolvedValue(undefined), - getExplicitSessions: vi.fn().mockResolvedValue([]), - clearExplicitSessions: vi.fn().mockResolvedValue(undefined), - saveImplicitSession: vi.fn().mockResolvedValue(undefined), - getImplicitSession: vi.fn().mockResolvedValue(null), - clearImplicitSession: vi.fn().mockResolvedValue(undefined), - saveSessionlessConnection: vi.fn().mockResolvedValue(undefined), - getSessionlessConnection: vi.fn().mockResolvedValue(null), - clearSessionlessConnection: vi.fn().mockResolvedValue(undefined), - saveEthAuthProof: vi.fn().mockResolvedValue(undefined), - getEthAuthProof: vi.fn().mockResolvedValue(null), - clearEthAuthProof: vi.fn().mockResolvedValue(undefined), - clearAllData: vi.fn().mockResolvedValue(undefined), - }) - - it('persists ETHAuth proof when connect requests ethAuth in redirect mode', async () => { - const fetchMock = vi.fn() - vi.stubGlobal('fetch', fetchMock) - vi.stubGlobal('window', { fetch: fetchMock }) - - const ethAuthProof = { - typedData: { - domain: {}, - types: {}, - message: {}, - }, - ewtString: 'proof-string', - } - - const sequenceStorage = createSequenceStorageMock() - const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ - walletAddress: '0x1111111111111111111111111111111111111111', - ethAuthProof, - }) - - const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { - sequenceStorage, - transportMode: TransportMode.REDIRECT, - canUseIndexedDb: false, - redirectActionHandler: vi.fn(), - }) - - await client.connect(1, undefined, { - ethAuth: { - app: 'app-name', - }, - }) - - expect(sendRequestMock).toHaveBeenCalledWith( - RequestActionType.CREATE_NEW_SESSION, - 'https://dapp.example', - expect.objectContaining({ - ethAuth: { - app: 'app-name', - }, - }), - expect.any(Object), - ) - expect(sequenceStorage.saveEthAuthProof).toHaveBeenCalledWith(ethAuthProof) - }) - - it('persists ETHAuth proof when connect requests ethAuth in popup mode', async () => { - const fetchMock = vi.fn() - vi.stubGlobal('fetch', fetchMock) - vi.stubGlobal('window', { fetch: fetchMock }) - vi.stubGlobal('document', {}) - - const ethAuthProof = { - typedData: { - domain: {}, - types: {}, - message: {}, - }, - ewtString: 'proof-string', - } - - const sequenceStorage = createSequenceStorageMock() - const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ - walletAddress: '0x1111111111111111111111111111111111111111', - ethAuthProof, - }) - - const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { - sequenceStorage, - transportMode: TransportMode.POPUP, - canUseIndexedDb: false, - }) - - await client.connect(1, undefined, { - ethAuth: { - app: 'app-name', - }, - }) - - expect(sendRequestMock).toHaveBeenCalledWith( - RequestActionType.CREATE_NEW_SESSION, - 'https://dapp.example', - expect.objectContaining({ - ethAuth: { - app: 'app-name', - }, - }), - expect.any(Object), - ) - expect(sequenceStorage.saveEthAuthProof).toHaveBeenCalledWith(ethAuthProof) - }) - - it('does not persist ETHAuth proof when connect does not request ethAuth', async () => { - const fetchMock = vi.fn() - vi.stubGlobal('fetch', fetchMock) - vi.stubGlobal('window', { fetch: fetchMock }) - - const ethAuthProof = { - typedData: { - domain: {}, - types: {}, - message: {}, - }, - ewtString: 'proof-string', - } - - const sequenceStorage = createSequenceStorageMock() - const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ - walletAddress: '0x1111111111111111111111111111111111111111', - ethAuthProof, - }) - - const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { - sequenceStorage, - transportMode: TransportMode.REDIRECT, - canUseIndexedDb: false, - redirectActionHandler: vi.fn(), - }) - - await client.connect(1) - - expect(sendRequestMock).toHaveBeenCalledWith( - RequestActionType.CREATE_NEW_SESSION, - 'https://dapp.example', - expect.not.objectContaining({ - ethAuth: expect.anything(), - }), - expect.any(Object), - ) - expect(sequenceStorage.saveEthAuthProof).not.toHaveBeenCalled() - }) - - it('clears ETHAuth proof on disconnect', async () => { - const fetchMock = vi.fn() - vi.stubGlobal('fetch', fetchMock) - vi.stubGlobal('window', { fetch: fetchMock }) - - const ethAuthProof = { - typedData: { - domain: {}, - types: {}, - message: {}, - }, - ewtString: 'proof-string', - } - - vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ - walletAddress: '0x1111111111111111111111111111111111111111', - ethAuthProof, - }) - - const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { - sequenceStorage: new WebStorage(), - transportMode: TransportMode.REDIRECT, - canUseIndexedDb: false, - redirectActionHandler: vi.fn(), - }) - - await client.connect(1, undefined, { - ethAuth: { - app: 'app-name', - }, - }) - - expect(await client.getEthAuthProof()).toEqual(ethAuthProof) - - await client.disconnect() - - expect(await client.getEthAuthProof()).toBeNull() - }) -}) diff --git a/packages/wallet/dapp-client/tsconfig.json b/packages/wallet/dapp-client/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/wallet/dapp-client/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/wallet/primitives-cli/eslint.config.js b/packages/wallet/primitives-cli/eslint.config.js deleted file mode 100644 index cecf89b031..0000000000 --- a/packages/wallet/primitives-cli/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config as baseConfig } from "@repo/eslint-config/base" - -/** @type {import("eslint").Linter.Config} */ -export default baseConfig diff --git a/packages/wallet/primitives-cli/package.json b/packages/wallet/primitives-cli/package.json deleted file mode 100644 index ec35fc66e7..0000000000 --- a/packages/wallet/primitives-cli/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@0xsequence/wallet-primitives-cli", - "type": "module", - "bin": "./dist/index.js", - "private": true, - "scripts": { - "build": "tsc", - "build:esbuild": "esbuild src/index.ts --bundle --platform=node --target=node16 --outfile=dist/index.js", - "dev": "tsc --watch", - "dev:esbuild": "esbuild src/index.ts --bundle --platform=node --target=node16 --outfile=dist/index.js --watch --sourcemap", - "start": "tsc && node dist/index.js", - "start:multi:server": "tsc && bash -c 'trap \"exit\" INT TERM; trap \"kill 0\" EXIT; for p in $(seq 9990 9999); do node dist/index.js server --silent --port \"$p\" & done; wait'", - "lint": "eslint . --max-warnings 0", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@types/yargs": "^17.0.35", - "concurrently": "^9.2.1", - "esbuild": "^0.27.3", - "nodemon": "^3.1.14", - "typescript": "^5.9.3" - }, - "dependencies": { - "@0xsequence/wallet-primitives": "workspace:^", - "ox": "^0.9.17", - "yargs": "^18.0.0" - } -} diff --git a/packages/wallet/primitives-cli/src/index.ts b/packages/wallet/primitives-cli/src/index.ts deleted file mode 100644 index 6935660d35..0000000000 --- a/packages/wallet/primitives-cli/src/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node - -import yargs from 'yargs' -import { hideBin } from 'yargs/helpers' -import payloadCommand from './subcommands/payload.js' -import configCommand from './subcommands/config.js' -import devToolsCommand from './subcommands/devTools.js' -import signatureCommand from './subcommands/signature.js' -import sessionCommand from './subcommands/session.js' -import serverCommand from './subcommands/server.js' -import addressCommand from './subcommands/address.js' -import recoveryCommand from './subcommands/recovery.js' -import passkeysCommand from './subcommands/passkeys.js' - -void yargs(hideBin(process.argv)) - .command(payloadCommand) - .command(configCommand) - .command(devToolsCommand) - .command(signatureCommand) - .command(sessionCommand) - .command(serverCommand) - .command(addressCommand) - .command(recoveryCommand) - .command(passkeysCommand) - .demandCommand(1) - .strict() - .help().argv diff --git a/packages/wallet/primitives-cli/src/subcommands/address.ts b/packages/wallet/primitives-cli/src/subcommands/address.ts deleted file mode 100644 index 062349efca..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/address.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Address, Bytes } from 'ox' -import type { CommandModule } from 'yargs' -import { Address as SequenceAddress, Context } from '@0xsequence/wallet-primitives' - -export async function doCalculateAddress(options: { - imageHash: string - factory: string - module: string - creationCode?: string -}): Promise { - const context = { - factory: Address.from(options.factory), - stage1: Address.from(options.module), - creationCode: (options.creationCode || Context.Dev2.creationCode) as `0x${string}`, - } - - return SequenceAddress.from(Bytes.fromHex(options.imageHash as `0x${string}`), context) -} - -const addressCommand: CommandModule = { - command: 'address', - describe: 'Address utilities', - builder: (yargs) => { - return yargs - .command( - 'calculate ', - 'Calculate counterfactual wallet address', - (yargs) => { - return yargs - .positional('imageHash', { - type: 'string', - description: 'Image hash of the wallet', - demandOption: true, - }) - .positional('factory', { - type: 'string', - description: 'Factory address', - demandOption: true, - }) - .positional('module', { - type: 'string', - description: 'Stage1 address', - demandOption: true, - }) - .option('creationCode', { - type: 'string', - description: 'Creation code (optional)', - default: Context.Rc5.creationCode, - }) - }, - async (argv) => { - const { imageHash, factory, module, creationCode } = argv - console.log( - await doCalculateAddress({ - imageHash: imageHash!, - factory: factory!, - module: module!, - creationCode, - }), - ) - }, - ) - .demandCommand(1, 'You must specify a subcommand for address') - }, - handler: () => {}, -} - -export default addressCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/config.ts b/packages/wallet/primitives-cli/src/subcommands/config.ts deleted file mode 100644 index cf3e96bd94..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/config.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { CommandModule } from 'yargs' -import { Address, Hex } from 'ox' -import { fromPosOrStdin } from '../utils.js' -import { Signature, Config } from '@0xsequence/wallet-primitives' - -export const PossibleElements = [ - { - type: 'signer', - format: 'signer:
:', - description: 'A signer leaf', - }, - { - type: 'subdigest', - format: 'subdigest:', - description: 'A subdigest leaf', - }, - { - type: 'sapient', - format: 'sapient::
:', - description: 'A sapient leaf', - }, - { - type: 'nested', - format: 'nested:::()', - description: 'A nested leaf', - }, - { - type: 'node', - format: 'node:', - description: 'A node leaf', - }, - { - type: 'any-address-subdigest', - format: 'any-address-subdigest:', - description: 'An any address subdigest leaf', - }, -] - -function parseElements(elements: string): Config.Leaf[] { - const leaves: Config.Leaf[] = [] - let remainingElements = elements - - // Split by space and get first element - while (remainingElements.length > 0) { - const firstElement = remainingElements.split(' ')[0] - const firstElementType = firstElement!.split(':')[0] - if (firstElementType === 'signer') { - const [_, address, weight] = firstElement!.split(':') - leaves.push({ - type: 'signer', - address: Address.from(address!), - weight: BigInt(weight!), - }) - remainingElements = remainingElements.slice(firstElement!.length + 1) - } else if (firstElementType === 'subdigest') { - const [_, digest] = firstElement!.split(':') - leaves.push({ - type: 'subdigest', - digest: digest as `0x${string}`, - }) - remainingElements = remainingElements.slice(firstElement!.length + 1) - } else if (firstElementType === 'any-address-subdigest') { - const [_, digest] = firstElement!.split(':') - leaves.push({ - type: 'any-address-subdigest', - digest: digest as `0x${string}`, - }) - remainingElements = remainingElements.slice(firstElement!.length + 1) - } else if (firstElementType === 'sapient') { - const [_, imageHash, address, weight] = firstElement!.split(':') - if (!imageHash || !imageHash.startsWith('0x') || imageHash.length !== 66) { - throw new Error(`Invalid image hash: ${imageHash}`) - } - leaves.push({ - type: 'sapient-signer', - imageHash: imageHash as `0x${string}`, - address: Address.from(address!), - weight: BigInt(weight!), - }) - remainingElements = remainingElements.slice(firstElement!.length + 1) - } else if (firstElementType === 'nested') { - // This is a bit spacial - // as we need to grab all nested elements within ( ) - const [_, threshold, weight] = firstElement!.split(':') - const startSubElements = remainingElements.indexOf('(') - const endSubElements = remainingElements.indexOf(')') - if (startSubElements === -1 || endSubElements === -1) { - throw new Error(`Missing ( ) for nested element: ${remainingElements}`) - } - const innerSubElements = remainingElements.slice(startSubElements + 1, endSubElements) - leaves.push({ - type: 'nested', - threshold: BigInt(threshold!), - weight: BigInt(weight!), - tree: Config.flatLeavesToTopology(parseElements(innerSubElements)), - }) - remainingElements = remainingElements.slice(endSubElements + 1).trim() - } else if (firstElementType === 'node') { - const [_, hash] = firstElement!.split(':') - leaves.push(hash as Hex.Hex) - remainingElements = remainingElements.slice(firstElement!.length + 1) - } else { - throw new Error(`Invalid element: ${firstElement}`) - } - } - - return leaves -} - -export async function createConfig(options: { - threshold: string - checkpoint: string - from: string - content: string[] - checkpointer?: string -}): Promise { - const leaves = parseElements(options.content.join(' ')) - const config: Config.Config = { - threshold: BigInt(options.threshold), - checkpoint: BigInt(options.checkpoint), - // Starts with empty topology - topology: Config.flatLeavesToTopology(leaves), - checkpointer: options.checkpointer ? Address.from(options.checkpointer) : undefined, - } - - return Config.configToJson(config) -} - -export async function calculateImageHash(input: string): Promise { - const config = Config.configFromJson(input) - return Hex.fromBytes(Config.hashConfiguration(config)) -} - -export async function doEncode(input: string): Promise { - const configuration = Config.configFromJson(input) - return Hex.fromBytes(Signature.encodeSignature({ noChainId: true, configuration })) -} - -const configCommand: CommandModule = { - command: 'config', - describe: 'Configuration utilities', - builder: (yargs) => { - return yargs - .command( - 'new [content...]', - 'Create a new configuration', - (yargs) => { - return yargs - .option('threshold', { - type: 'string', - description: 'Threshold value for the configuration', - demandOption: true, - alias: 't', - }) - .option('checkpoint', { - type: 'string', - description: 'Checkpoint value for the configuration', - demandOption: true, - alias: 'c', - }) - .option('checkpointer', { - type: 'string', - description: 'Checkpointer address for the configuration', - demandOption: false, - alias: 'p', - }) - .option('from', { - type: 'string', - description: 'The process to use to create the configuration', - demandOption: false, - default: 'flat', - choices: ['flat'], - alias: 'f', - }) - .positional('content', { - type: 'string', - array: true, - description: - 'The elements to use to create the configuration:\n' + - PossibleElements.map((e) => `- ${e.format}`).join('\n'), - demandOption: true, - }) - }, - async (argv) => { - console.log(await createConfig(argv)) - }, - ) - .command( - 'image-hash [input]', - 'Calculate image hash from hex input', - (yargs) => { - return yargs.positional('input', { - type: 'string', - description: 'Hex input to hash (if not using pipe)', - }) - }, - async (argv) => { - const input = await fromPosOrStdin(argv, 'input') - console.log(await calculateImageHash(input)) - }, - ) - .command( - 'encode [input]', - 'Encode configuration from hex input', - (yargs) => { - return yargs.positional('input', { - type: 'string', - description: 'Hex input to encode (if not using pipe)', - }) - }, - async (argv) => { - const input = await fromPosOrStdin(argv, 'input') - console.log(await doEncode(input)) - }, - ) - .demandCommand(1, 'You must specify a subcommand for config') - }, - handler: () => {}, -} - -export default configCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/devTools.ts b/packages/wallet/primitives-cli/src/subcommands/devTools.ts deleted file mode 100644 index 15df34db0b..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/devTools.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { Permission, SessionConfig, Config } from '@0xsequence/wallet-primitives' -import crypto from 'crypto' -import { Bytes } from 'ox' -import type { CommandModule } from 'yargs' - -export interface RandomOptions { - seededRandom?: () => number - minThresholdOnNested?: number - maxPermissions?: number - maxRules?: number - checkpointerMode?: 'no' | 'random' | 'yes' - skewed?: 'left' | 'right' | 'none' -} - -export function createSeededRandom(seed: string) { - let currentSeed = seed - let hash = crypto.createHash('sha256').update(currentSeed).digest() - let index = 0 - - return () => { - if (index >= hash.length - 4) { - currentSeed = currentSeed + '1' - hash = crypto.createHash('sha256').update(currentSeed).digest() - index = 0 - } - - const value = hash.readUInt32LE(index) / 0x100000000 - index += 4 - return value - } -} - -function randomBytes(length: number, options?: RandomOptions): Uint8Array { - const bytes = new Uint8Array(length) - if (options?.seededRandom) { - for (let i = 0; i < length; i++) { - bytes[i] = Math.floor(options.seededRandom() * 256) - } - return bytes - } - return crypto.getRandomValues(bytes) -} - -function randomHex(length: number, options?: RandomOptions): `0x${string}` { - return Bytes.toHex(randomBytes(length, options)) -} - -function randomBigInt(max: bigint, options?: RandomOptions): bigint { - if (options?.seededRandom) { - return BigInt(Math.floor(options.seededRandom() * Number(max))) - } - return BigInt(Math.floor(Math.random() * Number(max))) -} - -function randomAddress(options?: RandomOptions): `0x${string}` { - return `0x${Buffer.from(randomBytes(20, options)).toString('hex')}` -} - -function generateRandomTopology(depth: number, options?: RandomOptions): Config.Topology { - if (depth <= 0) { - const leafType = Math.floor((options?.seededRandom ?? Math.random)() * 5) - - switch (leafType) { - case 0: // SignerLeaf - return { - type: 'signer', - address: randomAddress(options), - weight: randomBigInt(256n, options), - } - - case 1: // SapientSigner - return { - type: 'sapient-signer', - address: randomAddress(options), - weight: randomBigInt(256n, options), - imageHash: randomHex(32, options), - } - - case 2: // SubdigestLeaf - return { - type: 'subdigest', - digest: randomHex(32, options), - } - - case 3: // NodeLeaf - return randomHex(32, options) - - case 4: { - // NestedLeaf - const minThreshold = BigInt(options?.minThresholdOnNested ?? 0) - return { - type: 'nested', - tree: generateRandomTopology(0, options), - weight: randomBigInt(256n, options), - threshold: minThreshold + randomBigInt(65535n - minThreshold, options), - } - } - } - } - - // Generate a node with two random subtrees - if (options?.skewed === 'left') { - return [generateRandomTopology(0, options), generateRandomTopology(depth - 1, options)] - } else if (options?.skewed === 'right') { - return [generateRandomTopology(depth - 1, options), generateRandomTopology(0, options)] - } else { - return [generateRandomTopology(depth - 1, options), generateRandomTopology(depth - 1, options)] - } -} - -async function generateSessionsTopology( - depth: number, - options?: RandomOptions, -): Promise { - const isLeaf = (options?.seededRandom ?? Math.random)() * 2 > 1 - - if (isLeaf || depth <= 1) { - const permissionsCount = Math.floor((options?.seededRandom ?? Math.random)() * (options?.maxPermissions ?? 5)) + 1 - const permissions = await Promise.all( - Array.from({ length: permissionsCount }, () => generateRandomPermission(options)), - ) - return { - type: 'session-permissions', - signer: randomAddress(options), - chainId: Number(randomBigInt(1000000000000000000n, options)), - valueLimit: randomBigInt(100n, options), - deadline: randomBigInt(1000n, options), - permissions: permissions as [Permission.Permission, ...Permission.Permission[]], - } - } - - return [await generateSessionsTopology(depth - 1, options), await generateSessionsTopology(depth - 1, options)] -} - -async function generateRandomPermission(options?: RandomOptions): Promise { - const rulesCount = Math.floor((options?.seededRandom ?? Math.random)() * (options?.maxRules ?? 5)) + 1 - return { - target: randomAddress(options), - rules: await Promise.all(Array.from({ length: rulesCount }, () => generateRandomRule(options))), - } -} - -async function generateRandomRule(options?: RandomOptions): Promise { - return { - cumulative: (options?.seededRandom ?? Math.random)() * 2 > 1, - operation: Math.floor((options?.seededRandom ?? Math.random)() * 4), - value: randomBytes(32, options), - offset: randomBigInt(100n, options), - mask: randomBytes(32, options), - } -} - -export async function doRandomConfig(maxDepth: number, options?: RandomOptions): Promise { - const config: Config.Config = { - threshold: randomBigInt(100n, options), - checkpoint: randomBigInt(1000n, options), - topology: generateRandomTopology(maxDepth, options), - checkpointer: (() => { - switch (options?.checkpointerMode) { - case 'yes': - return randomAddress(options) - case 'random': - return (options?.seededRandom?.() ?? Math.random()) > 0.5 ? randomAddress(options) : undefined - case 'no': - default: - return undefined - } - })(), - } - return Config.configToJson(config) -} - -export async function doRandomSessionTopology(maxDepth: number, options?: RandomOptions): Promise { - const topology = await generateSessionsTopology(maxDepth, options) - return SessionConfig.sessionsTopologyToJson(topology) -} - -const command: CommandModule = { - command: 'dev-tools', - describe: 'Development tools and utilities', - builder: (yargs) => - yargs - .command( - 'random-config', - 'Generate a random configuration', - (yargs) => { - return yargs - .option('max-depth', { - type: 'number', - description: 'Maximum depth of the configuration tree', - default: 3, - }) - .option('seed', { - type: 'string', - description: 'Seed for deterministic generation', - required: false, - }) - .option('min-threshold-on-nested', { - type: 'number', - description: 'Minimum threshold value for nested leaves', - default: 0, - }) - .option('checkpointer', { - type: 'string', - choices: ['no', 'random', 'yes'], - description: 'Checkpointer mode: no (never add), random (50% chance), yes (always add)', - default: 'no', - }) - .option('skewed', { - type: 'string', - choices: ['left', 'right', 'none'], - description: 'Skewed topology: left (left-heavy), right (right-heavy), none (balanced)', - default: 'none', - }) - }, - async (argv) => { - const options: RandomOptions = { - seededRandom: argv.seed ? createSeededRandom(argv.seed) : undefined, - minThresholdOnNested: argv.minThresholdOnNested, - checkpointerMode: argv.checkpointer as 'no' | 'random' | 'yes', - skewed: argv.skewed as 'left' | 'right' | undefined, - } - const result = await doRandomConfig(argv.maxDepth as number, options) - console.log(result) - }, - ) - .command( - 'random-session-topology', - 'Generate a random session topology', - (yargs) => { - return yargs - .option('max-depth', { - type: 'number', - description: 'Maximum depth of the session topology', - default: 1, - }) - .option('max-permissions', { - type: 'number', - description: 'Maximum number of permissions in each session', - default: 1, - }) - .option('max-rules', { - type: 'number', - description: 'Maximum number of rules in each permission', - default: 1, - }) - .option('seed', { - type: 'string', - description: 'Seed for deterministic generation', - required: false, - }) - }, - async (argv) => { - const options: RandomOptions = { - seededRandom: argv.seed ? createSeededRandom(argv.seed) : undefined, - maxPermissions: argv.maxPermissions, - maxRules: argv.maxRules, - skewed: argv.skewed as 'left' | 'right' | undefined, - } - const result = await doRandomSessionTopology(argv.maxDepth as number, options) - console.log(result) - }, - ) - .demandCommand(1, 'You must specify a subcommand for dev-tools') - .strict(), - handler: () => {}, -} - -export default command diff --git a/packages/wallet/primitives-cli/src/subcommands/passkeys.ts b/packages/wallet/primitives-cli/src/subcommands/passkeys.ts deleted file mode 100644 index 5858409bec..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/passkeys.ts +++ /dev/null @@ -1,298 +0,0 @@ -// ./packages/wallet/primitives-cli/src/subcommands/passkeys.ts - -import type { CommandModule } from 'yargs' -import { Bytes, Hex } from 'ox' -import { fromPosOrStdin } from '../utils.js' -import { Extensions } from '@0xsequence/wallet-primitives' - -// Reusable function for encoding a signature -export async function doEncodeSignature(options: { - x: string - y: string - requireUserVerification: boolean - credentialId?: string - metadataHash?: string - r: string - s: string - authenticatorData: string - clientDataJson: string | object - embedMetadata: boolean -}): Promise { - if (options.credentialId && options.metadataHash) { - throw new Error('Cannot provide both credential-id and metadata-hash') - } - if (options.embedMetadata && !options.credentialId && !options.metadataHash) { - throw new Error('Metadata (credential-id or metadata-hash) is required when embed-metadata is true') - } - - const publicKey: Extensions.Passkeys.PublicKey = { - x: options.x as Hex.Hex, - y: options.y as Hex.Hex, - requireUserVerification: options.requireUserVerification, - metadata: options.credentialId - ? { credentialId: options.credentialId } - : options.metadataHash - ? (options.metadataHash as Hex.Hex) - : undefined, - } - - const decodedSignature: Extensions.Passkeys.DecodedSignature = { - publicKey, - r: Bytes.fromHex(options.r as Hex.Hex), - s: Bytes.fromHex(options.s as Hex.Hex), - authenticatorData: Bytes.fromHex(options.authenticatorData as Hex.Hex), - clientDataJSON: - typeof options.clientDataJson === 'string' ? options.clientDataJson : JSON.stringify(options.clientDataJson), - embedMetadata: options.embedMetadata, - } - - const encoded = Extensions.Passkeys.encode(decodedSignature) - return Bytes.toHex(encoded) -} - -// Reusable function for decoding a signature -export async function doDecodeSignature(encodedSignatureHex: string): Promise { - const encodedBytes = Bytes.fromHex(encodedSignatureHex as Hex.Hex) - const decoded = Extensions.Passkeys.decode(encodedBytes) - - // Convert bytes back to hex for readability in JSON output - const jsonFriendlyDecoded = { - ...decoded, - publicKey: { - ...decoded.publicKey, - metadata: - typeof decoded.publicKey.metadata === 'string' - ? decoded.publicKey.metadata // Keep hex hash as is - : decoded.publicKey.metadata, // Keep credentialId object as is - }, - r: Bytes.toHex(decoded.r), - s: Bytes.toHex(decoded.s), - authenticatorData: Bytes.toHex(decoded.authenticatorData), - } - - return JSON.stringify(jsonFriendlyDecoded, null, 2) -} - -// Reusable function for computing the root -export async function doComputeRoot(options: { - x: string - y: string - requireUserVerification: boolean - credentialId?: string - metadataHash?: string -}): Promise { - if (options.credentialId && options.metadataHash) { - throw new Error('Cannot provide both credential-id and metadata-hash') - } - - const publicKey: Extensions.Passkeys.PublicKey = { - x: options.x as Hex.Hex, - y: options.y as Hex.Hex, - requireUserVerification: options.requireUserVerification, - metadata: options.credentialId - ? { credentialId: options.credentialId } - : options.metadataHash - ? (options.metadataHash as Hex.Hex) - : undefined, - } - - const root = Extensions.Passkeys.rootFor(publicKey) - return root -} - -// Reusable function for validating a signature -export async function doValidateSignature(options: { - challenge: string - x: string - y: string - requireUserVerification: boolean - credentialId?: string - metadataHash?: string - r: string - s: string - authenticatorData: string - clientDataJson: string -}): Promise { - if (options.credentialId && options.metadataHash) { - throw new Error('Cannot provide both credential-id and metadata-hash') - } - - const publicKey: Extensions.Passkeys.PublicKey = { - x: options.x as Hex.Hex, - y: options.y as Hex.Hex, - requireUserVerification: options.requireUserVerification, - metadata: options.credentialId - ? { credentialId: options.credentialId } - : options.metadataHash - ? (options.metadataHash as Hex.Hex) - : undefined, - } - - // Construct DecodedSignature without embedMetadata flag, as validation doesn't need it directly - const decodedSignature: Omit = { - publicKey, - r: Bytes.fromHex(options.r as Hex.Hex), - s: Bytes.fromHex(options.s as Hex.Hex), - authenticatorData: Bytes.fromHex(options.authenticatorData as Hex.Hex), - clientDataJSON: options.clientDataJson, - } - - return Extensions.Passkeys.isValidSignature(options.challenge as Hex.Hex, decodedSignature) -} - -const passkeysCommand: CommandModule = { - command: 'passkeys', - describe: 'Passkeys extension utilities', - builder: (yargs) => { - return yargs - .command( - 'encode-signature', - 'Encode a passkey signature', - (yargs) => { - return yargs - .option('x', { type: 'string', description: 'Public key X coordinate (hex)', demandOption: true }) - .option('y', { type: 'string', description: 'Public key Y coordinate (hex)', demandOption: true }) - .option('require-user-verification', { - type: 'boolean', - description: 'Flag if UV is required', - default: false, - }) - .option('credential-id', { type: 'string', description: 'Credential ID (string, for metadata)' }) - .option('metadata-hash', { - type: 'string', - description: 'Metadata hash (hex, alternative to credential-id)', - }) - .option('r', { type: 'string', description: 'Signature R component (hex)', demandOption: true }) - .option('s', { type: 'string', description: 'Signature S component (hex)', demandOption: true }) - .option('authenticator-data', { - type: 'string', - description: 'Authenticator data (hex)', - demandOption: true, - }) - .option('client-data-json', { - type: 'string', - description: 'Client data JSON (string)', - demandOption: true, - }) - .option('embed-metadata', { - type: 'boolean', - description: 'Flag to embed metadata hash in the encoded signature', - default: false, - }) - .conflicts('credential-id', 'metadata-hash') - }, - async (argv) => { - const result = await doEncodeSignature({ - x: argv.x, - y: argv.y, - requireUserVerification: argv.requireUserVerification, - credentialId: argv.credentialId, - metadataHash: argv.metadataHash, - r: argv.r, - s: argv.s, - authenticatorData: argv.authenticatorData, - clientDataJson: argv.clientDataJson, - embedMetadata: argv.embedMetadata, - }) - console.log(result) - }, - ) - .command( - 'decode-signature [encoded-signature]', - 'Decode an encoded passkey signature', - (yargs) => { - return yargs.positional('encoded-signature', { - type: 'string', - description: 'Encoded signature in hex format (or read from stdin)', - }) - }, - async (argv) => { - const encodedSignatureHex = await fromPosOrStdin(argv, 'encoded-signature') - const result = await doDecodeSignature(encodedSignatureHex) - console.log(result) - }, - ) - .command( - 'root', - 'Compute the root hash of a passkey public key tree', - (yargs) => { - return yargs - .option('x', { type: 'string', description: 'Public key X coordinate (hex)', demandOption: true }) - .option('y', { type: 'string', description: 'Public key Y coordinate (hex)', demandOption: true }) - .option('require-user-verification', { - type: 'boolean', - description: 'Flag if UV is required', - default: false, - }) - .option('credential-id', { type: 'string', description: 'Credential ID (string, for metadata)' }) - .option('metadata-hash', { - type: 'string', - description: 'Metadata hash (hex, alternative to credential-id)', - }) - .conflicts('credential-id', 'metadata-hash') - }, - async (argv) => { - const result = await doComputeRoot({ - x: argv.x, - y: argv.y, - requireUserVerification: argv.requireUserVerification, - credentialId: argv.credentialId, - metadataHash: argv.metadataHash, - }) - console.log(result) - }, - ) - .command( - 'validate-signature', - 'Validate a passkey signature', - (yargs) => { - return yargs - .option('challenge', { type: 'string', description: 'Original challenge (hex)', demandOption: true }) - .option('x', { type: 'string', description: 'Public key X coordinate (hex)', demandOption: true }) - .option('y', { type: 'string', description: 'Public key Y coordinate (hex)', demandOption: true }) - .option('require-user-verification', { - type: 'boolean', - description: 'Flag if UV is required', - default: false, - }) - .option('credential-id', { type: 'string', description: 'Credential ID (string, for metadata)' }) - .option('metadata-hash', { - type: 'string', - description: 'Metadata hash (hex, alternative to credential-id)', - }) - .option('r', { type: 'string', description: 'Signature R component (hex)', demandOption: true }) - .option('s', { type: 'string', description: 'Signature S component (hex)', demandOption: true }) - .option('authenticator-data', { - type: 'string', - description: 'Authenticator data (hex)', - demandOption: true, - }) - .option('client-data-json', { - type: 'string', - description: 'Client data JSON (string)', - demandOption: true, - }) - .conflicts('credential-id', 'metadata-hash') - }, - async (argv) => { - const isValid = await doValidateSignature({ - challenge: argv.challenge, - x: argv.x, - y: argv.y, - requireUserVerification: argv.requireUserVerification, - credentialId: argv.credentialId, - metadataHash: argv.metadataHash, - r: argv.r, - s: argv.s, - authenticatorData: argv.authenticatorData, - clientDataJson: argv.clientDataJson, - }) - console.log(isValid) - }, - ) - .demandCommand(1, 'You must specify a subcommand for passkeys') - }, - handler: () => {}, -} - -export default passkeysCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/payload.ts b/packages/wallet/primitives-cli/src/subcommands/payload.ts deleted file mode 100644 index eb86674ac4..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/payload.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { AbiParameters, Address, Hex } from 'ox' -import type { CommandModule } from 'yargs' -import { Payload } from '@0xsequence/wallet-primitives' -import { fromPosOrStdin } from '../utils.js' - -const CallAbi = [ - { type: 'address', name: 'to' }, - { type: 'uint256', name: 'value' }, - { type: 'bytes', name: 'data' }, - { type: 'uint256', name: 'gasLimit' }, - { type: 'bool', name: 'delegateCall' }, - { type: 'bool', name: 'onlyFallback' }, - { type: 'uint256', name: 'behaviorOnError' }, -] - -export const DecodedAbi = [ - { type: 'uint8', name: 'kind' }, - { type: 'bool', name: 'noChainId' }, - { - type: 'tuple[]', - name: 'calls', - components: CallAbi, - }, - { type: 'uint256', name: 'space' }, - { type: 'uint256', name: 'nonce' }, - { type: 'bytes', name: 'message' }, - { type: 'bytes32', name: 'imageHash' }, - { type: 'bytes32', name: 'digest' }, - { type: 'address[]', name: 'parentWallets' }, -] - -export async function doConvertToAbi(_payload: string): Promise { - // Not implemented yet, but following the pattern - throw new Error('Not implemented') -} - -export async function doConvertToPacked(payload: string, wallet?: string): Promise { - const decodedPayload = Payload.fromAbiFormat( - AbiParameters.decode( - [{ type: 'tuple', name: 'payload', components: DecodedAbi }], - payload as Hex.Hex, - )[0] as unknown as Payload.SolidityDecoded, - ) - - if (Payload.isCalls(decodedPayload)) { - const packed = Payload.encode(decodedPayload, wallet ? (wallet as `0x${string}`) : undefined) - return Hex.from(packed) - } - - throw new Error('Not implemented') -} - -export async function doConvertToJson(payload: string): Promise { - const decoded = AbiParameters.decode( - [{ type: 'tuple', name: 'payload', components: DecodedAbi }], - payload as Hex.Hex, - )[0] as unknown as Payload.SolidityDecoded - - const json = JSON.stringify(decoded) - return json -} - -export async function doHash(wallet: string, chainId: number, payload: string): Promise { - const decoded = AbiParameters.decode( - [{ type: 'tuple', name: 'payload', components: DecodedAbi }], - payload as Hex.Hex, - )[0] as unknown as Payload.SolidityDecoded - - return Hex.from(Payload.hash(Address.from(wallet), chainId, Payload.fromAbiFormat(decoded))) -} - -const payloadCommand: CommandModule = { - command: 'payload', - describe: 'Payload conversion utilities', - builder: (yargs) => { - return yargs - .command( - 'to-abi [payload]', - 'Convert payload to ABI format', - (yargs) => { - return yargs.positional('payload', { - type: 'string', - description: 'Input payload to convert', - }) - }, - async (argv) => { - const payload = await fromPosOrStdin(argv, 'payload') - const result = await doConvertToAbi(payload) - console.log(result) - }, - ) - .command( - 'to-packed [payload] [wallet]', - 'Convert payload to packed format', - (yargs) => { - return yargs - .positional('payload', { - type: 'string', - description: 'Input payload to convert', - }) - .positional('wallet', { - type: 'string', - description: 'Wallet of the wallet to hash the payload', - demandOption: false, - }) - }, - async (argv) => { - const payload = await fromPosOrStdin(argv, 'payload') - const result = await doConvertToPacked(payload, argv.wallet) - console.log(result) - }, - ) - .command( - 'to-json [payload]', - 'Convert payload to JSON format', - (yargs) => { - return yargs.positional('payload', { - type: 'string', - description: 'Input payload to convert', - }) - }, - async (argv) => { - const payload = await fromPosOrStdin(argv, 'payload') - const result = await doConvertToJson(payload) - console.log(result) - }, - ) - .command( - 'hash [payload]', - 'Hash the payload', - (yargs) => { - return yargs - .option('wallet', { - type: 'string', - description: 'Wallet of the wallet to hash the payload', - demandOption: true, - }) - .option('chainId', { - type: 'string', - description: 'Chain ID of the payload', - demandOption: true, - }) - .positional('payload', { - type: 'string', - description: 'Input payload to hash', - }) - }, - async (argv) => { - const payload = await fromPosOrStdin(argv, 'payload') - const result = await doHash(argv.wallet, Number(argv.chainId), payload) - console.log(result) - }, - ) - .demandCommand(1, 'You must specify a subcommand for payload') - }, - handler: () => {}, -} - -export default payloadCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/recovery.ts b/packages/wallet/primitives-cli/src/subcommands/recovery.ts deleted file mode 100644 index fb9a0a03de..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/recovery.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { CommandModule } from 'yargs' -import { readStdin } from '../utils.js' -import { Address, Bytes, Hex } from 'ox' -import { Extensions } from '@0xsequence/wallet-primitives' - -async function parseLeaves(leavesInput: string | string[]): Promise { - if (typeof leavesInput === 'string') { - return parseLeaves(leavesInput.split(' ')) - } - - return leavesInput.map((leafStr) => { - const parts = leafStr.split(':') - if (parts.length !== 4 || parts[0] !== 'signer') { - throw new Error(`Invalid leaf format: ${leafStr}`) - } - const [_, address, requiredDeltaTimeStr, minTimestampStr] = parts - if (!requiredDeltaTimeStr || !minTimestampStr) { - throw new Error(`Invalid leaf format: ${leafStr}`) - } - const requiredDeltaTime = BigInt(requiredDeltaTimeStr) - const minTimestamp = BigInt(minTimestampStr) - return { - type: 'leaf', - signer: address as Address.Address, - requiredDeltaTime, - minTimestamp, - } - }) -} - -export async function doHashFromLeaves(leavesInput: string | string[]): Promise { - const leaves = await parseLeaves(leavesInput) - const topology = Extensions.Recovery.fromRecoveryLeaves(leaves) - return Extensions.Recovery.hashConfiguration(topology) -} - -export async function doEncode(leavesInput: string | string[]): Promise { - const leaves = await parseLeaves(leavesInput) - const topology = Extensions.Recovery.fromRecoveryLeaves(leaves) - const encoded = Extensions.Recovery.encodeTopology(topology) - return Bytes.toHex(encoded) -} - -export async function doTrim(leavesInput: string | string[], signer: string): Promise { - const leaves = await parseLeaves(leavesInput) - const topology = Extensions.Recovery.fromRecoveryLeaves(leaves) - const trimmed = Extensions.Recovery.trimTopology(topology, signer as Address.Address) - const encoded = Extensions.Recovery.encodeTopology(trimmed) - return Bytes.toHex(encoded) -} - -export async function doHashEncoded(encodedStr: Hex.Hex): Promise { - const encoded = Bytes.fromHex(encodedStr) - const topology = Extensions.Recovery.decodeTopology(encoded) - return Extensions.Recovery.hashConfiguration(topology) -} - -const recoveryCommand: CommandModule = { - command: 'recovery', - describe: 'Recovery tree utilities', - builder: (yargs) => { - return yargs - .command( - 'hash-from-leaves [leaves...]', - 'Compute the hash of a recovery topology from leaves', - (yargs) => { - return yargs - .positional('leaves', { - type: 'string', - array: true, - description: 'List of recovery leaves in "signer:address:requiredDeltaTime:minTimestamp" format', - demandOption: false, - }) - .example('$0 recovery hash-from-leaves signer:0x123...:100:1600000000', 'hash a single leaf') - }, - async (argv) => { - let leavesInput: string[] - if (argv.leaves) { - leavesInput = argv.leaves - } else { - const stdin = await readStdin() - leavesInput = stdin - .split('\n') - .map((line) => line.trim()) - .filter((line) => line) - } - try { - const hash = await doHashFromLeaves(leavesInput) - console.log(hash) - } catch (error) { - console.error((error as Error).message) - process.exit(1) - } - }, - ) - .command( - 'encode [leaves...]', - 'Encode recovery leaves into topology bytes', - (yargs) => { - return yargs.positional('leaves', { - type: 'string', - array: true, - description: 'List of recovery leaves in "signer:address:requiredDeltaTime:minTimestamp" format', - demandOption: false, - }) - }, - async (argv) => { - let leavesInput: string[] - if (argv.leaves) { - leavesInput = argv.leaves - } else { - const stdin = await readStdin() - leavesInput = stdin - .split('\n') - .map((line) => line.trim()) - .filter((line) => line) - } - try { - const encoded = await doEncode(leavesInput) - console.log(encoded) - } catch (error) { - console.error((error as Error).message) - process.exit(1) - } - }, - ) - .command( - 'trim [leaves...]', - 'Trim the topology to a specific signer and encode', - (yargs) => { - return yargs - .positional('leaves', { - type: 'string', - array: true, - description: 'List of recovery leaves in "signer:address:requiredDeltaTime:minTimestamp" format', - demandOption: false, - }) - .option('signer', { - type: 'string', - description: 'Signer address to keep', - demandOption: true, - }) - }, - async (argv) => { - let leavesInput: string[] - if (argv.leaves) { - leavesInput = argv.leaves - } else { - const stdin = await readStdin() - leavesInput = stdin - .split('\n') - .map((line) => line.trim()) - .filter((line) => line) - } - const signer = argv.signer - try { - const encoded = await doTrim(leavesInput, signer) - console.log(encoded) - } catch (error) { - console.error((error as Error).message) - process.exit(1) - } - }, - ) - .command( - 'hash-encoded [encoded]', - 'Compute the hash of an encoded recovery topology', - (yargs) => { - return yargs.positional('encoded', { - type: 'string', - description: 'The encoded topology in hex format', - demandOption: true, - }) - }, - async (argv) => { - const encodedStr = argv.encoded - try { - const hash = await doHashEncoded(Hex.fromString(encodedStr)) - console.log(hash) - } catch (error) { - console.error((error as Error).message) - process.exit(1) - } - }, - ) - .demandCommand(1, 'You must specify a subcommand for recovery') - }, - handler: () => {}, -} - -export default recoveryCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/server.ts b/packages/wallet/primitives-cli/src/subcommands/server.ts deleted file mode 100644 index 29c5e1118b..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/server.ts +++ /dev/null @@ -1,404 +0,0 @@ -import type { CommandModule } from 'yargs' -import { createServer, IncomingMessage, ServerResponse } from 'http' -import * as config from './config.js' -import * as devTools from './devTools.js' -import * as payload from './payload.js' -import * as session from './session.js' -import * as sessionExplicit from './sessionExplicit.js' -import * as sessionImplicit from './sessionImplicit.js' -import * as signatureUtils from './signature.js' -import * as address from './address.js' -import * as recovery from './recovery.js' -import * as passkeys from './passkeys.js' - -// Basic JSON-RPC types -interface JsonRpcRequest { - jsonrpc: string - method: string - params?: any // eslint-disable-line @typescript-eslint/no-explicit-any - id?: number | string -} - -interface JsonRpcSuccessResponse { - jsonrpc: '2.0' - result: any // eslint-disable-line @typescript-eslint/no-explicit-any - id?: number | string -} - -interface JsonRpcErrorResponse { - jsonrpc: '2.0' - error: { - code: number - message: string - data?: any // eslint-disable-line @typescript-eslint/no-explicit-any - } - id?: number | string -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function successResponse(id: number | string | undefined, result: any): JsonRpcSuccessResponse { - return { - jsonrpc: '2.0', - id, - result, - } -} - -function errorResponse( - id: number | string | undefined, - code: number, - message: string, - data?: any, // eslint-disable-line @typescript-eslint/no-explicit-any -): JsonRpcErrorResponse { - return { - jsonrpc: '2.0', - id, - error: { - code, - message, - data, - }, - } -} - -// We collect all of the CLI methods into a single map that can be invoked by name. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const rpcMethods: Record Promise> = { - // CONFIG - async config_new(params) { - const { threshold, checkpoint, from = 'flat', content, checkpointer } = params - const result = await config.createConfig({ threshold, checkpoint, from, content: content.split(' '), checkpointer }) - return result - }, - async config_imageHash(params) { - const { input } = params - const result = await config.calculateImageHash(JSON.stringify(input)) - return result - }, - async config_encode(params) { - const { input } = params - const result = await config.doEncode(JSON.stringify(input)) - return result - }, - - // DEV TOOLS - async devTools_randomConfig(params) { - const { maxDepth = 3, seed, minThresholdOnNested = 0, checkpointer = 'no', skewed } = params - const options: devTools.RandomOptions = { - seededRandom: seed ? devTools.createSeededRandom(seed) : undefined, - minThresholdOnNested, - checkpointerMode: checkpointer as 'no' | 'random' | 'yes', - skewed: skewed as 'left' | 'right' | 'none', - } - const result = await devTools.doRandomConfig(maxDepth, options) - return result - }, - async devTools_randomSessionTopology(params) { - const { maxDepth = 1, maxPermissions = 1, maxRules = 1, seed } = params - const options: devTools.RandomOptions = { - seededRandom: seed ? devTools.createSeededRandom(seed) : undefined, - maxPermissions, - maxRules, - } - const result = await devTools.doRandomSessionTopology(maxDepth, options) - return result - }, - - // PAYLOAD - async payload_toAbi(params) { - const { payload: inputPayload } = params - const result = await payload.doConvertToAbi(inputPayload) - return result - }, - async payload_toPacked(params) { - const { payload: inputPayload, wallet } = params - const result = await payload.doConvertToPacked(inputPayload, wallet) - return result - }, - async payload_toJson(params) { - const { payload: inputPayload } = params - const result = await payload.doConvertToJson(inputPayload) - return result - }, - async payload_hashFor(params) { - const result = await payload.doHash(params.wallet, params.chainId, params.payload) - return result - }, - - // SESSION - async session_empty(params) { - const { identitySigner } = params - const result = await session.doEmptyTopology(identitySigner) - return result - }, - async session_encodeTopology(params) { - const { sessionTopology } = params - const result = await session.doEncodeTopology(JSON.stringify(sessionTopology)) - return result - }, - async session_encodeCallSignatures(params) { - const { sessionTopology, callSignatures, explicitSigners, implicitSigners, identitySigner } = params - const result = await session.doEncodeSessionCallSignatures( - JSON.stringify(sessionTopology), - callSignatures.map(JSON.stringify), - identitySigner, - explicitSigners, - implicitSigners, - ) - return result - }, - async session_imageHash(params) { - const { sessionTopology } = params - const result = await session.doImageHash(JSON.stringify(sessionTopology)) - return result - }, - - // SESSION EXPLICIT - async session_explicit_add(params) { - const { explicitSession, sessionTopology } = params - const result = await sessionExplicit.doAddSession(JSON.stringify(explicitSession), JSON.stringify(sessionTopology)) - return result - }, - async session_explicit_remove(params) { - const { explicitSessionAddress, sessionTopology } = params - const result = await sessionExplicit.doRemoveSession(explicitSessionAddress, JSON.stringify(sessionTopology)) - return result - }, - - // SESSION IMPLICIT - async session_implicit_addBlacklistAddress(params) { - const { blacklistAddress, sessionTopology } = params - const result = await sessionImplicit.doAddBlacklistAddress(blacklistAddress, JSON.stringify(sessionTopology)) - return result - }, - async session_implicit_removeBlacklistAddress(params) { - const { blacklistAddress, sessionTopology } = params - const result = await sessionImplicit.doRemoveBlacklistAddress(blacklistAddress, JSON.stringify(sessionTopology)) - return result - }, - - // SIGNATURE - async signature_encode(params) { - const { input, signatures, chainId = true, checkpointerData } = params - const result = await signatureUtils.doEncode( - JSON.stringify(input), - signatures.split(' '), - !chainId, - checkpointerData, - ) - return result - }, - async signature_concat(params) { - const { signatures } = params - const result = await signatureUtils.doConcat(signatures) - return result - }, - async signature_decode(params) { - const { signature: sig } = params - const result = await signatureUtils.doDecode(sig) - return result - }, - - // ADDRESS - async address_calculate(params) { - const { imageHash, factory, module, creationCode } = params - return await address.doCalculateAddress({ imageHash, factory, module, creationCode }) - }, - - // RECOVERY - async recovery_hashFromLeaves(params) { - const { leaves } = params - const result = await recovery.doHashFromLeaves(leaves) - return result - }, - async recovery_encode(params) { - const { leaves } = params - const result = await recovery.doEncode(leaves) - return result - }, - async recovery_trim(params) { - const { leaves, signer } = params - const result = await recovery.doTrim(leaves, signer) - return result - }, - async recovery_hashEncoded(params) { - const { encoded } = params - const result = await recovery.doHashEncoded(encoded) - return result - }, - - // PASSKEYS - async passkeys_encodeSignature(params) { - const result = await passkeys.doEncodeSignature(params) - return result - }, - async passkeys_decodeSignature(params) { - const { encodedSignature } = params - const resultString = await passkeys.doDecodeSignature(encodedSignature) - return JSON.parse(resultString) - }, - async passkeys_computeRoot(params) { - const result = await passkeys.doComputeRoot(params) - return result - }, - async passkeys_validateSignature(params) { - const result = await passkeys.doValidateSignature(params) - return result - }, -} - -async function handleSingleRequest( - rpcRequest: JsonRpcRequest, - debug: boolean, - silent: boolean, -): Promise { - const { id, jsonrpc, method, params } = rpcRequest - - if (!silent) console.log(`[${new Date().toISOString()}] Processing request: method=${method} id=${id}`) - if (debug && !silent) { - console.log('Request details:', JSON.stringify(rpcRequest, null, 2)) - } - - if (jsonrpc !== '2.0') { - const error = errorResponse(id, -32600, 'Invalid JSON-RPC version') - if (!silent) - console.log( - `[${new Date().toISOString()}] Error response:`, - debug ? JSON.stringify(error, null, 2) : error.error.message, - ) - return error - } - - const fn = rpcMethods[method] - if (!fn) { - const error = errorResponse(id, -32601, `Method not found: ${method}`) - if (!silent) - console.log( - `[${new Date().toISOString()}] Error response:`, - debug ? JSON.stringify(error, null, 2) : error.error.message, - ) - return error - } - - try { - const result = await fn(params ?? {}) - const response = successResponse(id, result) - if (!silent) console.log(`[${new Date().toISOString()}] Success response for method=${method} id=${id}`) - if (debug && !silent) { - console.log('Response details:', JSON.stringify(response, null, 2)) - } - return response - } catch (err: unknown) { - const error = errorResponse(id, -32000, err instanceof Error ? err.message : 'Unknown error') - if (!silent) - console.log( - `[${new Date().toISOString()}] Error response:`, - debug ? JSON.stringify(error, null, 2) : error.error.message, - ) - return error - } -} - -async function handleHttpRequest(req: IncomingMessage, res: ServerResponse, debug: boolean, silent: boolean) { - if (!silent) console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} from ${req.socket.remoteAddress}`) - - // Only handle POST /rpc - if (req.method !== 'POST' || req.url !== '/rpc') { - if (!silent) console.log(`[${new Date().toISOString()}] 404 Not Found`) - res.statusCode = 404 - res.end('Not Found') - return - } - - // Read the request body - let body = '' - for await (const chunk of req) { - body += chunk - } - - if (debug && !silent) { - console.log('Raw request body:', body) - } - - // Try to parse JSON. If invalid, return an error - let rpcRequests: JsonRpcRequest[] | JsonRpcRequest - try { - rpcRequests = JSON.parse(body) - } catch (error) { - if (!silent) console.log(`[${new Date().toISOString()}] JSON parse error:`, error) - res.statusCode = 400 - res.end(JSON.stringify(errorResponse(undefined, -32700, 'Parse error', String(error)))) - return - } - - // Might be a batch request (array of requests) or a single request - if (Array.isArray(rpcRequests)) { - if (!silent) console.log(`[${new Date().toISOString()}] Processing batch request with ${rpcRequests.length} items`) - const results = await Promise.all(rpcRequests.map((req) => handleSingleRequest(req, debug, silent))) - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify(results)) - } else { - const result = await handleSingleRequest(rpcRequests, debug, silent) - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify(result)) - } -} - -async function startServer(host: string, port: number, debug: boolean, silent: boolean) { - const server = createServer((req, res) => { - handleHttpRequest(req, res, debug, silent).catch((err) => { - // If something truly unexpected happens, respond with 500 - if (!silent) console.error(`[${new Date().toISOString()}] Internal server error:`, err) - res.statusCode = 500 - res.end(JSON.stringify(errorResponse(undefined, -32000, 'Internal server error', String(err)))) - }) - }) - - server.listen(port, host, () => { - if (!silent) { - console.log(`[${new Date().toISOString()}] RPC server running at http://${host}:${port}/rpc`) - if (debug) { - console.log('Debug mode enabled - detailed logging active') - } - } - }) -} - -const serverCommand: CommandModule = { - command: 'server', - describe: 'Run a JSON-RPC server exposing all CLI functionality, without using Express', - builder: (yargs) => { - return yargs - .option('host', { - type: 'string', - description: 'Hostname to listen on', - default: '127.0.0.1', - }) - .option('port', { - type: 'number', - description: 'Port to listen on', - default: 9999, - }) - .option('debug', { - type: 'boolean', - description: 'Enable debug logging', - default: false, - }) - .option('silent', { - type: 'boolean', - description: 'Disable all logging output', - default: false, - }) - }, - handler: async (argv) => { - const host = argv.host as string - const port = argv.port as number - const debug = argv.debug as boolean - const silent = argv.silent as boolean - await startServer(host, port, debug, silent) - }, -} - -export default serverCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/session.ts b/packages/wallet/primitives-cli/src/subcommands/session.ts deleted file mode 100644 index 2672721c61..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/session.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Hex } from 'ox' -import { CommandModule } from 'yargs' -import sessionExplicitCommand from './sessionExplicit.js' -import sessionImplicitCommand from './sessionImplicit.js' - -import { GenericTree, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' - -export async function doEmptyTopology(identitySigner: `0x${string}`): Promise { - const topology = SessionConfig.emptySessionsTopology(identitySigner) - return SessionConfig.sessionsTopologyToJson(topology) -} - -export async function doEncodeTopology(sessionTopologyInput: string): Promise { - const sessionTopology = SessionConfig.sessionsTopologyFromJson(sessionTopologyInput) - const encoded = SessionConfig.encodeSessionsTopology(sessionTopology) - return Hex.from(encoded) -} - -export async function doEncodeSessionCallSignatures( - sessionTopologyInput: string, - callSignaturesInput: string[], - identitySigner?: string, - explicitSigners: string[] = [], - implicitSigners: string[] = [], -): Promise { - const sessionTopology = SessionConfig.sessionsTopologyFromJson(sessionTopologyInput) - const callSignatures = callSignaturesInput.map((s) => SessionSignature.sessionCallSignatureFromJson(s)) - // Use first identity signer if not provided - if (!identitySigner) { - const identitySigners = SessionConfig.getIdentitySigners(sessionTopology) - if (identitySigners.length === 0) { - throw new Error('No identity signers found') - } - identitySigner = identitySigners[0]! - } - const encoded = SessionSignature.encodeSessionSignature( - callSignatures, - sessionTopology, - identitySigner as `0x${string}`, - explicitSigners as `0x${string}`[], - implicitSigners as `0x${string}`[], - ) - return Hex.from(encoded) -} - -export async function doImageHash(sessionTopologyInput: string): Promise { - const sessionTopology = SessionConfig.sessionsTopologyFromJson(sessionTopologyInput) - const encoded = SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology) - const hash = GenericTree.hash(encoded) - return Hex.from(hash) -} - -const sessionCommand: CommandModule = { - command: 'session', - describe: 'Session utilities', - builder: (yargs) => { - return yargs - .command( - 'empty [identity-signer]', - 'Create an empty session topology with the given identity signer', - (yargs) => { - return yargs.positional('identity-signer', { - type: 'string', - description: 'The identity signer for the session topology', - demandOption: true, - alias: 'i', - }) - }, - async (args) => { - console.log(await doEmptyTopology(args.identitySigner as `0x${string}`)) - }, - ) - .command( - 'encode-topology [session-topology]', - 'Encode a session topology', - (yargs) => { - return yargs.positional('session-topology', { - type: 'string', - description: 'The session topology', - demandOption: true, - }) - }, - async (args) => { - console.log(await doEncodeTopology(args.sessionTopology)) - }, - ) - .command( - 'encode-calls [session-topology] [call-signatures] [explicit-signers] [implicit-signers]', - 'Encode call signatures for sessions', - (yargs) => { - return yargs - .positional('session-topology', { - type: 'string', - description: 'The session topology', - demandOption: true, - }) - .positional('call-signatures', { - type: 'string', - array: true, - description: 'The call signatures', - demandOption: true, - }) - .option('identity-signer', { - type: 'string', - description: 'The identity signer', - demandOption: false, - default: undefined, - alias: 'id', - }) - .option('explicit-signers', { - type: 'string', - array: true, - description: 'The explicit signers', - demandOption: false, - default: [], - alias: 'e', - }) - .option('implicit-signers', { - type: 'string', - array: true, - description: 'The implicit signers', - demandOption: false, - default: [], - alias: 'i', - }) - }, - async (args) => { - console.log( - await doEncodeSessionCallSignatures( - args.sessionTopology, - args.callSignatures, - args.identitySigner, - args.explicitSigners, - args.implicitSigners, - ), - ) - }, - ) - .command( - 'image-hash [session-topology]', - 'Hash a session topology', - (yargs) => { - return yargs.positional('session-topology', { - type: 'string', - description: 'The session topology', - demandOption: true, - }) - }, - async (args) => { - console.log(await doImageHash(args.sessionTopology)) - }, - ) - .command(sessionExplicitCommand) - .command(sessionImplicitCommand) - .demandCommand(1, 'You must specify a subcommand for session') - }, - handler: () => {}, -} - -export default sessionCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/sessionExplicit.ts b/packages/wallet/primitives-cli/src/subcommands/sessionExplicit.ts deleted file mode 100644 index 3f9d775e3e..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/sessionExplicit.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { CommandModule } from 'yargs' -import { fromPosOrStdin } from '../utils.js' -import { Permission, SessionConfig } from '@0xsequence/wallet-primitives' - -export async function doAddSession(sessionInput: string, topologyInput: string): Promise { - const session = Permission.sessionPermissionsFromJson(sessionInput) - let topology = SessionConfig.sessionsTopologyFromJson(topologyInput) - if (!SessionConfig.isSessionsTopology(session)) { - throw new Error('Explicit session must be a valid session topology') - } - if (!SessionConfig.isSessionsTopology(topology)) { - throw new Error('Session topology must be a valid session topology') - } - // Find the session in the topology - if (SessionConfig.getSessionPermissions(topology, session.signer)) { - throw new Error('Session already exists') - } - // Merge the session into the topology - topology = SessionConfig.addExplicitSession(topology, session) - return SessionConfig.sessionsTopologyToJson(topology) -} - -export async function doRemoveSession(explicitSessionAddress: string, topologyInput: string): Promise { - const topology = SessionConfig.sessionsTopologyFromJson(topologyInput) - if (!SessionConfig.isSessionsTopology(topology)) { - throw new Error('Session topology must be a valid session topology') - } - if (!explicitSessionAddress || !explicitSessionAddress.startsWith('0x')) { - throw new Error('Explicit session address must be a valid address') - } - const updated = SessionConfig.removeExplicitSession(topology, explicitSessionAddress as `0x${string}`) - if (!updated) { - throw new Error('Session topology is empty') - } - return SessionConfig.sessionsTopologyToJson(updated) -} - -const sessionExplicitCommand: CommandModule = { - command: 'explicit', - describe: 'Explicit session utilities', - builder: (yargs) => { - return yargs - .command( - 'add [explicit-session] [session-topology]', - 'Add a session to the session topology', - (yargs) => { - return yargs - .positional('explicit-session', { - type: 'string', - description: 'Explicit session to add', - demandOption: true, - }) - .positional('session-topology', { - type: 'string', - description: 'Session topology to add to', - demandOption: true, - }) - }, - async (argv) => { - const sessionInput = argv.explicitSession - if (!sessionInput) { - throw new Error('Explicit session is required') - } - const topologyInput = await fromPosOrStdin(argv, 'session-topology') - console.log(await doAddSession(sessionInput, topologyInput)) - }, - ) - .command( - 'remove [explicit-session-address] [session-topology]', - 'Remove a session from the session topology', - (yargs) => { - return yargs - .positional('explicit-session-address', { - type: 'string', - description: 'Explicit session address to remove', - demandOption: true, - }) - .positional('session-topology', { - type: 'string', - description: 'Session topology to remove from', - demandOption: true, - }) - }, - async (argv) => { - const explicitSessionAddress = argv.explicitSessionAddress - const topologyInput = await fromPosOrStdin(argv, 'session-topology') - console.log(await doRemoveSession(explicitSessionAddress!, topologyInput)) - }, - ) - .demandCommand(1, 'You must specify a subcommand for session') - }, - handler: () => {}, -} - -export default sessionExplicitCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/sessionImplicit.ts b/packages/wallet/primitives-cli/src/subcommands/sessionImplicit.ts deleted file mode 100644 index 713e419b9a..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/sessionImplicit.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { SessionConfig } from '@0xsequence/wallet-primitives' -import { Address } from 'ox' -import type { CommandModule } from 'yargs' -import { fromPosOrStdin, requireString } from '../utils.js' - -export async function doAddBlacklistAddress(blacklistAddress: string, sessionTopologyInput: string): Promise { - const sessionTopology = SessionConfig.sessionsTopologyFromJson(sessionTopologyInput) - const updated = SessionConfig.addToImplicitBlacklist(sessionTopology, blacklistAddress as Address.Address) - return SessionConfig.sessionsTopologyToJson(updated) -} - -export async function doRemoveBlacklistAddress( - blacklistAddress: string, - sessionTopologyInput: string, -): Promise { - const sessionTopology = SessionConfig.sessionsTopologyFromJson(sessionTopologyInput) - const updated = SessionConfig.removeFromImplicitBlacklist(sessionTopology, blacklistAddress as Address.Address) - return SessionConfig.sessionsTopologyToJson(updated) -} - -const sessionImplicitCommand: CommandModule = { - command: 'implicit', - describe: 'Implicit session utilities', - builder: (yargs) => { - return yargs - .command( - 'blacklist-add [blacklist-address] [session-topology]', - 'Add an address to the implicit session blacklist', - (yargs) => { - return yargs - .positional('blacklist-address', { - type: 'string', - description: 'Blacklist address', - demandOption: true, - }) - .positional('session-topology', { - type: 'string', - description: 'Session topology', - demandOption: true, - }) - }, - async (argv) => { - const blacklistAddress = argv.blacklistAddress - requireString(blacklistAddress, 'Blacklist address') - const sessionTopologyInput = await fromPosOrStdin(argv, 'session-topology') - console.log(await doAddBlacklistAddress(blacklistAddress, sessionTopologyInput)) - }, - ) - .command( - 'blacklist-remove [blacklist-address] [session-topology]', - 'Remove an address from the implicit session blacklist', - (yargs) => { - return yargs - .positional('blacklist-address', { - type: 'string', - description: 'Blacklist address', - demandOption: true, - }) - .positional('session-topology', { - type: 'string', - description: 'Session topology', - demandOption: true, - }) - }, - async (argv) => { - const blacklistAddress = argv.blacklistAddress as string - if (!blacklistAddress) { - throw new Error('Blacklist address is required') - } - const sessionTopologyInput = await fromPosOrStdin(argv, 'session-topology') - console.log(await doRemoveBlacklistAddress(blacklistAddress, sessionTopologyInput)) - }, - ) - .demandCommand(1, 'You must specify a subcommand for implicit session') - }, - handler: () => {}, -} - -export default sessionImplicitCommand diff --git a/packages/wallet/primitives-cli/src/subcommands/signature.ts b/packages/wallet/primitives-cli/src/subcommands/signature.ts deleted file mode 100644 index 0dacf0da53..0000000000 --- a/packages/wallet/primitives-cli/src/subcommands/signature.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { Config, Signature } from '@0xsequence/wallet-primitives' -import { Address, Bytes, Hex, Signature as OxSignature } from 'ox' -import { type CommandModule } from 'yargs' -import { fromPosOrStdin } from '../utils.js' -import { PossibleElements } from './config.js' - -// const SignatureElements = [ -// { -// type: 'eth_sign', -// format: '
:eth_sign:::', -// description: 'An eth_sign signature', -// }, -// { -// type: 'hash', -// format: '
:hash:::', -// description: 'A hash signature', -// }, -// { -// type: 'erc1271', -// format: '
:erc1271:', -// description: 'An erc1271 signature', -// }, -// { -// type: 'sapient', -// format: '
:sapient:', -// description: 'A sapient signature', -// }, -// { -// type: 'sapient_compact', -// format: '
:sapient_compact:', -// description: 'A sapient compact signature', -// }, -// ] - -export async function doEncode( - input: string, - signatures: string[] = [], - noChainId: boolean, - checkpointerData?: string, -): Promise { - const config = Config.configFromJson(input) - - const allSignatures = signatures.filter(Boolean).map((s) => { - const values = s.split(':') - return { - address: Address.from(values[0] as `0x${string}`), - type: values[1], - values: values.slice(2), - } - }) - - const fullTopology = Signature.fillLeaves(config.topology, (leaf) => { - if (Config.isSignerLeaf(leaf)) { - // Type must be 1271, eth_sign, or hash - const candidate = allSignatures.find((s) => Address.isEqual(s.address, leaf.address)) - - if (!candidate) { - return undefined - } - - if (candidate.type === 'erc1271') { - return { - address: candidate.address as `0x${string}`, - data: candidate.values[0] as `0x${string}`, - type: 'erc1271', - } - } - - if (candidate.type === 'eth_sign') { - return { - r: Bytes.toBigInt(Bytes.fromHex(candidate.values[0] as `0x${string}`, { size: 32 })), - s: Bytes.toBigInt(Bytes.fromHex(candidate.values[1] as `0x${string}`, { size: 32 })), - yParity: OxSignature.vToYParity(Number(candidate.values[2])), - type: 'eth_sign', - } - } - - if (candidate.type === 'hash') { - return { - r: Bytes.toBigInt(Bytes.fromHex(candidate.values[0] as `0x${string}`, { size: 32 })), - s: Bytes.toBigInt(Bytes.fromHex(candidate.values[1] as `0x${string}`, { size: 32 })), - yParity: OxSignature.vToYParity(Number(candidate.values[2])), - type: 'hash', - } - } - - if (candidate.type === 'sapient' || candidate.type === 'sapient_compact') { - throw new Error(`Incorrect type for leaf: ${leaf.type}`) - } - - throw new Error(`Unsupported signature type: ${candidate.type}`) - } - - if (Config.isSapientSignerLeaf(leaf)) { - const candidate = allSignatures.find((s) => Address.isEqual(s.address, leaf.address)) - if (!candidate) { - return undefined - } - - if (candidate.type === 'sapient' || candidate.type === 'sapient_compact') { - return { - address: candidate.address as `0x${string}`, - data: candidate.values[0] as `0x${string}`, - type: candidate.type, - } - } - - if (candidate.type === 'eth_sign' || candidate.type === 'hash' || candidate.type === 'erc1271') { - throw new Error(`Incorrect type for leaf: ${leaf.type}`) - } - - throw new Error(`Unsupported signature type: ${candidate.type}`) - } - - return undefined - }) - - const encoded = Signature.encodeSignature({ - noChainId, - configuration: { ...config, topology: fullTopology }, - checkpointerData: checkpointerData ? Bytes.fromHex(checkpointerData as `0x${string}`) : undefined, - }) - - return Hex.fromBytes(encoded) -} - -export async function doConcat(signatures: string[]): Promise { - if (signatures.length === 0) { - throw new Error('No signatures provided') - } - - const decoded = signatures.map((s) => Signature.decodeSignature(Bytes.fromHex(s as `0x${string}`))) - - const reEncoded = Signature.encodeSignature({ - ...decoded[0]!, - suffix: decoded.slice(1), - }) - - return Hex.fromBytes(reEncoded) -} - -export async function doDecode(signature: string): Promise { - const bytes = Bytes.fromHex(signature as `0x${string}`) - const decoded = Signature.decodeSignature(bytes) - return Signature.rawSignatureToJson(decoded) -} - -const signatureCommand: CommandModule = { - command: 'signature', - describe: 'Signature utilities', - builder: (yargs) => { - return yargs - .command( - 'encode [input]', - 'Encode signature from hex input', - (yargs) => { - return yargs - .option('signature', { - type: 'string', - array: true, - description: - 'A signature to include in the encoded signature, one of:\n' + - PossibleElements.map((e) => `- ${e.format}`).join('\n'), - demandOption: false, - alias: 's', - }) - .option('chain-id', { - type: 'boolean', - description: 'Use chainId of recovered chain on signature', - demandOption: false, - default: true, - }) - .option('checkpointer-data', { - type: 'string', - description: 'Checkpointer data in hex format', - demandOption: false, - }) - .positional('input', { - type: 'string', - description: 'Hex input to encode (if not using pipe)', - }) - }, - async (argv) => { - const input = await fromPosOrStdin(argv, 'input') - console.log(await doEncode(input, argv.signature, !argv.chainId, argv.checkpointerData)) - }, - ) - .command( - 'concat [signatures...]', - 'Concatenate multiple signatures', - (yargs) => { - return yargs.positional('signatures', { - type: 'string', - array: true, - description: 'Hex signatures to concatenate', - demandOption: true, - }) - }, - async (argv) => { - console.log(await doConcat(argv.signatures)) - }, - ) - .command( - 'decode [signature]', - 'Decode a signature from bytes', - (yargs) => { - return yargs.positional('signature', { - type: 'string', - description: 'Hex signature to decode', - demandOption: true, - }) - }, - async (argv) => { - const input = await fromPosOrStdin(argv, 'signature') - console.log(await doDecode(input)) - }, - ) - .demandCommand(1, 'You must specify a subcommand for signature') - }, - handler: () => {}, -} - -export default signatureCommand diff --git a/packages/wallet/primitives-cli/src/utils.ts b/packages/wallet/primitives-cli/src/utils.ts deleted file mode 100644 index d4bc27ab58..0000000000 --- a/packages/wallet/primitives-cli/src/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Arguments } from 'yargs' - -export async function readStdin(): Promise { - return new Promise((resolve, reject) => { - let data = '' - process.stdin.on('data', (chunk) => { - data += chunk - }) - process.stdin.on('end', () => { - resolve(data.trim()) - }) - process.stdin.on('error', (err) => { - reject(err) - }) - }) -} -export async function fromPosOrStdin(argv: Arguments, arg: keyof T): Promise { - const argValue = String(argv[arg]) - const hasArg = typeof argv[arg] === 'string' && argValue.length > 0 - - if (hasArg) { - return argValue - } - - const hasStdin = !process.stdin.isTTY - if (!hasStdin) { - throw new Error(`No ${String(arg)} provided and no stdin data`) - } - - return await readStdin() -} - -export function requireString(arg: string | undefined, name: string): asserts arg is string { - if (!arg) { - throw new Error(`${name} is required`) - } -} diff --git a/packages/wallet/primitives-cli/tsconfig.json b/packages/wallet/primitives-cli/tsconfig.json deleted file mode 100644 index 1e325a596c..0000000000 --- a/packages/wallet/primitives-cli/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "sourceMap": true - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/wallet/primitives/CHANGELOG.md b/packages/wallet/primitives/CHANGELOG.md deleted file mode 100644 index 7e5a4cdbf7..0000000000 --- a/packages/wallet/primitives/CHANGELOG.md +++ /dev/null @@ -1,170 +0,0 @@ -# @0xsequence/wallet-primitives - -## 3.0.5 - -### Patch Changes - -- Account federation support - -## 3.0.4 - -### Patch Changes - -- id-token login support - -## 3.0.3 - -### Patch Changes - -- 3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer - -## 3.0.1 - -### Patch Changes - -- Network and session fixes - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 diff --git a/packages/wallet/primitives/eslint.config.js b/packages/wallet/primitives/eslint.config.js deleted file mode 100644 index d10bbd1e97..0000000000 --- a/packages/wallet/primitives/eslint.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { config as baseConfig } from '@repo/eslint-config/base' - -/** @type {import("eslint").Linter.Config} */ -export default [ - ...baseConfig, - { - // files: ['**/*.{test,spec}.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - }, -] diff --git a/packages/wallet/primitives/package.json b/packages/wallet/primitives/package.json deleted file mode 100644 index e0e283de61..0000000000 --- a/packages/wallet/primitives/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@0xsequence/wallet-primitives", - "version": "3.0.5", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run", - "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@vitest/coverage-v8": "^4.0.18", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "ox": "^0.9.17" - } -} diff --git a/packages/wallet/primitives/src/address.ts b/packages/wallet/primitives/src/address.ts deleted file mode 100644 index 4de08a39d9..0000000000 --- a/packages/wallet/primitives/src/address.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Address, Bytes, Hash } from 'ox' -import { Context } from './context.js' -import { Config, hashConfiguration } from './config.js' - -export function from(configuration: Bytes.Bytes | Config, context: Omit): Address.Address { - const imageHash = configuration instanceof Uint8Array ? configuration : hashConfiguration(configuration) - - return Bytes.toHex( - Hash.keccak256( - Bytes.concat( - Bytes.from('0xff'), - Bytes.from(context.factory), - imageHash, - Hash.keccak256(Bytes.concat(Bytes.from(context.creationCode), Bytes.padLeft(Bytes.from(context.stage1), 32))), - ), - { as: 'Bytes' }, - ).subarray(12), - ) -} diff --git a/packages/wallet/primitives/src/attestation.ts b/packages/wallet/primitives/src/attestation.ts deleted file mode 100644 index 4f576f7a0b..0000000000 --- a/packages/wallet/primitives/src/attestation.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Address, Bytes, Hash, Hex } from 'ox' - -export type Attestation = { - approvedSigner: Address.Address - identityType: Bytes.Bytes // bytes4 - issuerHash: Bytes.Bytes // bytes32 - audienceHash: Bytes.Bytes // bytes32 - applicationData: Bytes.Bytes // bytes - authData: AuthData -} - -export type AuthData = { - redirectUrl: string // bytes - issuedAt: bigint // uint64 -} - -type EncodedAttestation = { - approvedSigner: Address.Address - identityType: Hex.Hex - issuerHash: Hex.Hex - audienceHash: Hex.Hex - applicationData: Hex.Hex - authData: { - redirectUrl: string - issuedAt: string - } -} - -// Encoding and decoding - -export function encode(attestation: Attestation): Bytes.Bytes { - const authDataBytes = encodeAuthData(attestation.authData) - const parts: Bytes.Bytes[] = [ - Bytes.fromHex(attestation.approvedSigner, { size: 20 }), - Bytes.padLeft(attestation.identityType.slice(0, 4), 4), // Truncate identity type to 4 bytes - Bytes.padLeft(attestation.issuerHash, 32), - Bytes.padLeft(attestation.audienceHash, 32), - Bytes.fromNumber(attestation.applicationData.length, { size: 3 }), - attestation.applicationData, - authDataBytes, - ] - return Bytes.concat(...parts) -} - -export function encodeAuthData(authData: AuthData): Bytes.Bytes { - return Bytes.concat( - Bytes.fromNumber(authData.redirectUrl.length, { size: 3 }), - Bytes.fromString(authData.redirectUrl), - Bytes.fromNumber(authData.issuedAt, { size: 8 }), - ) -} - -export function decode(bytes: Bytes.Bytes): Attestation { - const approvedSigner = Bytes.toHex(bytes.slice(0, 20)) - const identityType = bytes.slice(20, 24) - const issuerHash = bytes.slice(24, 56) - const audienceHash = bytes.slice(56, 88) - const applicationDataLength = Bytes.toNumber(bytes.slice(88, 91)) - const applicationData = bytes.slice(91, 91 + applicationDataLength) - const authData = decodeAuthData(bytes.slice(91 + applicationDataLength)) - - return { - approvedSigner, - identityType, - issuerHash, - audienceHash, - applicationData, - authData, - } -} - -export function decodeAuthData(bytes: Bytes.Bytes): AuthData { - const redirectUrlLength = Bytes.toNumber(bytes.slice(0, 3)) - const redirectUrl = Bytes.toString(bytes.slice(3, 3 + redirectUrlLength)) - const issuedAt = Bytes.toBigInt(bytes.slice(3 + redirectUrlLength, 3 + redirectUrlLength + 8)) - - return { - redirectUrl, - issuedAt, - } -} - -export function hash(attestation: Attestation): Bytes.Bytes { - return Hash.keccak256(encode(attestation)) -} - -export function toJson(attestation: Attestation, indent?: number): string { - return JSON.stringify(encodeForJson(attestation), null, indent) -} - -export function encodeForJson(attestation: Attestation): EncodedAttestation { - return { - approvedSigner: attestation.approvedSigner, - identityType: Bytes.toHex(attestation.identityType), - issuerHash: Bytes.toHex(attestation.issuerHash), - audienceHash: Bytes.toHex(attestation.audienceHash), - applicationData: Bytes.toHex(attestation.applicationData), - authData: { - redirectUrl: attestation.authData.redirectUrl, - issuedAt: attestation.authData.issuedAt.toString(), - }, - } -} - -export function fromJson(json: string): Attestation { - return fromParsed(JSON.parse(json)) -} - -export function fromParsed(parsed: EncodedAttestation): Attestation { - return { - approvedSigner: Address.from(parsed.approvedSigner), - identityType: Bytes.fromHex(parsed.identityType), - issuerHash: Bytes.fromHex(parsed.issuerHash), - audienceHash: Bytes.fromHex(parsed.audienceHash), - applicationData: Bytes.fromHex(parsed.applicationData), - authData: { - redirectUrl: parsed.authData.redirectUrl, - issuedAt: BigInt(parsed.authData.issuedAt), - }, - } -} - -// Library functions - -export const ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX = Hash.keccak256(Bytes.fromString('acceptImplicitRequest')) - -export function generateImplicitRequestMagic(attestation: Attestation, wallet: Address.Address): Bytes.Bytes { - return Hash.keccak256( - Bytes.concat( - ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX, - Bytes.fromHex(wallet, { size: 20 }), - attestation.audienceHash, - attestation.issuerHash, - ), - ) -} diff --git a/packages/wallet/primitives/src/config.ts b/packages/wallet/primitives/src/config.ts deleted file mode 100644 index 20c54dc494..0000000000 --- a/packages/wallet/primitives/src/config.ts +++ /dev/null @@ -1,707 +0,0 @@ -import { Address, Bytes, Hash, Hex } from 'ox' -import { - isRawConfig, - isRawNestedLeaf, - isRawSignerLeaf, - isSignedSapientSignerLeaf, - isSignedSignerLeaf, - RawConfig, - RawTopology, - SignatureOfSapientSignerLeaf, - SignatureOfSignerLeaf, -} from './signature.js' -import { Constants } from './index.js' - -export type SignerLeaf = { - type: 'signer' - address: Address.Address - weight: bigint - signed?: boolean - signature?: SignatureOfSignerLeaf -} - -export type SapientSignerLeaf = { - type: 'sapient-signer' - address: Address.Address - weight: bigint - imageHash: Hex.Hex - signed?: boolean - signature?: SignatureOfSapientSignerLeaf -} - -export type SubdigestLeaf = { - type: 'subdigest' - digest: Hex.Hex -} - -export type AnyAddressSubdigestLeaf = { - type: 'any-address-subdigest' - digest: Hex.Hex -} - -export type NestedLeaf = { - type: 'nested' - tree: Topology - weight: bigint - threshold: bigint -} - -export type NodeLeaf = Hex.Hex - -export type Node = [Topology, Topology] - -export type Leaf = SignerLeaf | SapientSignerLeaf | SubdigestLeaf | AnyAddressSubdigestLeaf | NestedLeaf | NodeLeaf - -export type Topology = Node | Leaf - -/** Encoded topology types for JSON serialization */ -type EncodedSignerLeaf = { - type: 'signer' - address: Address.Address - weight: string -} - -type EncodedSapientSignerLeaf = { - type: 'sapient-signer' - address: Address.Address - weight: string - imageHash: Hex.Hex -} - -type EncodedSubdigestLeaf = { - type: 'subdigest' - digest: Hex.Hex -} - -type EncodedAnyAddressSubdigestLeaf = { - type: 'any-address-subdigest' - digest: Hex.Hex -} - -type EncodedNestedLeaf = { - type: 'nested' - tree: EncodedTopology - weight: string - threshold: string -} - -type EncodedNodeLeaf = Hex.Hex - -export type EncodedLeaf = - | EncodedSignerLeaf - | EncodedSapientSignerLeaf - | EncodedSubdigestLeaf - | EncodedAnyAddressSubdigestLeaf - | EncodedNestedLeaf - | EncodedNodeLeaf -export type EncodedNode = [EncodedTopology, EncodedTopology] -export type EncodedTopology = EncodedNode | EncodedLeaf - -export type Config = { - threshold: bigint - checkpoint: bigint - topology: Topology - checkpointer?: Address.Address -} - -export function isSignerLeaf(cand: unknown): cand is SignerLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'signer' -} - -export function isSapientSignerLeaf(cand: unknown): cand is SapientSignerLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'sapient-signer' -} - -export function isSubdigestLeaf(cand: unknown): cand is SubdigestLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'subdigest' -} - -export function isAnyAddressSubdigestLeaf(cand: unknown): cand is AnyAddressSubdigestLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'any-address-subdigest' -} - -export function isNodeLeaf(cand: unknown): cand is NodeLeaf { - return typeof cand === 'string' && Hex.validate(cand) && cand.length === 66 -} - -export function isNestedLeaf(cand: unknown): cand is NestedLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'nested' -} - -export function isNode(cand: unknown): cand is Node { - return Array.isArray(cand) && cand.length === 2 && isTopology(cand[0]) && isTopology(cand[1]) -} - -export function isConfig(cand: unknown): cand is Config { - return typeof cand === 'object' && cand !== null && 'threshold' in cand && 'checkpoint' in cand && 'topology' in cand -} - -export function isLeaf(cand: unknown): cand is Leaf { - return ( - isSignerLeaf(cand) || - isSapientSignerLeaf(cand) || - isSubdigestLeaf(cand) || - isAnyAddressSubdigestLeaf(cand) || - isNodeLeaf(cand) || - isNestedLeaf(cand) - ) -} - -export function isTopology(cand: unknown): cand is Topology { - return isNode(cand) || isLeaf(cand) -} - -export function getSigners(configuration: Config | Topology): { - signers: Address.Address[] - sapientSigners: { address: Address.Address; imageHash: Hex.Hex }[] - isComplete: boolean -} { - const signers = new Set() - const sapientSigners = new Set<{ address: Address.Address; imageHash: Hex.Hex }>() - - let isComplete = true - - const scan = (topology: Topology) => { - if (isNode(topology)) { - scan(topology[0]) - scan(topology[1]) - } else if (isSignerLeaf(topology)) { - if (topology.weight) { - signers.add(topology.address) - } - } else if (isSapientSignerLeaf(topology)) { - sapientSigners.add({ address: topology.address, imageHash: topology.imageHash }) - } else if (isNodeLeaf(topology)) { - isComplete = false - } else if (isNestedLeaf(topology)) { - if (topology.weight) { - scan(topology.tree) - } - } - } - - scan(isConfig(configuration) ? configuration.topology : configuration) - return { signers: Array.from(signers), sapientSigners: Array.from(sapientSigners), isComplete } -} - -export function findSignerLeaf( - configuration: Config | Topology, - address: Address.Address, -): SignerLeaf | SapientSignerLeaf | undefined { - if (isConfig(configuration)) { - return findSignerLeaf(configuration.topology, address) - } else if (isNode(configuration)) { - return findSignerLeaf(configuration[0], address) || findSignerLeaf(configuration[1], address) - } else if (isSignerLeaf(configuration)) { - if (Address.isEqual(configuration.address, address)) { - return configuration - } - } else if (isSapientSignerLeaf(configuration)) { - if (Address.isEqual(configuration.address, address)) { - return configuration - } - } else if (isNestedLeaf(configuration)) { - return findSignerLeaf(configuration.tree, address) - } - return undefined -} - -export function getWeight( - topology: RawTopology | RawConfig | Config, - canSign: (signer: SignerLeaf | SapientSignerLeaf) => boolean, -): { weight: bigint; maxWeight: bigint } { - topology = isRawConfig(topology) || isConfig(topology) ? topology.topology : topology - - if (isSignedSignerLeaf(topology)) { - return { weight: topology.weight, maxWeight: topology.weight } - } else if (isSignerLeaf(topology)) { - return { weight: 0n, maxWeight: canSign(topology) ? topology.weight : 0n } - } else if (isRawSignerLeaf(topology)) { - return { weight: topology.weight, maxWeight: topology.weight } - } else if (isSignedSapientSignerLeaf(topology)) { - return { weight: topology.weight, maxWeight: topology.weight } - } else if (isSapientSignerLeaf(topology)) { - return { weight: 0n, maxWeight: canSign(topology) ? topology.weight : 0n } - } else if (isSubdigestLeaf(topology)) { - return { weight: 0n, maxWeight: 0n } - } else if (isAnyAddressSubdigestLeaf(topology)) { - return { weight: 0n, maxWeight: 0n } - } else if (isRawNestedLeaf(topology)) { - const { weight, maxWeight } = getWeight(topology.tree, canSign) - return { - weight: weight >= topology.threshold ? topology.weight : 0n, - maxWeight: maxWeight >= topology.threshold ? topology.weight : 0n, - } - } else if (isNodeLeaf(topology)) { - return { weight: 0n, maxWeight: 0n } - } else { - const [left, right] = [getWeight(topology[0], canSign), getWeight(topology[1], canSign)] - return { weight: left.weight + right.weight, maxWeight: left.maxWeight + right.maxWeight } - } -} - -export function hashConfiguration(topology: Topology | Config): Bytes.Bytes { - if (isConfig(topology)) { - let root = hashConfiguration(topology.topology) - root = Hash.keccak256(Bytes.concat(root, Bytes.padLeft(Bytes.fromNumber(topology.threshold), 32))) - root = Hash.keccak256(Bytes.concat(root, Bytes.padLeft(Bytes.fromNumber(topology.checkpoint), 32))) - root = Hash.keccak256( - Bytes.concat(root, Bytes.padLeft(Bytes.fromHex(topology.checkpointer ?? Constants.ZeroAddress), 32)), - ) - return root - } - - if (isSignerLeaf(topology)) { - return Hash.keccak256( - Bytes.concat( - Bytes.fromString('Sequence signer:\n'), - Bytes.fromHex(topology.address), - Bytes.padLeft(Bytes.fromNumber(topology.weight), 32), - ), - ) - } - - if (isSapientSignerLeaf(topology)) { - return Hash.keccak256( - Bytes.concat( - Bytes.fromString('Sequence sapient config:\n'), - Bytes.fromHex(topology.address), - Bytes.padLeft(Bytes.fromNumber(topology.weight), 32), - Bytes.padLeft(Bytes.fromHex(topology.imageHash), 32), - ), - ) - } - - if (isSubdigestLeaf(topology)) { - return Hash.keccak256(Bytes.concat(Bytes.fromString('Sequence static digest:\n'), Bytes.fromHex(topology.digest))) - } - - if (isAnyAddressSubdigestLeaf(topology)) { - return Hash.keccak256( - Bytes.concat(Bytes.fromString('Sequence any address subdigest:\n'), Bytes.fromHex(topology.digest)), - ) - } - - if (isNodeLeaf(topology)) { - return Bytes.fromHex(topology) - } - - if (isNestedLeaf(topology)) { - return Hash.keccak256( - Bytes.concat( - Bytes.fromString('Sequence nested config:\n'), - hashConfiguration(topology.tree), - Bytes.padLeft(Bytes.fromNumber(topology.threshold), 32), - Bytes.padLeft(Bytes.fromNumber(topology.weight), 32), - ), - ) - } - - if (isNode(topology)) { - return Hash.keccak256(Bytes.concat(hashConfiguration(topology[0]), hashConfiguration(topology[1]))) - } - - throw new Error('Invalid topology') -} - -export function flatLeavesToTopology(leaves: Leaf[]): Topology { - if (leaves.length === 0) { - throw new Error('Cannot create topology from empty leaves') - } - - if (leaves.length === 1) { - return leaves[0]! - } - - if (leaves.length === 2) { - return [leaves[0]!, leaves[1]!] - } - - return [ - flatLeavesToTopology(leaves.slice(0, leaves.length / 2)), - flatLeavesToTopology(leaves.slice(leaves.length / 2)), - ] -} - -export function topologyToFlatLeaves(topology: Topology): Leaf[] { - if (isNode(topology)) { - return [...topologyToFlatLeaves(topology[0]), ...topologyToFlatLeaves(topology[1])] - } - if (isNestedLeaf(topology)) { - return [...topologyToFlatLeaves(topology.tree)] - } - return [topology] -} - -export function configToJson(config: Config): string { - return JSON.stringify({ - threshold: config.threshold.toString(), - checkpoint: config.checkpoint.toString(), - topology: encodeTopology(config.topology), - checkpointer: config.checkpointer, - }) -} - -export function configFromJson(json: string): Config { - const parsed = JSON.parse(json) - return { - threshold: BigInt(parsed.threshold), - checkpoint: BigInt(parsed.checkpoint), - checkpointer: parsed.checkpointer, - topology: decodeTopology(parsed.topology), - } -} - -function encodeTopology(top: Topology): EncodedTopology { - if (isNode(top)) { - return [encodeTopology(top[0]), encodeTopology(top[1])] - } else if (isSignerLeaf(top)) { - return { - type: 'signer', - address: top.address, - weight: top.weight.toString(), - } - } else if (isSapientSignerLeaf(top)) { - return { - type: 'sapient-signer', - address: top.address, - weight: top.weight.toString(), - imageHash: top.imageHash, - } - } else if (isSubdigestLeaf(top)) { - return { - type: 'subdigest', - digest: top.digest, - } - } else if (isAnyAddressSubdigestLeaf(top)) { - return { - type: 'any-address-subdigest', - digest: top.digest, - } - } else if (isNodeLeaf(top)) { - return top - } else if (isNestedLeaf(top)) { - return { - type: 'nested', - tree: encodeTopology(top.tree), - weight: top.weight.toString(), - threshold: top.threshold.toString(), - } - } - - throw new Error('Invalid topology') -} - -function decodeTopology(obj: EncodedTopology): Topology { - if (Array.isArray(obj)) { - if (obj.length !== 2) { - throw new Error('Invalid node structure in JSON') - } - return [decodeTopology(obj[0]), decodeTopology(obj[1])] - } - - if (typeof obj === 'string') { - return obj as Hex.Hex - } - - switch (obj.type) { - case 'signer': - return { - type: 'signer', - address: obj.address, - weight: BigInt(obj.weight), - } - case 'sapient-signer': - return { - type: 'sapient-signer', - address: obj.address, - weight: BigInt(obj.weight), - imageHash: obj.imageHash, - } - case 'subdigest': - return { - type: 'subdigest', - digest: obj.digest, - } - case 'any-address-subdigest': - return { - type: 'any-address-subdigest', - digest: obj.digest, - } - case 'nested': - return { - type: 'nested', - tree: decodeTopology(obj.tree), - weight: BigInt(obj.weight), - threshold: BigInt(obj.threshold), - } - default: - throw new Error('Invalid type in topology JSON') - } -} - -export type SignerSignature = [T] extends [Promise] - ? never - : MaybePromise | { signature: Promise; onSignerSignature?: SignerSignatureCallback; onCancel?: CancelCallback } - -export function normalizeSignerSignature(signature: SignerSignature): { - signature: Promise - onSignerSignature?: SignerSignatureCallback - onCancel?: CancelCallback -} { - if (signature instanceof Promise) { - return { signature } - } else if ( - typeof signature === 'object' && - signature && - 'signature' in signature && - signature.signature instanceof Promise - ) { - return signature as ReturnType - } else { - return { signature: Promise.resolve(signature) as Promise } - } -} - -export type SignerErrorCallback = (signer: SignerLeaf | SapientSignerLeaf, error: unknown) => void - -type SignerSignatureCallback = (topology: RawTopology) => void -type CancelCallback = (success: boolean) => void -type MaybePromise = T | Promise - -export function mergeTopology(a: Topology, b: Topology): Topology { - if (isNode(a) && isNode(b)) { - return [mergeTopology(a[0], b[0]), mergeTopology(a[1], b[1])] - } - - if (isNode(a) && !isNode(b)) { - if (!isNodeLeaf(b)) { - throw new Error('Topology mismatch: cannot merge node with non-node that is not a node leaf') - } - const hb = hashConfiguration(b) - if (!Bytes.isEqual(hb, hashConfiguration(a))) { - throw new Error('Topology mismatch: node hash does not match') - } - return a - } - - if (!isNode(a) && isNode(b)) { - if (!isNodeLeaf(a)) { - throw new Error('Topology mismatch: cannot merge node with non-node that is not a node leaf') - } - const ha = hashConfiguration(a) - if (!Bytes.isEqual(ha, hashConfiguration(b))) { - throw new Error('Topology mismatch: node hash does not match') - } - return b - } - - return mergeLeaf(a as Leaf, b as Leaf) -} - -/** - * Checks if a wallet topology or config has any values that are too large. - * - * Recursively checks: - * - threshold (max 65535) - * - checkpoint (max 72057594037927935) - * - weight (max 255) - * If any value is too large, or a nested part is invalid, returns true. - * - * @param topology - The wallet topology or config to check. - * @returns True if any value is invalid, otherwise false. - */ -export function hasInvalidValues(topology: Topology | Config): boolean { - if (isConfig(topology)) { - return ( - topology.threshold > 65535n || topology.checkpoint > 72057594037927935n || hasInvalidValues(topology.topology) - ) - } - - if (isNode(topology)) { - return hasInvalidValues(topology[0]) || hasInvalidValues(topology[1]) - } - - if (isNestedLeaf(topology)) { - return hasInvalidValues(topology.tree) || topology.weight > 255n || topology.threshold > 65535n - } - - if (isSignerLeaf(topology) || isSapientSignerLeaf(topology)) { - return topology.weight > 255n - } - - return false -} - -/** - * Calculates the maximum depth of a wallet topology tree. - * - * The depth is defined as the longest path from the root node to any leaf node. - * - * @param topology - The wallet topology to evaluate. - * @returns The maximum depth of the topology tree. - */ -export function maximumDepth(topology: Topology): number { - if (isNode(topology)) { - return Math.max(maximumDepth(topology[0]), maximumDepth(topology[1])) + 1 - } - - if (isNestedLeaf(topology)) { - return maximumDepth(topology.tree) + 1 - } - - return 0 -} - -/** - * Evaluates the safety of a wallet configuration. - * - * This function checks for several potential security issues: - * 1. Zero threshold - would allow anyone to send transactions - * 2. Excessive tree depth - could cause issues with contract execution - * 3. Unreachable threshold - would make it impossible to sign transactions - * 4. Invalid values - would make it impossible to encode in a signature - * - * @param config The wallet configuration to evaluate - * @throws {Error} With code 'unsafe-threshold-0' if the threshold is zero - * @throws {Error} With code 'unsafe-depth' if the tree depth exceeds 32 - * @throws {Error} With code 'unsafe-threshold' if the threshold is higher than the maximum possible weight - * @throws {Error} With code 'unsafe-invalid-values' if the configuration has invalid values - */ -export function evaluateConfigurationSafety(config: Config) { - // If the configuration has a threshold of zero then anyone - // and send a transaction on the wallet - if (config.threshold === 0n) { - throw new Error('unsafe-threshold-0') - } - - // The configuration may have invalid values, that are not possible - // to encode in a signature - if (hasInvalidValues(config)) { - throw new Error('unsafe-invalid-values') - } - - // The contracts can safely handle trees up to a depth of 54 - // but we use 32 as a maximum depth to leave some safety margning - // as 32 should be more than enough for all use cases - if (maximumDepth(config.topology) > 32) { - throw new Error('unsafe-depth') - } - - // The threshold must be reachable, otherwise it would be - // impossible to sign any signatures using this configuration - const { maxWeight } = getWeight(config.topology, () => true) - if (maxWeight < config.threshold) { - throw new Error('unsafe-threshold') - } -} - -function mergeLeaf(a: Leaf, b: Leaf): Leaf { - if (isNodeLeaf(a) && isNodeLeaf(b)) { - if (!Hex.isEqual(a, b)) { - throw new Error('Topology mismatch: different node leaves') - } - return a - } - - if (isNodeLeaf(a) && !isNodeLeaf(b)) { - const hb = hashConfiguration(b) - if (!Bytes.isEqual(hb, Bytes.fromHex(a))) { - throw new Error('Topology mismatch: node leaf hash does not match') - } - return b - } - - if (!isNodeLeaf(a) && isNodeLeaf(b)) { - const ha = hashConfiguration(a) - if (!Bytes.isEqual(ha, Bytes.fromHex(b))) { - throw new Error('Topology mismatch: node leaf hash does not match') - } - return a - } - - if (isSignerLeaf(a) && isSignerLeaf(b)) { - if (a.address !== b.address || a.weight !== b.weight) { - throw new Error('Topology mismatch: signer fields differ') - } - if (!!a.signed !== !!b.signed || !!a.signature !== !!b.signature) { - throw new Error('Topology mismatch: signer signature fields differ') - } - return a - } - - if (isSapientSignerLeaf(a) && isSapientSignerLeaf(b)) { - if (a.address !== b.address || a.weight !== b.weight || a.imageHash !== b.imageHash) { - throw new Error('Topology mismatch: sapient signer fields differ') - } - if (!!a.signed !== !!b.signed || !!a.signature !== !!b.signature) { - throw new Error('Topology mismatch: sapient signature fields differ') - } - return a - } - - if (isSubdigestLeaf(a) && isSubdigestLeaf(b)) { - if (!Bytes.isEqual(Bytes.fromHex(a.digest), Bytes.fromHex(b.digest))) { - throw new Error('Topology mismatch: subdigest fields differ') - } - return a - } - - if (isAnyAddressSubdigestLeaf(a) && isAnyAddressSubdigestLeaf(b)) { - if (!Bytes.isEqual(Bytes.fromHex(a.digest), Bytes.fromHex(b.digest))) { - throw new Error('Topology mismatch: any-address-subdigest fields differ') - } - return a - } - - if (isNestedLeaf(a) && isNestedLeaf(b)) { - if (a.weight !== b.weight || a.threshold !== b.threshold) { - throw new Error('Topology mismatch: nested leaf fields differ') - } - const mergedTree = mergeTopology(a.tree, b.tree) - return { - type: 'nested', - weight: a.weight, - threshold: a.threshold, - tree: mergedTree, - } - } - - throw new Error('Topology mismatch: incompatible leaf types') -} - -export function replaceAddress( - topology: Topology, - targetAddress: Address.Address, - replacementAddress: Address.Address, -): Topology { - // 1. Handle Branches/Nodes (Recursion) - if (isNode(topology)) { - return [ - replaceAddress(topology[0], targetAddress, replacementAddress), - replaceAddress(topology[1], targetAddress, replacementAddress), - ] - } - - // 2. Handle Nested Leaves (Recursion) - if (isNestedLeaf(topology)) { - return { - ...topology, - tree: replaceAddress(topology.tree, targetAddress, replacementAddress), - } - } - - // 3. Handle Leaves (Replacement) - if (isSignerLeaf(topology) || isSapientSignerLeaf(topology)) { - // If this leaf holds the placeholder address, swap it - if (Address.isEqual(topology.address, targetAddress)) { - return { - ...topology, - address: replacementAddress, - } - } - } - - // 4. Return other leaf types unchanged (Subdigest, NodeLeaf, etc.) - return topology -} diff --git a/packages/wallet/primitives/src/constants.ts b/packages/wallet/primitives/src/constants.ts deleted file mode 100644 index 763f94389e..0000000000 --- a/packages/wallet/primitives/src/constants.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Abi } from 'ox' - -export const ZeroAddress = '0x0000000000000000000000000000000000000000' as const -export const PlaceholderAddress = '0xffff0000ffff0000ffff0000ffff0000ffff0000' as const - -export const DefaultGuestAddress = '0x0000000000006Ac72ed1d192fa28f0058D3F8806' as const - -// ERC1271 -export const IS_VALID_SIGNATURE = Abi.from([ - 'function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue)', -])[0] - -// Factory -export const DEPLOY = Abi.from([ - 'function deploy(address _mainModule, bytes32 _salt) public payable returns (address _contract)', -])[0] - -// Stage1Module -export const GET_IMPLEMENTATION = Abi.from(['function getImplementation() external view returns (address)'])[0] - -// Stage2Module -export const IMAGE_HASH = Abi.from(['function imageHash() external view returns (bytes32)'])[0] -export const READ_NONCE = Abi.from(['function readNonce(uint256 _space) public view returns (uint256)'])[0] -export const EXECUTE = Abi.from(['function execute(bytes calldata _payload, bytes calldata _signature) external'])[0] -export const UPDATE_IMAGE_HASH = Abi.from(['function updateImageHash(bytes32 _imageHash) external'])[0] - -// Sapient -export const RECOVER_SAPIENT_SIGNATURE = Abi.from([ - 'function recoverSapientSignature((uint8 kind,bool noChainId,(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)[] calls,uint256 space,uint256 nonce,bytes message,bytes32 imageHash,bytes32 digest,address[] parentWallets) calldata _payload, bytes calldata _signature) external view returns (bytes32)', -])[0] - -// SapientCompact -export const RECOVER_SAPIENT_SIGNATURE_COMPACT = Abi.from([ - 'function recoverSapientSignatureCompact(bytes32 _digest, bytes calldata _signature) external view returns (bytes32)', -])[0] - -// ERC4337 -export const EXECUTE_USER_OP = Abi.from(['function executeUserOp(bytes calldata _userOp) external'])[0] -export const READ_NONCE_4337 = Abi.from([ - 'function getNonce(address _account, uint192 _key) public view returns (uint256)', -])[0] -export const READ_ENTRYPOINT = Abi.from(['function entrypoint() public view returns (address)'])[0] - -// SessionManager -export const INCREMENT_USAGE_LIMIT = Abi.from([ - { - type: 'function', - name: 'incrementUsageLimit', - inputs: [ - { - name: 'limits', - type: 'tuple[]', - internalType: 'struct UsageLimit[]', - components: [ - { name: 'usageHash', type: 'bytes32', internalType: 'bytes32' }, - { name: 'usageAmount', type: 'uint256', internalType: 'uint256' }, - ], - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, -])[0] -export const GET_LIMIT_USAGE = Abi.from([ - 'function getLimitUsage(address wallet, bytes32 usageHash) public view returns (uint256)', -])[0] diff --git a/packages/wallet/primitives/src/context.ts b/packages/wallet/primitives/src/context.ts deleted file mode 100644 index fa70f8e3ac..0000000000 --- a/packages/wallet/primitives/src/context.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Address, Hex } from 'ox' - -export type Capabilities = { - erc4337?: { - entrypoint: Address.Address - } -} - -export type Context = { - factory: Address.Address - stage1: Address.Address - stage2: Address.Address - creationCode: Hex.Hex - capabilities?: Capabilities -} - -export const Dev1: Context = { - factory: '0xe828630697817291140D6B7A42a2c3b7277bE45a', - stage1: '0x2a4fB19F66F1427A5E363Bf1bB3be27b9A9ACC39', - stage2: '0xe1299E4456b267123F7Aba29B72C2164ff501BDa', - creationCode: '0x603e600e3d39601e805130553df33d3d34601c57363d3d373d363d30545af43d82803e903d91601c57fd5bf3', -} - -export const Dev2: Context = { - factory: '0xFE14B91dE3c5Ca74c4D24608EBcD4B2848aA6010', - stage1: '0x300E98ae5bEA4A7291d62Eb0b9feD535E10095dD', - stage2: '0x90cb0a8ccf40bEdA60896e408bdc7801033447C6', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', -} - -export const Dev2_4337: Context = { - factory: '0xFE14B91dE3c5Ca74c4D24608EBcD4B2848aA6010', - stage1: '0x8Ae58FCc0Ee9b32994CA52c9854deb969DC8fa2A', - stage2: '0x30f8e3AceAcDEac8a3F28935D87FD58DC5f71ad2', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', - capabilities: { - erc4337: { - entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', - }, - }, -} - -export const Rc3: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x00000000000084fA81809Dd337311297C5594d62', - stage2: '0x7438718F9E4b9B834e305A620EEeCf2B9E6eBE79', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', -} - -export const Rc3_4337: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x0000000000005A02E3218e820EA45102F84A35C7', - stage2: '0x7706aaC0cc2C42C01CE17136F7475b0E46F2ABA1', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', - capabilities: { - erc4337: { - entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', - }, - }, -} - -export const Rc4: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x0000000000003DF093bc4257E6dCE45D937EF161', - stage2: '0x10bE1Abf3cD0918bb1079ECc6b8220c177F34088', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', -} - -export const Rc4_4337: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x0000000000003add039FF84b064B7347Fc23C444', - stage2: '0x4B3E5735665057A0A15eE448A7293bC01e3b4De9', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', - capabilities: { - erc4337: { - entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', - }, - }, -} - -export const Rc5: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x0000000000001f3C39d61698ab21131a12134454', - stage2: '0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', -} - -export const Rc5_4337: Context = { - factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', - stage1: '0x0000000000009caFdeDb6f64Bf5F31a22124B2a8', - stage2: '0xcBca3328a731deffE6Ce4c2fb51b585c3c37FB92', - creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', - capabilities: { - erc4337: { - entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', - }, - }, -} - -export type KnownContext = Context & { - name: string - development: boolean -} - -export const KnownContexts: KnownContext[] = [ - { name: 'Dev1', development: true, ...Dev1 }, - { name: 'Dev2', development: true, ...Dev2 }, - { name: 'Dev2_4337', development: true, ...Dev2_4337 }, - { name: 'Rc3', development: true, ...Rc3 }, - { name: 'Rc3_4337', development: true, ...Rc3_4337 }, - { name: 'Rc4', development: false, ...Rc4 }, - { name: 'Rc4_4337', development: false, ...Rc4_4337 }, - { name: 'Rc5', development: false, ...Rc5 }, - { name: 'Rc5_4337', development: false, ...Rc5_4337 }, -] - -export function isKnownContext(context: Context): context is KnownContext { - return (context as KnownContext).name !== undefined && (context as KnownContext).development !== undefined -} diff --git a/packages/wallet/primitives/src/erc-6492.ts b/packages/wallet/primitives/src/erc-6492.ts deleted file mode 100644 index 868350edff..0000000000 --- a/packages/wallet/primitives/src/erc-6492.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { AbiFunction, AbiParameters, Address, Bytes, Hex, Provider } from 'ox' -import { SignatureErc6492 } from 'ox/erc6492' -import { DEPLOY } from './constants.js' -import { Context } from './context.js' - -const EIP_6492_OFFCHAIN_DEPLOY_CODE = - '0x608060405234801561001057600080fd5b5060405161124a38038061124a83398101604081905261002f91610124565b600060405161003d906100dd565b604051809103906000f080158015610059573d6000803e3d6000fd5b5090506000816001600160a01b0316638f0684308686866040518463ffffffff1660e01b815260040161008e939291906101fb565b6020604051808303816000875af11580156100ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d19190610244565b9050806000526001601ff35b610fdc8061026e83390190565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561011b578181015183820152602001610103565b50506000910152565b60008060006060848603121561013957600080fd5b83516001600160a01b038116811461015057600080fd5b6020850151604086015191945092506001600160401b038082111561017457600080fd5b818601915086601f83011261018857600080fd5b81518181111561019a5761019a6100ea565b604051601f8201601f19908116603f011681019083821181831017156101c2576101c26100ea565b816040528281528960208487010111156101db57600080fd5b6101ec836020830160208801610100565b80955050505050509250925092565b60018060a01b0384168152826020820152606060408201526000825180606084015261022e816080850160208701610100565b601f01601f191691909101608001949350505050565b60006020828403121561025657600080fd5b8151801515811461026657600080fd5b939250505056fe608060405234801561001057600080fd5b50610fbc806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806376be4cea1161005057806376be4cea146100a65780638f068430146100b957806398ef1ed8146100cc57600080fd5b80631c6453271461006c5780633d787b6314610093575b600080fd5b61007f61007a366004610ad4565b6100df565b604051901515815260200160405180910390f35b61007f6100a1366004610ad4565b61023d565b61007f6100b4366004610b3e565b61031e565b61007f6100c7366004610ad4565b6108e1565b61007f6100da366004610ad4565b61096e565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061012890889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610181575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261017e91810190610c45565b60015b610232573d8080156101af576040519150601f19603f3d011682016040523d82523d6000602084013e6101b4565b606091505b508051600181900361022757816000815181106101d3576101d3610c69565b6020910101517fff00000000000000000000000000000000000000000000000000000000000000167f0100000000000000000000000000000000000000000000000000000000000000149250610235915050565b600092505050610235565b90505b949350505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906102879088908890889088906001908990600401610bc3565b6020604051808303816000875af19250505080156102e0575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526102dd91810190610c45565b60015b610232573d80801561030e576040519150601f19603f3d011682016040523d82523d6000602084013e610313565b606091505b506000915050610235565b600073ffffffffffffffffffffffffffffffffffffffff87163b6060827f64926492649264926492649264926492649264926492649264926492649264928888610369602082610c98565b610375928b9290610cd8565b61037e91610d02565b1490508015610484576000606089828a610399602082610c98565b926103a693929190610cd8565b8101906103b39190610e18565b955090925090508415806103c45750865b1561047d576000808373ffffffffffffffffffffffffffffffffffffffff16836040516103f19190610eb2565b6000604051808303816000865af19150503d806000811461042e576040519150601f19603f3d011682016040523d82523d6000602084013e610433565b606091505b50915091508161047a57806040517f9d0d6e2d0000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b60405180910390fd5b50505b50506104be565b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509294505050505b80806104ca5750600083115b156106bb576040517f1626ba7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b1690631626ba7e90610523908c908690600401610f2b565b602060405180830381865afa92505050801561057a575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261057791810190610f44565b60015b61060f573d8080156105a8576040519150601f19603f3d011682016040523d82523d6000602084013e6105ad565b606091505b50851580156105bc5750600084115b156105db576105d08b8b8b8b8b600161031e565b9450505050506108d7565b806040517f6f2a95990000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b7fffffffff0000000000000000000000000000000000000000000000000000000081167f1626ba7e000000000000000000000000000000000000000000000000000000001480158161065f575086155b801561066b5750600085115b1561068b5761067f8c8c8c8c8c600161031e565b955050505050506108d7565b841580156106965750825b80156106a0575087155b156106af57806000526001601ffd5b94506108d79350505050565b6041871461074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5369676e617475726556616c696461746f72237265636f7665725369676e657260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610471565b600061075a6020828a8c610cd8565b61076391610d02565b90506000610775604060208b8d610cd8565b61077e91610d02565b905060008a8a604081811061079557610795610c69565b919091013560f81c915050601b81148015906107b557508060ff16601c14155b15610842576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f5369676e617475726556616c696461746f723a20696e76616c6964207369676e60448201527f617475726520762076616c7565000000000000000000000000000000000000006064820152608401610471565b6040805160008152602081018083528e905260ff831691810191909152606081018490526080810183905273ffffffffffffffffffffffffffffffffffffffff8e169060019060a0016020604051602081039080840390855afa1580156108ad573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff161496505050505050505b9695505050505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061092b9088908890889088906001908990600401610bc3565b6020604051808303816000875af115801561094a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102329190610c45565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906109b790889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610a10575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610a0d91810190610c45565b60015b610232573d808015610a3e576040519150601f19603f3d011682016040523d82523d6000602084013e610a43565b606091505b5080516001819003610a6257816000815181106101d3576101d3610c69565b8082fd5b73ffffffffffffffffffffffffffffffffffffffff81168114610a8857600080fd5b50565b60008083601f840112610a9d57600080fd5b50813567ffffffffffffffff811115610ab557600080fd5b602083019150836020828501011115610acd57600080fd5b9250929050565b60008060008060608587031215610aea57600080fd5b8435610af581610a66565b935060208501359250604085013567ffffffffffffffff811115610b1857600080fd5b610b2487828801610a8b565b95989497509550505050565b8015158114610a8857600080fd5b60008060008060008060a08789031215610b5757600080fd5b8635610b6281610a66565b955060208701359450604087013567ffffffffffffffff811115610b8557600080fd5b610b9189828a01610a8b565b9095509350506060870135610ba581610b30565b91506080870135610bb581610b30565b809150509295509295509295565b73ffffffffffffffffffffffffffffffffffffffff8716815285602082015260a060408201528360a0820152838560c0830137600060c085830181019190915292151560608201529015156080820152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909101019392505050565b600060208284031215610c5757600080fd5b8151610c6281610b30565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b81810381811115610cd2577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b60008085851115610ce857600080fd5b83861115610cf557600080fd5b5050820193919092039150565b80356020831015610cd2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610d7e57600080fd5b813567ffffffffffffffff80821115610d9957610d99610d3e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715610ddf57610ddf610d3e565b81604052838152866020858801011115610df857600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215610e2d57600080fd5b8335610e3881610a66565b9250602084013567ffffffffffffffff80821115610e5557600080fd5b610e6187838801610d6d565b93506040860135915080821115610e7757600080fd5b50610e8486828701610d6d565b9150509250925092565b60005b83811015610ea9578181015183820152602001610e91565b50506000910152565b60008251610ec4818460208701610e8e565b9190910192915050565b60008151808452610ee6816020860160208601610e8e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c626020830184610ece565b8281526040602082015260006102356040830184610ece565b600060208284031215610f5657600080fd5b81517fffffffff0000000000000000000000000000000000000000000000000000000081168114610c6257600080fdfea26469706673582212201a72aed4b15ffb05b6502997a9bb655992e06590bd26b336dfbb153d7ff6f34b64736f6c63430008120033' - -export function deploy( - deployHash: T, - context: Context, -): { to: Address.Address; data: T } { - const encoded = AbiFunction.encodeData(DEPLOY, [context.stage1, Hex.from(deployHash)]) - - switch (typeof deployHash) { - case 'object': - return { to: context.factory, data: Hex.toBytes(encoded) as T } - case 'string': - return { to: context.factory, data: encoded as T } - } -} - -export function wrap( - signature: T, - { to, data }: { to: Address.Address; data: Bytes.Bytes | Hex.Hex }, -): T { - const encoded = Hex.concat( - AbiParameters.encode( - [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], - [to, Hex.from(data), Hex.from(signature)], - ), - SignatureErc6492.magicBytes, - ) - - switch (typeof signature) { - case 'object': - return Hex.toBytes(encoded) as T - case 'string': - return encoded as T - } -} - -export function decode( - signature: T, -): { signature: T; erc6492?: { to: Address.Address; data: T } } { - switch (typeof signature) { - case 'object': - if ( - Bytes.toHex(signature.subarray(-SignatureErc6492.magicBytes.slice(2).length / 2)) === - SignatureErc6492.magicBytes - ) { - const [to, data, decoded] = AbiParameters.decode( - [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], - signature.subarray(0, -SignatureErc6492.magicBytes.slice(2).length / 2), - ) - return { signature: Hex.toBytes(decoded) as T, erc6492: { to, data: Hex.toBytes(data) as T } } - } else { - return { signature } - } - - case 'string': - if (signature.endsWith(SignatureErc6492.magicBytes.slice(2))) { - try { - const [to, data, decoded] = AbiParameters.decode( - [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], - signature.slice(0, -SignatureErc6492.magicBytes.slice(2).length) as Hex.Hex, - ) - return { signature: decoded as T, erc6492: { to, data: data as T } } - } catch { - return { signature } - } - } else { - return { signature } - } - } -} - -export function isValid( - address: Address.Address, - messageHash: Bytes.Bytes | Hex.Hex, - encodedSignature: Bytes.Bytes | Hex.Hex, - provider: Provider.Provider, -): Promise { - // Validate off chain with ERC-6492 - const validationCallData: Hex.Hex = AbiParameters.encode(AbiParameters.from('address, bytes32, bytes'), [ - address, - Hex.from(messageHash), - Hex.from(encodedSignature), - ]) - const callData = Hex.concat(EIP_6492_OFFCHAIN_DEPLOY_CODE, validationCallData) - return provider - .request({ - method: 'eth_call', - params: [{ data: callData }, 'latest'], - }) - .then((result) => parseInt(result, 16) === 1) -} diff --git a/packages/wallet/primitives/src/extensions/index.ts b/packages/wallet/primitives/src/extensions/index.ts deleted file mode 100644 index 2ff8ac16b2..0000000000 --- a/packages/wallet/primitives/src/extensions/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Address } from 'ox' - -export type Extensions = { - passkeys: Address.Address - recovery: Address.Address - sessions: Address.Address -} - -export const Dev1: Extensions = { - passkeys: '0x8f26281dB84C18aAeEa8a53F94c835393229d296', - recovery: '0xd98da48C4FF9c19742eA5856A277424557C863a6', - sessions: '0x06aa3a8F781F2be39b888Ac8a639c754aEe9dA29', -} - -export const Dev2: Extensions = { - passkeys: '0x4491845806B757D67BE05BbD877Cab101B9bee5C', - recovery: '0xdED857b9b5142832634129aFfc1D67cD106b927c', - sessions: '0x06aa3a8F781F2be39b888Ac8a639c754aEe9dA29', -} - -export const Rc3: Extensions = { - passkeys: '0x0000000000dc2d96870dc108c5E15570B715DFD2', - recovery: '0x0000000000213697bCA95E7373787a40858a51C7', - sessions: '0x0000000000CC58810c33F3a0D78aA1Ed80FaDcD8', -} - -export const Rc4: Extensions = { - passkeys: '0x0000000000005204F3711851EAD52CC9c241499a', - recovery: '0x000000000001FC499c3E177DD56Febb0A4bc15b7', - sessions: '0x00000000000030Bcc832F7d657f50D6Be35C92b3', -} - -export const Rc5: Extensions = { - passkeys: '0x0000000000005204F3711851EAD52CC9c241499a', - recovery: '0x000000000000AB36D17eB1150116371520565205', - sessions: '0x00000000000030Bcc832F7d657f50D6Be35C92b3', -} - -export * as Passkeys from './passkeys.js' -export * as Recovery from './recovery.js' diff --git a/packages/wallet/primitives/src/extensions/passkeys.ts b/packages/wallet/primitives/src/extensions/passkeys.ts deleted file mode 100644 index e5500cc294..0000000000 --- a/packages/wallet/primitives/src/extensions/passkeys.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { Bytes, Hex, WebAuthnP256 } from 'ox' -import * as GenericTree from '../generic-tree.js' - -export type PasskeyMetadata = { - credentialId: string -} - -export type PublicKey = { - requireUserVerification: boolean - x: Hex.Hex - y: Hex.Hex - metadata?: PasskeyMetadata | Hex.Hex -} - -export function metadataTree(metadata: Required['metadata']): GenericTree.Tree { - if (typeof metadata === 'object') { - return { - type: 'leaf', - value: Bytes.fromString(metadata.credentialId), - } - } else { - return metadata - } -} - -export function metadataNode(metadata: Required['metadata']): GenericTree.Node { - return GenericTree.hash(metadataTree(metadata)) -} - -export function toTree(publicKey: PublicKey): GenericTree.Tree { - const a = Hex.padLeft(publicKey.x, 32) - const b = Hex.padLeft(publicKey.y, 32) - const c = Hex.padLeft(publicKey.requireUserVerification ? '0x01' : '0x00', 32) - - if (publicKey.metadata) { - return [ - [a, b], - [c, metadataTree(publicKey.metadata)], - ] - } else { - return [ - [a, b], - [c, Hex.padLeft('0x00', 32)], - ] - } -} - -export function fromTree(tree: GenericTree.Tree): PublicKey { - if (!GenericTree.isBranch(tree) || tree.length !== 2) { - throw new Error('Invalid tree') - } - const [p1, p2] = tree - if (!GenericTree.isBranch(p1) || p1.length !== 2) { - throw new Error('Invalid tree for x,y') - } - - const [x, y] = p1 - if (!GenericTree.isNode(x)) { - throw new Error('Invalid x bytes') - } - if (!GenericTree.isNode(y)) { - throw new Error('Invalid y bytes') - } - - let requireUserVerification = false - let metadata: PublicKey['metadata'] - - if (GenericTree.isBranch(p2)) { - if (p2.length !== 2) { - throw new Error('Invalid tree for c,metadata') - } - - const [c, meta] = p2 - if (!GenericTree.isNode(c)) { - throw new Error('Invalid c bytes') - } - const cBytes = Hex.toBytes(c) - requireUserVerification = cBytes[31] === 1 - - if (GenericTree.isBranch(meta)) { - if (meta.length !== 2) { - throw new Error('Invalid metadata tree') - } - - const [credLeaf, sub] = meta - if (!GenericTree.isLeaf(credLeaf)) { - throw new Error('Invalid credentialId leaf') - } - const credentialId = new TextDecoder().decode(credLeaf.value) - - if (!GenericTree.isBranch(sub) || sub.length !== 2) { - throw new Error('Invalid sub-branch for name and createdAt') - } - - const [nameLeaf, createdAtLeaf] = sub - if (!GenericTree.isLeaf(nameLeaf) || !GenericTree.isLeaf(createdAtLeaf)) { - throw new Error('Invalid metadata leaves') - } - - metadata = { credentialId } - } else if (GenericTree.isNode(meta)) { - metadata = meta - } else { - throw new Error('Invalid metadata node') - } - } else { - if (!GenericTree.isNode(p2)) { - throw new Error('Invalid c bytes') - } - const p2Bytes = Hex.toBytes(p2) - requireUserVerification = p2Bytes[31] === 1 - } - - return { requireUserVerification, x, y, metadata } -} - -export function rootFor(publicKey: PublicKey): Hex.Hex { - return GenericTree.hash(toTree(publicKey)) -} - -export type DecodedSignature = { - publicKey: PublicKey - r: Bytes.Bytes - s: Bytes.Bytes - authenticatorData: Bytes.Bytes - clientDataJSON: string - embedMetadata?: boolean -} - -export function encode(decoded: DecodedSignature): Bytes.Bytes { - const challengeIndex = decoded.clientDataJSON.indexOf('"challenge"') - const typeIndex = decoded.clientDataJSON.indexOf('"type"') - - const authDataSize = decoded.authenticatorData.length - const clientDataJSONSize = decoded.clientDataJSON.length - - if (authDataSize > 65535) { - throw new Error('Authenticator data size is too large') - } - if (clientDataJSONSize > 65535) { - throw new Error('Client data JSON size is too large') - } - - const bytesAuthDataSize = authDataSize <= 255 ? 1 : 2 - const bytesClientDataJSONSize = clientDataJSONSize <= 255 ? 1 : 2 - const bytesChallengeIndex = challengeIndex <= 255 ? 1 : 2 - const bytesTypeIndex = typeIndex <= 255 ? 1 : 2 - - let flags = 0 - - flags |= decoded.publicKey.requireUserVerification ? 1 : 0 // 0x01 bit - flags |= (bytesAuthDataSize - 1) << 1 // 0x02 bit - flags |= (bytesClientDataJSONSize - 1) << 2 // 0x04 bit - flags |= (bytesChallengeIndex - 1) << 3 // 0x08 bit - flags |= (bytesTypeIndex - 1) << 4 // 0x10 bit - - // Set metadata flag if metadata exists - if (decoded.embedMetadata) { - flags |= 1 << 6 // 0x40 bit - } - - let result: Bytes.Bytes = Bytes.from([flags]) - - // Add metadata if it exists - if (decoded.embedMetadata) { - if (!decoded.publicKey.metadata) { - throw new Error('Metadata is not present in the public key') - } - result = Bytes.concat(result, Hex.toBytes(metadataNode(decoded.publicKey.metadata))) - } - - result = Bytes.concat(result, Bytes.padLeft(Bytes.fromNumber(authDataSize), bytesAuthDataSize)) - result = Bytes.concat(result, decoded.authenticatorData) - - result = Bytes.concat(result, Bytes.padLeft(Bytes.fromNumber(decoded.clientDataJSON.length), bytesClientDataJSONSize)) - result = Bytes.concat(result, Bytes.from(new TextEncoder().encode(decoded.clientDataJSON))) - - result = Bytes.concat(result, Bytes.padLeft(Bytes.fromNumber(challengeIndex), bytesChallengeIndex)) - result = Bytes.concat(result, Bytes.padLeft(Bytes.fromNumber(typeIndex), bytesTypeIndex)) - - result = Bytes.concat(result, Bytes.padLeft(decoded.r, 32)) - result = Bytes.concat(result, Bytes.padLeft(decoded.s, 32)) - - result = Bytes.concat(result, Bytes.fromHex(decoded.publicKey.x)) - result = Bytes.concat(result, Bytes.fromHex(decoded.publicKey.y)) - - return result -} - -export function isValidSignature(challenge: Hex.Hex, decoded: DecodedSignature): boolean { - return WebAuthnP256.verify({ - challenge, - publicKey: { - x: Hex.toBigInt(decoded.publicKey.x), - y: Hex.toBigInt(decoded.publicKey.y), - prefix: 4, - }, - metadata: { - authenticatorData: Hex.fromBytes(decoded.authenticatorData), - challengeIndex: decoded.clientDataJSON.indexOf('"challenge"'), - clientDataJSON: decoded.clientDataJSON, - typeIndex: decoded.clientDataJSON.indexOf('"type"'), - userVerificationRequired: decoded.publicKey.requireUserVerification, - }, - signature: { - r: Bytes.toBigInt(decoded.r), - s: Bytes.toBigInt(decoded.s), - }, - }) -} - -export function decode(data: Bytes.Bytes): Required & { challengeIndex: number; typeIndex: number } { - let offset = 0 - - const flags = data[0] - offset += 1 - - if (flags === undefined) { - throw new Error('Invalid flags') - } - - const requireUserVerification = (flags & 0x01) !== 0x00 - const bytesAuthDataSize = ((flags >> 1) & 0x01) + 1 - const bytesClientDataJSONSize = ((flags >> 2) & 0x01) + 1 - const bytesChallengeIndex = ((flags >> 3) & 0x01) + 1 - const bytesTypeIndex = ((flags >> 4) & 0x01) + 1 - const hasMetadata = ((flags >> 6) & 0x01) === 0x01 - - // Check if fallback to abi decode is needed - if ((flags & 0x20) !== 0) { - throw new Error('Fallback to abi decode is not supported in this implementation') - } - - let metadata: Hex.Hex | undefined - - // Read metadata if present - if (hasMetadata) { - const metadataBytes = Bytes.slice(data, offset, offset + 32) - metadata = Hex.fromBytes(metadataBytes) - offset += 32 - } - - const authDataSize = Bytes.toNumber(Bytes.slice(data, offset, offset + bytesAuthDataSize)) - offset += bytesAuthDataSize - const authenticatorData = Bytes.slice(data, offset, offset + authDataSize) - offset += authDataSize - - const clientDataJSONSize = Bytes.toNumber(Bytes.slice(data, offset, offset + bytesClientDataJSONSize)) - offset += bytesClientDataJSONSize - const clientDataJSONBytes = Bytes.slice(data, offset, offset + clientDataJSONSize) - offset += clientDataJSONSize - const clientDataJSON = new TextDecoder().decode(clientDataJSONBytes) - - const challengeIndex = Bytes.toNumber(Bytes.slice(data, offset, offset + bytesChallengeIndex)) - offset += bytesChallengeIndex - const typeIndex = Bytes.toNumber(Bytes.slice(data, offset, offset + bytesTypeIndex)) - offset += bytesTypeIndex - - const r = Bytes.slice(data, offset, offset + 32) - offset += 32 - const s = Bytes.slice(data, offset, offset + 32) - offset += 32 - - const xBytes = Bytes.slice(data, offset, offset + 32) - offset += 32 - const yBytes = Bytes.slice(data, offset, offset + 32) - - return { - publicKey: { - requireUserVerification, - x: Hex.fromBytes(xBytes), - y: Hex.fromBytes(yBytes), - metadata, - }, - r, - s, - authenticatorData, - clientDataJSON, - challengeIndex, - typeIndex, - embedMetadata: hasMetadata, - } -} diff --git a/packages/wallet/primitives/src/extensions/recovery.ts b/packages/wallet/primitives/src/extensions/recovery.ts deleted file mode 100644 index 43cd05b9c3..0000000000 --- a/packages/wallet/primitives/src/extensions/recovery.ts +++ /dev/null @@ -1,547 +0,0 @@ -import { Abi, AbiFunction, Address, Bytes, Hex, Provider } from 'ox' -import * as GenericTree from '../generic-tree.js' -import { Signature } from '../index.js' -import * as Payload from '../payload.js' -import { packRSY } from '../utils.js' - -export const FLAG_RECOVERY_LEAF = 1 -export const FLAG_NODE = 3 -export const FLAG_BRANCH = 4 - -const RECOVERY_LEAF_PREFIX = Bytes.fromString('Sequence recovery leaf:\n') - -export const QUEUE_PAYLOAD = Abi.from([ - 'function queuePayload(address _wallet, address _signer, (uint8 kind,bool noChainId,(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)[] calls,uint256 space,uint256 nonce,bytes message,bytes32 imageHash,bytes32 digest,address[] parentWallets) calldata _payload, bytes calldata _signature) external', -])[0] - -export const TIMESTAMP_FOR_QUEUED_PAYLOAD = Abi.from([ - 'function timestampForQueuedPayload(address _wallet, address _signer, bytes32 _payloadHash) external view returns (uint256)', -])[0] - -export const QUEUED_PAYLOAD_HASHES = Abi.from([ - 'function queuedPayloadHashes(address _wallet, address _signer, uint256 _index) external view returns (bytes32)', -])[0] - -export const TOTAL_QUEUED_PAYLOADS = Abi.from([ - 'function totalQueuedPayloads(address _wallet, address _signer) external view returns (uint256)', -])[0] - -/** - * A leaf in the Recovery tree, storing: - * - signer who can queue a payload - * - requiredDeltaTime how many seconds must pass since the payload is queued - * - minTimestamp a minimal timestamp that must be at or below the queueing time - */ -export type RecoveryLeaf = { - type: 'leaf' - signer: Address.Address - requiredDeltaTime: bigint - minTimestamp: bigint -} - -/** - * A branch is a list of subtrees (≥2 in length). - */ -export type Branch = [Tree, Tree] - -/** - * The topology of a recovery tree can be either: - * - A node (pair of subtrees) - * - A node leaf (32-byte hash) - * - A recovery leaf (signer with timing constraints) - */ -export type Tree = Branch | GenericTree.Node | RecoveryLeaf - -/** - * Type guard to check if a value is a RecoveryLeaf - */ -export function isRecoveryLeaf(cand: unknown): cand is RecoveryLeaf { - return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'leaf' -} - -/** - * Type guard to check if a value is a Node (pair of subtrees) - */ -export function isBranch(cand: unknown): cand is Branch { - return Array.isArray(cand) && cand.length === 2 && isTree(cand[0]) && isTree(cand[1]) -} - -/** - * Type guard to check if a value is a Topology - */ -export function isTree(cand: unknown): cand is Tree { - return isRecoveryLeaf(cand) || GenericTree.isNode(cand) || isBranch(cand) -} - -/** - * EIP-712 domain parameters for "Sequence Wallet - Recovery Mode" - */ -export const DOMAIN_NAME = 'Sequence Wallet - Recovery Mode' -export const DOMAIN_VERSION = '1' - -/** - * Recursively computes the root hash of a RecoveryTree, - * consistent with the contract's fkeccak256 usage for (root, node). - * - * For recovery leaves, it hashes the leaf data with a prefix. - * For node leaves, it returns the hash directly. - * For nodes, it hashes the concatenation of the hashes of both subtrees. - */ -export function hashConfiguration(topology: Tree): Hex.Hex { - return GenericTree.hash(toGenericTree(topology)) -} - -/** - * Flatten a RecoveryTree into an array of just the leaves. - * Ignores branch boundaries or node references. - * - * @returns Object containing: - * - leaves: Array of RecoveryLeaf nodes - * - isComplete: boolean indicating if all leaves are present (no node references) - */ -export function getRecoveryLeaves(topology: Tree): { leaves: RecoveryLeaf[]; isComplete: boolean } { - const isComplete = true - if (isRecoveryLeaf(topology)) { - return { leaves: [topology], isComplete } - } else if (GenericTree.isNode(topology)) { - return { leaves: [], isComplete: false } - } else if (isBranch(topology)) { - const left = getRecoveryLeaves(topology[0]) - const right = getRecoveryLeaves(topology[1]) - return { leaves: [...left.leaves, ...right.leaves], isComplete: left.isComplete && right.isComplete } - } else { - throw new Error('Invalid topology') - } -} - -/** - * Decode a binary encoded topology into a Topology object - * - * @param encoded - The binary encoded topology - * @returns The decoded Topology object - * @throws Error if the encoding is invalid - */ -export function decodeTopology(encoded: Bytes.Bytes): Tree { - const { nodes, leftover } = parseBranch(encoded) - if (leftover.length > 0) { - throw new Error('Leftover bytes in branch') - } - return foldNodes(nodes) -} - -/** - * Parse a branch of the topology from binary encoding - * - * @param encoded - The binary encoded branch - * @returns Object containing: - * - nodes: Array of parsed Topology nodes - * - leftover: Any remaining unparsed bytes - * @throws Error if the encoding is invalid - */ -export function parseBranch(encoded: Bytes.Bytes): { nodes: Tree[]; leftover: Bytes.Bytes } { - if (encoded.length === 0) { - throw new Error('Empty branch') - } - - const nodes: Tree[] = [] - let index = 0 - - while (index < encoded.length) { - const flag = encoded[index]! - if (flag === FLAG_RECOVERY_LEAF) { - if (encoded.length < index + 32) { - throw new Error('Invalid recovery leaf') - } - const signer = Address.from(Hex.fromBytes(encoded.slice(index + 1, index + 21))) - const requiredDeltaTime = Bytes.toBigInt(encoded.slice(index + 21, index + 24)) - const minTimestamp = Bytes.toBigInt(encoded.slice(index + 24, index + 32)) - nodes.push({ type: 'leaf', signer, requiredDeltaTime, minTimestamp }) - index += 32 - continue - } else if (flag === FLAG_NODE) { - // total = 1 (flag) + 32 (node hash) - if (encoded.length < index + 33) { - throw new Error('Invalid node') - } - const node = Hex.fromBytes(encoded.slice(index + 1, index + 33)) - nodes.push(node) - index += 33 - continue - } else if (flag === FLAG_BRANCH) { - if (encoded.length < index + 4) { - throw new Error('Invalid branch') - } - const size = Bytes.toNumber(encoded.slice(index + 1, index + 4)) - if (encoded.length < index + 4 + size) { - throw new Error('Invalid branch') - } - const branch = encoded.slice(index + 4, index + 4 + size) - const { nodes: subNodes, leftover } = parseBranch(branch) - if (leftover.length > 0) { - throw new Error('Leftover bytes in sub-branch') - } - const subTree = foldNodes(subNodes) - nodes.push(subTree) - index += 4 + size - continue - } else { - throw new Error('Invalid flag') - } - } - - return { nodes, leftover: encoded.slice(index) } -} - -/** - * Trim a topology tree to only include leaves for a specific signer. - * All other leaves are replaced with their hashes. - * - * @param topology - The topology to trim - * @param signer - The signer address to keep - * @returns The trimmed topology - */ -export function trimTopology(topology: Tree, signer: Address.Address): Tree { - if (isRecoveryLeaf(topology)) { - if (Address.isEqual(topology.signer, signer)) { - return topology - } else { - return hashConfiguration(topology) - } - } - - if (GenericTree.isNode(topology)) { - return topology - } - - if (isBranch(topology)) { - const left = trimTopology(topology[0], signer) - const right = trimTopology(topology[1], signer) - - // If both are hashes, we can just return the hash of the node - if (GenericTree.isNode(left) && GenericTree.isNode(right)) { - return hashConfiguration(topology) - } - - return [left, right] as Branch - } - - throw new Error('Invalid topology') -} - -/** - * Encode a topology into its binary representation - * - * @param topology - The topology to encode - * @returns The binary encoded topology - * @throws Error if the topology is invalid - */ -export function encodeTopology(topology: Tree): Bytes.Bytes { - if (isBranch(topology)) { - const encoded0 = encodeTopology(topology[0]!) - const encoded1 = encodeTopology(topology[1]!) - const isBranching = isBranch(topology[1]!) - - if (isBranching) { - // max 3 bytes for the size - if (encoded1.length > 16777215) { - throw new Error('Branch too large') - } - - const flag = Bytes.fromNumber(FLAG_BRANCH) - const size = Bytes.padLeft(Bytes.fromNumber(encoded1.length), 3) - return Bytes.concat(encoded0, flag, size, encoded1) - } else { - return Bytes.concat(encoded0, encoded1) - } - } - - if (GenericTree.isNode(topology)) { - const flag = Bytes.fromNumber(FLAG_NODE) - const nodeHash = Bytes.fromHex(topology, { size: 32 }) - return Bytes.concat(flag, nodeHash) - } - - if (isRecoveryLeaf(topology)) { - const flag = Bytes.fromNumber(FLAG_RECOVERY_LEAF) - const signer = Bytes.fromHex(topology.signer, { size: 20 }) - - if (topology.requiredDeltaTime > 16777215n) { - throw new Error('Required delta time too large') - } - - const requiredDeltaTime = Bytes.padLeft(Bytes.fromNumber(topology.requiredDeltaTime), 3) - if (topology.minTimestamp > 18446744073709551615n) { - throw new Error('Min timestamp too large') - } - - const minTimestamp = Bytes.padLeft(Bytes.fromNumber(topology.minTimestamp), 8) - return Bytes.concat(flag, signer, requiredDeltaTime, minTimestamp) - } - - throw new Error('Invalid topology') -} - -/** - * Helper function to fold a list of nodes into a binary tree structure - * - * @param nodes - Array of topology nodes - * @returns A binary tree structure - * @throws Error if the nodes array is empty - */ -function foldNodes(nodes: Tree[]): Tree { - if (nodes.length === 0) { - throw new Error('Empty signature tree') - } - - if (nodes.length === 1) { - return nodes[0]! - } - - let tree: Tree = nodes[0]! - for (let i = 1; i < nodes.length; i++) { - tree = [tree, nodes[i]!] as Tree - } - return tree -} - -/** - * Build a RecoveryTree from an array of leaves, making a minimal branch structure. - * If there's exactly one leaf, we return that leaf. If there's more than one, we - * build a branch of them in pairs. - * - * @param leaves - Array of recovery leaves - * @returns A topology tree structure - * @throws Error if the leaves array is empty - */ -export function fromRecoveryLeaves(leaves: RecoveryLeaf[]): Tree { - if (leaves.length === 0) { - throw new Error('Cannot build a tree with zero leaves') - } - - if (leaves.length === 1) { - return leaves[0] as RecoveryLeaf - } - - const mid = Math.floor(leaves.length / 2) - const left = fromRecoveryLeaves(leaves.slice(0, mid)) - const right = fromRecoveryLeaves(leaves.slice(mid)) - return [left, right] as Branch -} - -/** - * Produces an EIP-712 typed data hash for a "recovery mode" payload, - * matching the logic in Recovery.sol: - * - * keccak256( - * "\x19\x01", - * domainSeparator(noChainId, wallet), - * Payload.toEIP712(payload) - * ) - * - * @param payload - The payload to hash - * @param wallet - The wallet address - * @param chainId - The chain ID - * @param noChainId - Whether to omit the chain ID from the domain separator - * @returns The payload hash - */ -export function hashRecoveryPayload( - payload: Payload.MayRecoveryPayload, - wallet: Address.Address, - chainId: number, - noChainId: boolean, -): Hex.Hex { - const recoveryPayload = Payload.toRecovery(payload) - return Hex.fromBytes(Payload.hash(wallet, noChainId ? 0 : chainId, recoveryPayload)) -} - -/** - * Convert a RecoveryTree topology to a generic tree format - * - * @param topology - The recovery tree topology to convert - * @returns A generic tree that produces the same root hash - */ -export function toGenericTree(topology: Tree): GenericTree.Tree { - if (isRecoveryLeaf(topology)) { - // Convert recovery leaf to generic leaf - return { - type: 'leaf', - value: Bytes.concat( - RECOVERY_LEAF_PREFIX, - Bytes.fromHex(topology.signer, { size: 20 }), - Bytes.padLeft(Bytes.fromNumber(topology.requiredDeltaTime), 32), - Bytes.padLeft(Bytes.fromNumber(topology.minTimestamp), 32), - ), - } - } else if (GenericTree.isNode(topology)) { - // Node leaves are already in the correct format - return topology - } else if (isBranch(topology)) { - // Convert node to branch - return [toGenericTree(topology[0]), toGenericTree(topology[1])] - } else { - throw new Error('Invalid topology') - } -} - -/** - * Convert a generic tree back to a RecoveryTree topology - * - * @param tree - The generic tree to convert - * @returns A recovery tree topology that produces the same root hash - */ -export function fromGenericTree(tree: GenericTree.Tree): Tree { - if (GenericTree.isLeaf(tree)) { - // Convert generic leaf back to recovery leaf - const bytes = tree.value - if ( - bytes.length !== RECOVERY_LEAF_PREFIX.length + 84 || - !Bytes.isEqual(bytes.slice(0, RECOVERY_LEAF_PREFIX.length), RECOVERY_LEAF_PREFIX) - ) { - throw new Error('Invalid recovery leaf format') - } - - const offset = RECOVERY_LEAF_PREFIX.length - const signer = Address.from(Hex.fromBytes(bytes.slice(offset, offset + 20))) - const requiredDeltaTime = Bytes.toBigInt(bytes.slice(offset + 20, offset + 52)) - const minTimestamp = Bytes.toBigInt(bytes.slice(offset + 52, offset + 84)) - - return { - type: 'leaf', - signer, - requiredDeltaTime, - minTimestamp, - } - } else if (GenericTree.isNode(tree)) { - // Nodes are already in the correct format - return tree - } else if (GenericTree.isBranch(tree)) { - // Convert branch back to node - if (tree.length !== 2) { - throw new Error('Recovery tree only supports binary branches') - } - return [fromGenericTree(tree[0]), fromGenericTree(tree[1])] as Branch - } else { - throw new Error('Invalid tree format') - } -} - -/** - * Encodes the calldata for queueing a recovery payload on the recovery extension - * - * @param wallet - The wallet address that owns the recovery configuration - * @param payload - The recovery payload to queue for execution - * @param signer - The recovery signer address that is queueing the payload - * @param signature - The signature from the recovery signer authorizing the payload - * @returns The encoded calldata for the queuePayload function on the recovery extension - */ -export function encodeCalldata( - wallet: Address.Address, - payload: Payload.Recovery, - signer: Address.Address, - signature: Signature.SignatureOfSignerLeaf, -): Hex.Hex { - let signatureBytes: Hex.Hex - - if (signature.type === 'erc1271') { - signatureBytes = signature.data - } else { - signatureBytes = Bytes.toHex(packRSY(signature)) - } - - const abiPayload = Payload.toAbiFormat(payload) - return AbiFunction.encodeData(QUEUE_PAYLOAD, [wallet, signer, abiPayload, signatureBytes]) -} - -/** - * Gets the total number of payloads queued by a recovery signer for a wallet - * - * @param provider - The provider to use for making the eth_call - * @param extension - The address of the recovery extension contract - * @param wallet - The wallet address to check queued payloads for - * @param signer - The recovery signer address to check queued payloads for - * @returns The total number of payloads queued by this signer for this wallet - */ -export async function totalQueuedPayloads( - provider: Provider.Provider, - extension: Address.Address, - wallet: Address.Address, - signer: Address.Address, -): Promise { - const total = await provider.request({ - method: 'eth_call', - params: [ - { - to: extension, - data: AbiFunction.encodeData(TOTAL_QUEUED_PAYLOADS, [wallet, signer]), - }, - 'latest', - ], - }) - - if (total === '0x') { - return 0n - } - return Hex.toBigInt(total) -} - -/** - * Gets the hash of a queued payload at a specific index - * - * @param provider - The provider to use for making the eth_call - * @param extension - The address of the recovery extension contract - * @param wallet - The wallet address to get the queued payload for - * @param signer - The recovery signer address that queued the payload - * @param index - The index of the queued payload to get the hash for - * @returns The hash of the queued payload at the specified index - */ -export async function queuedPayloadHashOf( - provider: Provider.Provider, - extension: Address.Address, - wallet: Address.Address, - signer: Address.Address, - index: bigint, -): Promise { - const hash = await provider.request({ - method: 'eth_call', - params: [ - { - to: extension, - data: AbiFunction.encodeData(QUEUED_PAYLOAD_HASHES, [wallet, signer, index]), - }, - 'latest', - ], - }) - - return hash -} - -/** - * Gets the timestamp when a specific payload was queued - * - * @param provider - The provider to use for making the eth_call - * @param extension - The address of the recovery extension contract - * @param wallet - The wallet address the payload was queued for - * @param signer - The recovery signer address that queued the payload - * @param payloadHash - The hash of the queued payload to get the timestamp for - * @returns The timestamp when the payload was queued, or 0 if not found - */ -export async function timestampForQueuedPayload( - provider: Provider.Provider, - extension: Address.Address, - wallet: Address.Address, - signer: Address.Address, - payloadHash: Hex.Hex, -): Promise { - const timestamp = await provider.request({ - method: 'eth_call', - params: [ - { - to: extension, - data: AbiFunction.encodeData(TIMESTAMP_FOR_QUEUED_PAYLOAD, [wallet, signer, payloadHash]), - }, - 'latest', - ], - }) - - return Hex.toBigInt(timestamp) -} diff --git a/packages/wallet/primitives/src/generic-tree.ts b/packages/wallet/primitives/src/generic-tree.ts deleted file mode 100644 index 4270e64dc7..0000000000 --- a/packages/wallet/primitives/src/generic-tree.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Bytes, Hash, Hex } from 'ox' - -// An encoded configuration tree is a generic configuration tree that has been encoded into a bytes sequence. -// It can be used to represent a configuration tree in a compact form. -// Implementations are free to use any encoding they want, as long as the encoding is consistent and can be decoded. - -export type Leaf = { - type: 'leaf' - value: Bytes.Bytes -} - -// Hashed leaf -export type Node = Hex.Hex - -export type Branch = [Tree, Tree, ...Tree[]] -export type Tree = Branch | Leaf | Node - -export function isBranch(tree: Tree): tree is Branch { - return Array.isArray(tree) && tree.length >= 2 && tree.every((child) => isTree(child)) -} - -export function isLeaf(tree: any): tree is Leaf { - return tree.type === 'leaf' && Bytes.validate(tree.value) -} - -export function isTree(tree: any): tree is Tree { - return isBranch(tree) || isLeaf(tree) || isNode(tree) -} - -export function isNode(node: any): node is Node { - return Hex.validate(node) && Hex.size(node) === 32 -} - -export function hash(tree: Tree): Hex.Hex { - if (isBranch(tree)) { - // Sequentially hash the children - const hashedChildren = tree.map(hash) - if (hashedChildren.length === 0) { - throw new Error('Empty branch') - } - let chashBytes = Hex.toBytes(hashedChildren[0]!) - for (let i = 1; i < hashedChildren.length; i++) { - chashBytes = Hash.keccak256(Bytes.concat(chashBytes, Hex.toBytes(hashedChildren[i]!))) - } - return Hex.fromBytes(chashBytes) - } - - // Nodes are already hashed - if (isNode(tree)) { - return tree - } - - // Hash the leaf - return Hash.keccak256(tree.value, { as: 'Hex' }) -} diff --git a/packages/wallet/primitives/src/index.ts b/packages/wallet/primitives/src/index.ts deleted file mode 100644 index 2b4c146c5d..0000000000 --- a/packages/wallet/primitives/src/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * as Address from './address.js' -export * as Attestation from './attestation.js' -export * as Constants from './constants.js' -export * as Erc6492 from './erc-6492.js' -export * as Payload from './payload.js' -export * as Permission from './permission.js' -export * as Precondition from './precondition.js' -export * as SessionConfig from './session-config.js' -export * as SessionSignature from './session-signature.js' -export * as Signature from './signature.js' -export * as Utils from './utils.js' -export * as Config from './config.js' -export * as Context from './context.js' -export * as Extensions from './extensions/index.js' -export * as GenericTree from './generic-tree.js' -export * as Network from './network.js' diff --git a/packages/wallet/primitives/src/network.ts b/packages/wallet/primitives/src/network.ts deleted file mode 100644 index 69f40ae4f4..0000000000 --- a/packages/wallet/primitives/src/network.ts +++ /dev/null @@ -1,1214 +0,0 @@ -import { Address } from 'ox' - -const DEFAULT_MULTICALL3_ADDRESS: Address.Address = '0xcA11bde05977b3631167028862bE2a173976CA11' -const SEQUENCE_MULTICALL3_ADDRESS: Address.Address = '0xae96419a81516f063744206d4b5E36f3168280f8' - -export enum NetworkType { - MAINNET = 'mainnet', - TESTNET = 'testnet', -} - -export type BlockExplorerConfig = { - name?: string - url: string -} - -export interface Network { - chainId: number - type: NetworkType - name: string - title?: string - rpcUrl: string - logoUrl?: string - blockExplorer?: BlockExplorerConfig - nativeCurrency: { - symbol: string - name: string - decimals: number - } - deprecated?: true - contracts?: { - multicall3?: Address.Address - ensUniversalResolver?: Address.Address - } -} - -export const ChainId = { - NONE: 0, - - // Ethereum - MAINNET: 1, - SEPOLIA: 11155111, - - // Polygon - POLYGON: 137, - POLYGON_ZKEVM: 1101, - POLYGON_AMOY: 80002, - - // BSC - BSC: 56, - BSC_TESTNET: 97, - - // Optimism - OPTIMISM: 10, - OPTIMISM_SEPOLIA: 11155420, - - // Arbitrum One - ARBITRUM: 42161, - ARBITRUM_SEPOLIA: 421614, - - // Arbitrum Nova - ARBITRUM_NOVA: 42170, - - // Avalanche - AVALANCHE: 43114, - AVALANCHE_TESTNET: 43113, - - // Gnosis Chain (XDAI) - GNOSIS: 100, - - // BASE - BASE: 8453, - BASE_SEPOLIA: 84532, - - // HOMEVERSE - HOMEVERSE_TESTNET: 40875, - HOMEVERSE: 19011, - - // Xai - XAI: 660279, - XAI_SEPOLIA: 37714555429, - - // TELOS - TELOS: 40, - TELOS_TESTNET: 41, - - // B3 Sepolia - B3: 8333, - B3_SEPOLIA: 1993, - - // APE Chain - APECHAIN: 33139, - APECHAIN_TESTNET: 33111, - - // Blast - BLAST: 81457, - BLAST_SEPOLIA: 168587773, - - // SKALE Nebula - SKALE_NEBULA: 1482601649, - SKALE_NEBULA_TESTNET: 37084624, - - // Soneium Minato - SONEIUM_MINATO: 1946, - SONEIUM: 1868, - - // TOY Testnet - TOY_TESTNET: 21000000, - - // Immutable zkEVM - IMMUTABLE_ZKEVM: 13371, - IMMUTABLE_ZKEVM_TESTNET: 13473, - - // ETHERLINK - ETHERLINK: 42793, - ETHERLINK_TESTNET: 128123, - ETHERLINK_SHADOWNET_TESTNET: 127823, - - // MOONBEAM - MOONBEAM: 1284, - MOONBASE_ALPHA: 1287, - - // MONAD - MONAD: 143, - MONAD_TESTNET: 10143, - - // SOMNIA - SOMNIA_TESTNET: 50312, - SOMNIA: 5031, - - // INCENTIV - INCENTIV: 24101, - INCENTIV_TESTNET_V2: 28802, - - // KATANA - KATANA: 747474, - - // SANDBOX - SANDBOX_TESTNET: 6252, - - // ARC - ARC_TESTNET: 5042002, - - // HYPEREVM - HYPEREVM: 999, - - // SONIC - SONIC: 146, - - // BERACHAIN - BERACHAIN: 80094, -} as const - -export type ChainId = (typeof ChainId)[keyof typeof ChainId] - -export const ALL: Network[] = [ - { - chainId: ChainId.MAINNET, - type: NetworkType.MAINNET, - name: 'mainnet', - title: 'Ethereum', - rpcUrl: getRpcUrl('mainnet'), - logoUrl: getLogoUrl(ChainId.MAINNET), - blockExplorer: { - name: 'Etherscan', - url: 'https://etherscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - ensUniversalResolver: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', - }, - }, - { - chainId: ChainId.SEPOLIA, - type: NetworkType.TESTNET, - name: 'sepolia', - title: 'Sepolia', - rpcUrl: getRpcUrl('sepolia'), - logoUrl: getLogoUrl(ChainId.SEPOLIA), - blockExplorer: { - name: 'Etherscan (Sepolia)', - url: 'https://sepolia.etherscan.io/', - }, - nativeCurrency: { - symbol: 'sETH', - name: 'Sepolia Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.POLYGON, - type: NetworkType.MAINNET, - name: 'polygon', - title: 'Polygon', - rpcUrl: getRpcUrl('polygon'), - logoUrl: getLogoUrl(ChainId.POLYGON), - blockExplorer: { - name: 'Polygonscan', - url: 'https://polygonscan.com/', - }, - nativeCurrency: { - symbol: 'POL', - name: 'POL', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.POLYGON_AMOY, - type: NetworkType.TESTNET, - name: 'amoy', - title: 'Polygon Amoy', - rpcUrl: getRpcUrl('amoy'), - logoUrl: getLogoUrl(ChainId.POLYGON_AMOY), - blockExplorer: { - name: 'OKLink (Amoy)', - url: 'https://www.oklink.com/amoy/', - }, - nativeCurrency: { - symbol: 'aPOL', - name: 'Amoy POL', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.POLYGON_ZKEVM, - type: NetworkType.MAINNET, - name: 'polygon-zkevm', - title: 'Polygon zkEVM', - rpcUrl: getRpcUrl('polygon-zkevm'), - logoUrl: getLogoUrl(ChainId.POLYGON_ZKEVM), - blockExplorer: { - name: 'Polygonscan (zkEVM)', - url: 'https://zkevm.polygonscan.com/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BSC, - type: NetworkType.MAINNET, - name: 'bsc', - title: 'BNB Smart Chain', - rpcUrl: getRpcUrl('bsc'), - logoUrl: getLogoUrl(ChainId.BSC), - blockExplorer: { - name: 'BSCScan', - url: 'https://bscscan.com/', - }, - nativeCurrency: { - symbol: 'BNB', - name: 'BNB', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BSC_TESTNET, - type: NetworkType.TESTNET, - name: 'bsc-testnet', - title: 'BNB Smart Chain Testnet', - rpcUrl: getRpcUrl('bsc-testnet'), - logoUrl: getLogoUrl(ChainId.BSC_TESTNET), - blockExplorer: { - name: 'BSCScan (Testnet)', - url: 'https://testnet.bscscan.com/', - }, - nativeCurrency: { - symbol: 'tBNB', - name: 'Testnet BNB', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.OPTIMISM, - type: NetworkType.MAINNET, - name: 'optimism', - title: 'Optimism', - rpcUrl: getRpcUrl('optimism'), - logoUrl: getLogoUrl(ChainId.OPTIMISM), - blockExplorer: { - name: 'Etherscan (Optimism)', - url: 'https://optimistic.etherscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.OPTIMISM_SEPOLIA, - type: NetworkType.TESTNET, - name: 'optimism-sepolia', - title: 'Optimism Sepolia', - rpcUrl: getRpcUrl('optimism-sepolia'), - logoUrl: getLogoUrl(ChainId.OPTIMISM_SEPOLIA), - blockExplorer: { - name: 'Etherscan (Optimism Sepolia)', - url: 'https://sepolia-optimistic.etherscan.io/', - }, - nativeCurrency: { - symbol: 'sETH', - name: 'Sepolia Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.ARBITRUM, - type: NetworkType.MAINNET, - name: 'arbitrum', - title: 'Arbitrum One', - rpcUrl: getRpcUrl('arbitrum'), - logoUrl: getLogoUrl(ChainId.ARBITRUM), - blockExplorer: { - name: 'Arbiscan', - url: 'https://arbiscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.ARBITRUM_SEPOLIA, - type: NetworkType.TESTNET, - name: 'arbitrum-sepolia', - title: 'Arbitrum Sepolia', - rpcUrl: getRpcUrl('arbitrum-sepolia'), - logoUrl: getLogoUrl(ChainId.ARBITRUM_SEPOLIA), - blockExplorer: { - name: 'Arbiscan (Sepolia Testnet)', - url: 'https://sepolia.arbiscan.io/', - }, - nativeCurrency: { - symbol: 'sETH', - name: 'Sepolia Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.ARBITRUM_NOVA, - type: NetworkType.MAINNET, - name: 'arbitrum-nova', - title: 'Arbitrum Nova', - rpcUrl: getRpcUrl('arbitrum-nova'), - logoUrl: getLogoUrl(ChainId.ARBITRUM_NOVA), - blockExplorer: { - name: 'Arbiscan Nova', - url: 'https://nova.arbiscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.AVALANCHE, - type: NetworkType.MAINNET, - name: 'avalanche', - title: 'Avalanche', - rpcUrl: getRpcUrl('avalanche'), - logoUrl: getLogoUrl(ChainId.AVALANCHE), - blockExplorer: { - name: 'Snowtrace', - url: 'https://subnets.avax.network/c-chain/', - }, - nativeCurrency: { - symbol: 'AVAX', - name: 'AVAX', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.AVALANCHE_TESTNET, - type: NetworkType.TESTNET, - name: 'avalanche-testnet', - title: 'Avalanche Testnet', - rpcUrl: getRpcUrl('avalanche-testnet'), - logoUrl: getLogoUrl(ChainId.AVALANCHE_TESTNET), - blockExplorer: { - name: 'Snowtrace (Testnet)', - url: 'https://subnets-test.avax.network/c-chain/', - }, - nativeCurrency: { - symbol: 'tAVAX', - name: 'Testnet AVAX', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.GNOSIS, - type: NetworkType.MAINNET, - name: 'gnosis', - title: 'Gnosis Chain', - rpcUrl: getRpcUrl('gnosis'), - logoUrl: getLogoUrl(ChainId.GNOSIS), - blockExplorer: { - name: 'Gnosis Chain Explorer', - url: 'https://blockscout.com/xdai/mainnet/', - }, - nativeCurrency: { - symbol: 'XDAI', - name: 'XDAI', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BASE, - type: NetworkType.MAINNET, - name: 'base', - title: 'Base', - rpcUrl: getRpcUrl('base'), - logoUrl: getLogoUrl(ChainId.BASE), - blockExplorer: { - name: 'Base Explorer', - url: 'https://basescan.org/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BASE_SEPOLIA, - type: NetworkType.TESTNET, - name: 'base-sepolia', - title: 'Base Sepolia', - rpcUrl: getRpcUrl('base-sepolia'), - logoUrl: getLogoUrl(ChainId.BASE_SEPOLIA), - blockExplorer: { - name: 'Base Sepolia Explorer', - url: 'https://base-sepolia.blockscout.com/', - }, - nativeCurrency: { - symbol: 'sETH', - name: 'Sepolia Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.HOMEVERSE, - type: NetworkType.MAINNET, - name: 'homeverse', - title: 'Oasys Homeverse', - rpcUrl: getRpcUrl('homeverse'), - logoUrl: getLogoUrl(ChainId.HOMEVERSE), - blockExplorer: { - name: 'Oasys Homeverse Explorer', - url: 'https://explorer.oasys.homeverse.games/', - }, - nativeCurrency: { - symbol: 'OAS', - name: 'OAS', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.HOMEVERSE_TESTNET, - type: NetworkType.TESTNET, - name: 'homeverse-testnet', - title: 'Oasys Homeverse Testnet', - rpcUrl: getRpcUrl('homeverse-testnet'), - logoUrl: getLogoUrl(ChainId.HOMEVERSE_TESTNET), - blockExplorer: { - name: 'Oasys Homeverse Explorer (Testnet)', - url: 'https://explorer.testnet.oasys.homeverse.games/', - }, - nativeCurrency: { - symbol: 'tOAS', - name: 'Testnet OAS', - decimals: 18, - }, - contracts: { - multicall3: SEQUENCE_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.XAI, - type: NetworkType.MAINNET, - name: 'xai', - title: 'Xai', - rpcUrl: getRpcUrl('xai'), - logoUrl: getLogoUrl(ChainId.XAI), - blockExplorer: { - name: 'Xai Explorer', - url: 'https://explorer.xai-chain.net/', - }, - nativeCurrency: { - symbol: 'XAI', - name: 'XAI', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.XAI_SEPOLIA, - type: NetworkType.TESTNET, - name: 'xai-sepolia', - title: 'Xai Sepolia', - rpcUrl: getRpcUrl('xai-sepolia'), - logoUrl: getLogoUrl(ChainId.XAI_SEPOLIA), - blockExplorer: { - name: 'Xai Sepolia Explorer', - url: 'https://testnet-explorer-v2.xai-chain.net/', - }, - nativeCurrency: { - symbol: 'sXAI', - name: 'Sepolia XAI', - decimals: 18, - }, - contracts: { - multicall3: SEQUENCE_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.B3, - type: NetworkType.MAINNET, - name: 'b3', - title: 'B3', - rpcUrl: getRpcUrl('b3'), - logoUrl: getLogoUrl(ChainId.B3), - blockExplorer: { - name: 'B3 Explorer', - url: 'https://explorer.b3.fun/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.B3_SEPOLIA, - type: NetworkType.TESTNET, - name: 'b3-sepolia', - title: 'B3 Sepolia', - rpcUrl: getRpcUrl('b3-sepolia'), - logoUrl: getLogoUrl(ChainId.B3_SEPOLIA), - blockExplorer: { - name: 'B3 Sepolia Explorer', - url: 'https://sepolia.explorer.b3.fun/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.APECHAIN, - type: NetworkType.MAINNET, - name: 'apechain', - title: 'APE Chain', - rpcUrl: getRpcUrl('apechain'), - logoUrl: getLogoUrl(ChainId.APECHAIN), - blockExplorer: { - name: 'APE Chain Explorer', - url: 'https://apechain.calderaexplorer.xyz/', - }, - nativeCurrency: { - symbol: 'APE', - name: 'ApeCoin', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.APECHAIN_TESTNET, - type: NetworkType.TESTNET, - name: 'apechain-testnet', - title: 'APE Chain Testnet', - rpcUrl: getRpcUrl('apechain-testnet'), - logoUrl: getLogoUrl(ChainId.APECHAIN_TESTNET), - blockExplorer: { - name: 'APE Chain Explorer', - url: 'https://curtis.explorer.caldera.xyz/', - }, - nativeCurrency: { - symbol: 'APE', - name: 'ApeCoin', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BLAST, - type: NetworkType.MAINNET, - name: 'blast', - title: 'Blast', - rpcUrl: getRpcUrl('blast'), - logoUrl: getLogoUrl(ChainId.BLAST), - blockExplorer: { - name: 'Blast Explorer', - url: 'https://blastscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.BLAST_SEPOLIA, - type: NetworkType.TESTNET, - name: 'blast-sepolia', - title: 'Blast Sepolia', - rpcUrl: getRpcUrl('blast-sepolia'), - logoUrl: getLogoUrl(ChainId.BLAST_SEPOLIA), - blockExplorer: { - name: 'Blast Sepolia Explorer', - url: 'https://sepolia.blastexplorer.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.TELOS, - type: NetworkType.MAINNET, - name: 'telos', - title: 'Telos', - rpcUrl: getRpcUrl('telos'), - logoUrl: getLogoUrl(ChainId.TELOS), - blockExplorer: { - name: 'Telos Explorer', - url: 'https://explorer.telos.net/network/', - }, - nativeCurrency: { - symbol: 'TLOS', - name: 'TLOS', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.TELOS_TESTNET, - type: NetworkType.TESTNET, - name: 'telos-testnet', - title: 'Telos Testnet', - rpcUrl: getRpcUrl('telos-testnet'), - logoUrl: getLogoUrl(ChainId.TELOS_TESTNET), - blockExplorer: { - name: 'Telos Testnet Explorer', - url: 'https://explorer-test.telos.net/network', - }, - nativeCurrency: { - symbol: 'TLOS', - name: 'TLOS', - decimals: 18, - }, - contracts: { - multicall3: SEQUENCE_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.SKALE_NEBULA, - type: NetworkType.MAINNET, - name: 'skale-nebula', - title: 'SKALE Nebula Gaming Hub', - rpcUrl: getRpcUrl('skale-nebula'), - logoUrl: getLogoUrl(ChainId.SKALE_NEBULA), - blockExplorer: { - name: 'SKALE Nebula Gaming Hub Explorer', - url: 'https://green-giddy-denebola.explorer.mainnet.skalenodes.com/', - }, - nativeCurrency: { - symbol: 'sFUEL', - name: 'SKALE Fuel', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.SKALE_NEBULA_TESTNET, - type: NetworkType.TESTNET, - name: 'skale-nebula-testnet', - title: 'SKALE Nebula Gaming Hub Testnet', - rpcUrl: getRpcUrl('skale-nebula-testnet'), - logoUrl: getLogoUrl(ChainId.SKALE_NEBULA_TESTNET), - blockExplorer: { - name: 'SKALE Nebula Gaming Hub Testnet Explorer', - url: 'https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com/', - }, - nativeCurrency: { - symbol: 'sFUEL', - name: 'SKALE Fuel', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.SONEIUM, - type: NetworkType.MAINNET, - name: 'soneium', - title: 'Soneium', - rpcUrl: getRpcUrl('soneium'), - logoUrl: getLogoUrl(ChainId.SONEIUM), - blockExplorer: { - name: 'Soneium Explorer', - url: 'https://soneium.blockscout.com/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.SONEIUM_MINATO, - type: NetworkType.TESTNET, - name: 'soneium-minato', - title: 'Soneium Minato (Testnet)', - rpcUrl: getRpcUrl('soneium-minato'), - logoUrl: getLogoUrl(ChainId.SONEIUM_MINATO), - blockExplorer: { - name: 'Soneium Minato Explorer', - url: 'https://explorer-testnet.soneium.org/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.TOY_TESTNET, - type: NetworkType.TESTNET, - name: 'toy-testnet', - title: 'TOY (Testnet)', - rpcUrl: getRpcUrl('toy-testnet'), - logoUrl: getLogoUrl(ChainId.TOY_TESTNET), - blockExplorer: { - name: 'TOY Testnet Explorer', - url: 'https://toy-chain-testnet.explorer.caldera.xyz/', - }, - nativeCurrency: { - symbol: 'TOY', - name: 'TOY', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.IMMUTABLE_ZKEVM, - type: NetworkType.MAINNET, - name: 'immutable-zkevm', - title: 'Immutable zkEVM', - rpcUrl: getRpcUrl('immutable-zkevm'), - logoUrl: getLogoUrl(ChainId.IMMUTABLE_ZKEVM), - blockExplorer: { - name: 'Immutable zkEVM Explorer', - url: 'https://explorer.immutable.com/', - }, - nativeCurrency: { - symbol: 'IMX', - name: 'IMX', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.IMMUTABLE_ZKEVM_TESTNET, - type: NetworkType.TESTNET, - name: 'immutable-zkevm-testnet', - title: 'Immutable zkEVM Testnet', - rpcUrl: getRpcUrl('immutable-zkevm-testnet'), - logoUrl: getLogoUrl(ChainId.IMMUTABLE_ZKEVM_TESTNET), - blockExplorer: { - name: 'Immutable zkEVM Testnet Explorer', - url: 'https://explorer.testnet.immutable.com/', - }, - nativeCurrency: { - symbol: 'IMX', - name: 'IMX', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.MOONBEAM, - type: NetworkType.MAINNET, - name: 'moonbeam', - title: 'Moonbeam', - rpcUrl: getRpcUrl('moonbeam'), - logoUrl: getLogoUrl(ChainId.MOONBEAM), - blockExplorer: { - name: 'Moonscan', - url: 'https://moonscan.io/', - }, - nativeCurrency: { - symbol: 'GLMR', - name: 'GLMR', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.MOONBASE_ALPHA, - type: NetworkType.TESTNET, - name: 'moonbase-alpha', - title: 'Moonbase Alpha', - rpcUrl: getRpcUrl('moonbase-alpha'), - logoUrl: getLogoUrl(ChainId.MOONBASE_ALPHA), - blockExplorer: { - name: 'Moonscan (Moonbase Alpha)', - url: 'https://moonbase.moonscan.io/', - }, - nativeCurrency: { - symbol: 'GLMR', - name: 'GLMR', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.ETHERLINK, - type: NetworkType.MAINNET, - name: 'etherlink', - title: 'Etherlink', - rpcUrl: getRpcUrl('etherlink'), - logoUrl: getLogoUrl(ChainId.ETHERLINK), - blockExplorer: { - name: 'Etherlink Explorer', - url: 'https://explorer.etherlink.com/', - }, - nativeCurrency: { - symbol: 'XTZ', - name: 'Tez', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.ETHERLINK_SHADOWNET_TESTNET, - type: NetworkType.TESTNET, - name: 'etherlink-shadownet-testnet', - title: 'Etherlink Shadownet Testnet', - rpcUrl: getRpcUrl('etherlink-shadownet-testnet'), - logoUrl: getLogoUrl(ChainId.ETHERLINK_SHADOWNET_TESTNET), - blockExplorer: { - name: 'Etherlink Shadownet Testnet Explorer', - url: 'https://shadownet.explorer.etherlink.com/', - }, - nativeCurrency: { - symbol: 'XTZ', - name: 'Tez', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.MONAD, - type: NetworkType.MAINNET, - name: 'monad', - title: 'Monad', - rpcUrl: getRpcUrl('monad'), - logoUrl: getLogoUrl(ChainId.MONAD), - blockExplorer: { - name: 'Monad Explorer', - url: 'https://mainnet-beta.monvision.io/', - }, - nativeCurrency: { - symbol: 'MON', - name: 'MON', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - { - chainId: ChainId.MONAD_TESTNET, - type: NetworkType.TESTNET, - name: 'monad-testnet', - title: 'Monad Testnet', - rpcUrl: getRpcUrl('monad-testnet'), - logoUrl: getLogoUrl(ChainId.MONAD_TESTNET), - blockExplorer: { - name: 'Monad Testnet Explorer', - url: 'https://testnet.monadexplorer.com/', - }, - nativeCurrency: { - symbol: 'MON', - name: 'MON', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.SOMNIA, - type: NetworkType.MAINNET, - name: 'somnia', - title: 'Somnia', - rpcUrl: getRpcUrl('somnia'), - logoUrl: getLogoUrl(ChainId.SOMNIA), - blockExplorer: { - name: 'Somnia Explorer', - url: 'https://mainnet.somnia.w3us.site/', - }, - nativeCurrency: { - symbol: 'SOMI', - name: 'SOMI', - decimals: 18, - }, - contracts: { - multicall3: SEQUENCE_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.SOMNIA_TESTNET, - type: NetworkType.TESTNET, - name: 'somnia-testnet', - title: 'Somnia Testnet', - rpcUrl: getRpcUrl('somnia-testnet'), - logoUrl: getLogoUrl(ChainId.SOMNIA_TESTNET), - blockExplorer: { - name: 'Somnia Testnet Explorer', - url: 'https://somnia-testnet.socialscan.io/', - }, - nativeCurrency: { - symbol: 'STT', - name: 'STT', - decimals: 18, - }, - contracts: { - multicall3: SEQUENCE_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.INCENTIV, - type: NetworkType.MAINNET, - name: 'incentiv', - title: 'Incentiv', - rpcUrl: getRpcUrl('incentiv'), - logoUrl: getLogoUrl(ChainId.INCENTIV), - blockExplorer: { - name: 'Incentiv Explorer', - url: 'https://explorer.incentiv.io/', - }, - nativeCurrency: { - symbol: 'CENT', - name: 'CENT', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.INCENTIV_TESTNET_V2, - type: NetworkType.TESTNET, - name: 'incentiv-testnet-v2', - title: 'Incentiv Testnet', - rpcUrl: getRpcUrl('incentiv-testnet-v2'), - logoUrl: getLogoUrl(ChainId.INCENTIV_TESTNET_V2), - blockExplorer: { - name: 'Incentiv Testnet Explorer', - url: 'https://explorer.testnet.incentiv.net/', - }, - nativeCurrency: { - symbol: 'TCENT', - name: 'TCENT', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.KATANA, - type: NetworkType.MAINNET, - name: 'katana', - title: 'Katana', - rpcUrl: getRpcUrl('katana'), - logoUrl: getLogoUrl(ChainId.KATANA), - blockExplorer: { - name: 'Katana Explorer', - url: 'https://katanascan.com/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'ETH', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.SANDBOX_TESTNET, - type: NetworkType.TESTNET, - name: 'sandbox-testnet', - title: 'Sandbox Testnet', - rpcUrl: getRpcUrl('sandbox-testnet'), - logoUrl: getLogoUrl(ChainId.SANDBOX_TESTNET), - blockExplorer: { - name: 'Sandbox Testnet Explorer', - url: 'https://sandbox-testnet.explorer.caldera.xyz/', - }, - nativeCurrency: { - symbol: 'SAND', - name: 'SAND', - decimals: 18, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.ARC_TESTNET, - type: NetworkType.TESTNET, - name: 'arc-testnet', - title: 'Arc Testnet', - rpcUrl: getRpcUrl('arc-testnet'), - logoUrl: getLogoUrl(ChainId.ARC_TESTNET), - blockExplorer: { - name: 'Arc Testnet Explorer', - url: 'https://1jr2dw1zdqvyes8u.blockscout.com/', - }, - nativeCurrency: { - symbol: 'USDC', - name: 'USDC', - decimals: 6, - }, - contracts: { - multicall3: DEFAULT_MULTICALL3_ADDRESS, - }, - }, - - { - chainId: ChainId.HYPEREVM, - type: NetworkType.MAINNET, - name: 'hyperevm', - title: 'HyperEVM', - rpcUrl: getRpcUrl('hyperevm'), - logoUrl: getLogoUrl(ChainId.HYPEREVM), - blockExplorer: { - name: 'HyperEVM Explorer', - url: 'https://www.hyperscan.com/', - }, - nativeCurrency: { - symbol: 'HYPE', - name: 'HYPE', - decimals: 18, - }, - }, - - { - chainId: ChainId.BERACHAIN, - type: NetworkType.MAINNET, - name: 'berachain', - title: 'Berachain', - rpcUrl: getRpcUrl('berachain'), - logoUrl: getLogoUrl(ChainId.BERACHAIN), - blockExplorer: { - name: 'Berachain Explorer', - url: 'https://berascan.com/', - }, - nativeCurrency: { - symbol: 'BEAR', - name: 'BEAR', - decimals: 18, - }, - }, - - { - chainId: ChainId.SONIC, - type: NetworkType.MAINNET, - name: 'sonic', - title: 'Sonic', - rpcUrl: getRpcUrl('sonic'), - logoUrl: getLogoUrl(ChainId.SONIC), - blockExplorer: { - name: 'Sonic Explorer', - url: 'https://sonicscan.com/', - }, - nativeCurrency: { - symbol: 'S', - name: 'Sonic', - decimals: 18, - }, - }, -] - -function getRpcUrl(networkName: string): string { - return `https://nodes.sequence.app/${networkName}` -} - -function getLogoUrl(chainId: ChainId): string { - return `https://assets.sequence.info/images/networks/medium/${chainId}.webp` -} - -export function getNetworkFromName(networkName: string): Network | undefined { - return ALL.find((network) => network.name === networkName) -} - -export function getNetworkFromChainId(chainId: ChainId | number | bigint | string): Network | undefined { - return ALL.find((network) => network.chainId === Number(chainId)) -} diff --git a/packages/wallet/primitives/src/payload.ts b/packages/wallet/primitives/src/payload.ts deleted file mode 100644 index c933a6118e..0000000000 --- a/packages/wallet/primitives/src/payload.ts +++ /dev/null @@ -1,955 +0,0 @@ -import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex } from 'ox' -import { getSignPayload } from 'ox/TypedData' -import { EXECUTE_USER_OP, RECOVER_SAPIENT_SIGNATURE } from './constants.js' -import { Attestation } from './index.js' -import { minBytesFor } from './utils.js' -import { UserOperation } from 'ox/erc4337' - -export const KIND_TRANSACTIONS = 0x00 -export const KIND_MESSAGE = 0x01 -export const KIND_CONFIG_UPDATE = 0x02 -export const KIND_DIGEST = 0x03 - -export const BEHAVIOR_IGNORE_ERROR = 0x00 -export const BEHAVIOR_REVERT_ON_ERROR = 0x01 -export const BEHAVIOR_ABORT_ON_ERROR = 0x02 - -interface SolidityCall { - to: Address.Address - value: bigint - data: Hex.Hex - gasLimit: bigint - delegateCall: boolean - onlyFallback: boolean - behaviorOnError: bigint -} - -export interface SolidityDecoded { - kind: number - noChainId: boolean - calls: SolidityCall[] - space: bigint - nonce: bigint - message: Hex.Hex - imageHash: Hex.Hex - digest: Hex.Hex - parentWallets: Address.Address[] -} - -export type Call = { - to: Address.Address - value: bigint - data: Hex.Hex - gasLimit: bigint - delegateCall: boolean - onlyFallback: boolean - behaviorOnError: 'ignore' | 'revert' | 'abort' -} - -export type Calls = { - type: 'call' - space: bigint - nonce: bigint - calls: Call[] -} - -export type Message = { - type: 'message' - message: Hex.Hex -} - -export type ConfigUpdate = { - type: 'config-update' - imageHash: Hex.Hex -} - -export type Digest = { - type: 'digest' - digest: Hex.Hex -} - -export type SessionImplicitAuthorize = { - type: 'session-implicit-authorize' - sessionAddress: Address.Address - attestation: Attestation.Attestation -} - -export type Parent = { - parentWallets?: Address.Address[] -} - -export type Calls4337_07 = { - type: 'call_4337_07' - calls: Call[] - entrypoint: Address.Address - callGasLimit: bigint - maxFeePerGas: bigint - maxPriorityFeePerGas: bigint - space: bigint - nonce: bigint - paymaster?: Address.Address | undefined - paymasterData?: Hex.Hex | undefined - paymasterPostOpGasLimit?: bigint | undefined - paymasterVerificationGasLimit?: bigint | undefined - preVerificationGas: bigint - verificationGasLimit: bigint - factory?: Address.Address | undefined - factoryData?: Hex.Hex | undefined -} - -export type Recovery = T & { - recovery: true -} - -export type MayRecoveryPayload = Calls | Message | ConfigUpdate | Digest - -export type Payload = - | Calls - | Message - | ConfigUpdate - | Digest - | Recovery - | SessionImplicitAuthorize - | Calls4337_07 - -export type Parented = Payload & Parent - -export type TypedDataToSign = { - domain: { - name: string - version: string - chainId: number - verifyingContract: Address.Address - } - types: Record> - primaryType: string - message: Record -} - -export function fromMessage(message: Hex.Hex): Message { - return { - type: 'message', - message, - } -} - -export function fromConfigUpdate(imageHash: Hex.Hex): ConfigUpdate { - return { - type: 'config-update', - imageHash, - } -} - -export function fromDigest(digest: Hex.Hex): Digest { - return { - type: 'digest', - digest, - } -} - -export function fromCall(nonce: bigint, space: bigint, calls: Call[]): Calls { - return { - type: 'call', - nonce, - space, - calls, - } -} - -export function isCalls(payload: Payload): payload is Calls { - return payload.type === 'call' -} - -export function isMessage(payload: Payload): payload is Message { - return payload.type === 'message' -} - -export function isConfigUpdate(payload: Payload): payload is ConfigUpdate { - return payload.type === 'config-update' -} - -export function isDigest(payload: Payload): payload is Digest { - return payload.type === 'digest' -} - -export function isRecovery(payload: Payload): payload is Recovery { - if (isSessionImplicitAuthorize(payload)) { - return false - } - - return (payload as Recovery).recovery === true -} - -export function isCalls4337_07(payload: Payload): payload is Calls4337_07 { - return payload.type === 'call_4337_07' -} - -export function isParented(payload: Payload): payload is Parented { - return 'parentWallets' in payload -} - -export function toRecovery(payload: T): Recovery { - if (isRecovery(payload)) { - return payload - } - - return { - ...payload, - recovery: true, - } -} - -export function isSessionImplicitAuthorize(payload: Payload): payload is SessionImplicitAuthorize { - return payload.type === 'session-implicit-authorize' -} - -export function encode(payload: Calls, self?: Address.Address): Bytes.Bytes { - const callsLen = payload.calls.length - const nonceBytesNeeded = minBytesFor(payload.nonce) - if (nonceBytesNeeded > 15) { - throw new Error('Nonce is too large') - } - - /* - globalFlag layout: - bit 0: spaceZeroFlag => 1 if space == 0, else 0 - bits [1..3]: how many bytes we use to encode nonce - bit 4: singleCallFlag => 1 if there's exactly one call - bit 5: callsCountSizeFlag => 1 if #calls stored in 2 bytes, 0 if in 1 byte - (bits [6..7] are unused/free) - */ - let globalFlag = 0 - - if (payload.space === 0n) { - globalFlag |= 0x01 - } - - // bits [1..3] => how many bytes for the nonce - globalFlag |= nonceBytesNeeded << 1 - - // bit [4] => singleCallFlag - if (callsLen === 1) { - globalFlag |= 0x10 - } - - /* - If there's more than one call, we decide if we store the #calls in 1 or 2 bytes. - bit [5] => callsCountSizeFlag: 1 => 2 bytes, 0 => 1 byte - */ - let callsCountSize = 0 - if (callsLen !== 1) { - if (callsLen < 256) { - callsCountSize = 1 - } else if (callsLen < 65536) { - callsCountSize = 2 - globalFlag |= 0x20 - } else { - throw new Error('Too many calls') - } - } - - // Start building the output - // We'll accumulate in a Bytes object as we go - let out = Bytes.fromNumber(globalFlag, { size: 1 }) - - // If space isn't 0, store it as exactly 20 bytes (like uint160) - if (payload.space !== 0n) { - const spaceBytes = Bytes.padLeft(Bytes.fromNumber(payload.space), 20) - out = Bytes.concat(out, spaceBytes) - } - - // Encode nonce in nonceBytesNeeded - if (nonceBytesNeeded > 0) { - // We'll store nonce in exactly nonceBytesNeeded bytes - const nonceBytes = Bytes.padLeft(Bytes.fromNumber(payload.nonce), nonceBytesNeeded) - out = Bytes.concat(out, nonceBytes) - } - - // Store callsLen if not single-call - if (callsLen !== 1) { - if (callsCountSize === 1) { - out = Bytes.concat(out, Bytes.fromNumber(callsLen, { size: 1 })) - } else { - // callsCountSize === 2 - out = Bytes.concat(out, Bytes.fromNumber(callsLen, { size: 2 })) - } - } - - // Now encode each call - for (const call of payload.calls) { - /* - call flags layout (1 byte): - bit 0 => toSelf (call.to == this) - bit 1 => hasValue (call.value != 0) - bit 2 => hasData (call.data.length > 0) - bit 3 => hasGasLimit (call.gasLimit != 0) - bit 4 => delegateCall - bit 5 => onlyFallback - bits [6..7] => behaviorOnError => 0=ignore, 1=revert, 2=abort - */ - let flags = 0 - - if (self && Address.isEqual(call.to, self)) { - flags |= 0x01 - } - - if (call.value !== 0n) { - flags |= 0x02 - } - - if (call.data && call.data.length > 0) { - flags |= 0x04 - } - - if (call.gasLimit !== 0n) { - flags |= 0x08 - } - - if (call.delegateCall) { - flags |= 0x10 - } - - if (call.onlyFallback) { - flags |= 0x20 - } - - flags |= encodeBehaviorOnError(call.behaviorOnError) << 6 - - out = Bytes.concat(out, Bytes.fromNumber(flags, { size: 1 })) - - // If toSelf bit not set, store 20-byte address - if ((flags & 0x01) === 0) { - const addrBytes = Bytes.fromHex(call.to) - if (addrBytes.length !== 20) { - throw new Error(`Invalid 'to' address: ${call.to}`) - } - out = Bytes.concat(out, addrBytes) - } - - // If hasValue, store 32 bytes of value - if ((flags & 0x02) !== 0) { - const valueBytes = Bytes.padLeft(Bytes.fromNumber(call.value), 32) - out = Bytes.concat(out, valueBytes) - } - - // If hasData, store 3 bytes of data length + data - if ((flags & 0x04) !== 0) { - const dataLen = Bytes.fromHex(call.data).length - if (dataLen > 0xffffff) { - throw new Error('Data too large') - } - // 3 bytes => up to 16,777,215 - const dataLenBytes = Bytes.fromNumber(dataLen, { size: 3 }) - out = Bytes.concat(out, dataLenBytes, Bytes.fromHex(call.data)) - } - - // If hasGasLimit, store 32 bytes of gasLimit - if ((flags & 0x08) !== 0) { - const gasBytes = Bytes.padLeft(Bytes.fromNumber(call.gasLimit), 32) - out = Bytes.concat(out, gasBytes) - } - } - - return out -} - -export function encodeSapient( - chainId: number, - payload: Parented, -): Exclude[0], undefined>[0] { - const encoded: ReturnType = { - kind: 0, - noChainId: !chainId, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - - switch (payload.type) { - case 'call': - encoded.kind = 0 - encoded.calls = payload.calls.map((call) => ({ - ...call, - data: call.data, - behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)), - })) - encoded.space = payload.space - encoded.nonce = payload.nonce - break - - case 'message': - encoded.kind = 1 - encoded.message = payload.message - break - - case 'config-update': - encoded.kind = 2 - encoded.imageHash = payload.imageHash - break - - case 'digest': - encoded.kind = 3 - encoded.digest = payload.digest - break - } - - return encoded -} - -export function hash(wallet: Address.Address, chainId: number, payload: Parented): Bytes.Bytes { - if (isDigest(payload)) { - return Bytes.fromHex(payload.digest) - } - if (isSessionImplicitAuthorize(payload)) { - return Attestation.hash(payload.attestation) - } - const typedData = toTyped(wallet, chainId, payload) - return Bytes.fromHex(getSignPayload(typedData)) -} - -function domainFor( - payload: Payload, - wallet: Address.Address, - chainId: number, -): { - name: string - version: string - chainId: number - verifyingContract: Address.Address -} { - if (isRecovery(payload)) { - return { - name: 'Sequence Wallet - Recovery Mode', - version: '1', - chainId: Number(chainId), - verifyingContract: wallet, - } - } - - return { - name: 'Sequence Wallet', - version: '3', - chainId: Number(chainId), - verifyingContract: wallet, - } -} - -export function encode4337Nonce(key: bigint, seq: bigint): bigint { - if (key > 6277101735386680763835789423207666416102355444464034512895n) throw new RangeError('key exceeds 192 bits') - if (seq > 18446744073709551615n) throw new RangeError('seq exceeds 64 bits') - return (key << 64n) | seq -} - -export function toTyped(wallet: Address.Address, chainId: number, payload: Parented): TypedDataToSign { - const domain = domainFor(payload, wallet, chainId) - - switch (payload.type) { - case 'call': { - // This matches the EIP712 structure used in our hash() function - const types = { - Calls: [ - { name: 'calls', type: 'Call[]' }, - { name: 'space', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'wallets', type: 'address[]' }, - ], - Call: [ - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'gasLimit', type: 'uint256' }, - { name: 'delegateCall', type: 'bool' }, - { name: 'onlyFallback', type: 'bool' }, - { name: 'behaviorOnError', type: 'uint256' }, - ], - } - - // We ensure 'behaviorOnError' is turned into a numeric value - const message = { - calls: payload.calls.map((call) => ({ - to: call.to, - value: call.value.toString(), - data: call.data, - gasLimit: call.gasLimit.toString(), - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)).toString(), - })), - space: payload.space.toString(), - nonce: payload.nonce.toString(), - wallets: payload.parentWallets ?? [], - } - - return { - domain, - types, - primaryType: 'Calls', - message, - } - } - - case 'message': { - const types = { - Message: [ - { name: 'message', type: 'bytes' }, - { name: 'wallets', type: 'address[]' }, - ], - } - - const message = { - message: payload.message, - wallets: payload.parentWallets ?? [], - } - - return { - domain, - types, - primaryType: 'Message', - message, - } - } - - case 'config-update': { - const types = { - ConfigUpdate: [ - { name: 'imageHash', type: 'bytes32' }, - { name: 'wallets', type: 'address[]' }, - ], - } - - const message = { - imageHash: payload.imageHash, - wallets: payload.parentWallets ?? [], - } - - return { - domain, - types, - primaryType: 'ConfigUpdate', - message, - } - } - - case 'digest': { - throw new Error('Digest does not support typed data - Use message instead') - } - - case 'session-implicit-authorize': { - throw new Error('Payload does not support typed data') - } - - case 'call_4337_07': { - const subPayload: Message = { - type: 'message', - message: to4337Message(payload, wallet, chainId), - } - - return toTyped(wallet, chainId, subPayload) - } - } -} - -export function to4337UserOperation( - payload: Calls4337_07, - wallet: Address.Address, - signature?: Hex.Hex, -): UserOperation.UserOperation<'0.7'> { - const callsPayload: Calls = { - type: 'call', - space: 0n, - nonce: 0n, - calls: payload.calls, - } - const packedCalls = Hex.fromBytes(encode(callsPayload)) - const operation: UserOperation.UserOperation<'0.7', false> = { - sender: wallet, - nonce: encode4337Nonce(payload.space, payload.nonce), - callData: AbiFunction.encodeData(EXECUTE_USER_OP, [packedCalls]), - callGasLimit: payload.callGasLimit, - maxFeePerGas: payload.maxFeePerGas, - maxPriorityFeePerGas: payload.maxPriorityFeePerGas, - preVerificationGas: payload.preVerificationGas, - verificationGasLimit: payload.verificationGasLimit, - factory: payload.factory, - factoryData: payload.factoryData, - paymaster: payload.paymaster, - paymasterData: payload.paymasterData, - paymasterPostOpGasLimit: payload.paymasterPostOpGasLimit, - paymasterVerificationGasLimit: payload.paymasterVerificationGasLimit, - signature, - } - - return operation -} - -export function to4337Message(payload: Calls4337_07, wallet: Address.Address, chainId: number): Hex.Hex { - const operation = to4337UserOperation(payload, wallet) - const accountGasLimits = Hex.concat( - Hex.padLeft(Hex.fromNumber(operation.verificationGasLimit), 16), - Hex.padLeft(Hex.fromNumber(operation.callGasLimit), 16), - ) - const gasFees = Hex.concat( - Hex.padLeft(Hex.fromNumber(operation.maxPriorityFeePerGas), 16), - Hex.padLeft(Hex.fromNumber(operation.maxFeePerGas), 16), - ) - const initCode_hashed = Hash.keccak256( - operation.factory && operation.factoryData ? Hex.concat(operation.factory, operation.factoryData) : '0x', - ) - const paymasterAndData_hashed = Hash.keccak256( - operation.paymaster - ? Hex.concat( - operation.paymaster, - Hex.padLeft(Hex.fromNumber(operation.paymasterVerificationGasLimit || 0), 16), - Hex.padLeft(Hex.fromNumber(operation.paymasterPostOpGasLimit || 0), 16), - operation.paymasterData || '0x', - ) - : '0x', - ) - - const packedUserOp = AbiParameters.encode( - [ - { type: 'address' }, - { type: 'uint256' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'uint256' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - ], - [ - operation.sender, - operation.nonce, - initCode_hashed, - Hash.keccak256(operation.callData), - accountGasLimits, - operation.preVerificationGas, - gasFees, - paymasterAndData_hashed, - ], - ) - - return AbiParameters.encode( - [{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }], - [Hash.keccak256(packedUserOp), payload.entrypoint, BigInt(chainId)], - ) -} - -export function encodeBehaviorOnError(behaviorOnError: Call['behaviorOnError']): number { - switch (behaviorOnError) { - case 'ignore': - return BEHAVIOR_IGNORE_ERROR - case 'revert': - return BEHAVIOR_REVERT_ON_ERROR - case 'abort': - return BEHAVIOR_ABORT_ON_ERROR - } -} - -export function hashCall(call: Call): Hex.Hex { - const CALL_TYPEHASH = Hash.keccak256( - Bytes.fromString( - 'Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)', - ), - ) - - return Hash.keccak256( - AbiParameters.encode( - [ - { type: 'bytes32' }, - { type: 'address' }, - { type: 'uint256' }, - { type: 'bytes32' }, - { type: 'uint256' }, - { type: 'bool' }, - { type: 'bool' }, - { type: 'uint256' }, - ], - [ - Hex.from(CALL_TYPEHASH), - Hex.from(call.to), - call.value, - Hex.from(Hash.keccak256(call.data)), - call.gasLimit, - call.delegateCall, - call.onlyFallback, - BigInt(encodeBehaviorOnError(call.behaviorOnError)), - ], - ), - ) -} - -export function decode(packed: Bytes.Bytes, self?: Address.Address): Calls { - let pointer = 0 - if (packed.length < 1) { - throw new Error('Invalid packed data: missing globalFlag') - } - - // Read globalFlag - const globalFlag = Bytes.toNumber(packed.slice(pointer, pointer + 1)) - pointer += 1 - - // bit 0 => spaceZeroFlag - const spaceZeroFlag = (globalFlag & 0x01) === 0x01 - let space = 0n - if (!spaceZeroFlag) { - if (pointer + 20 > packed.length) { - throw new Error('Invalid packed data: not enough bytes for space') - } - space = Bytes.toBigInt(packed.slice(pointer, pointer + 20)) - pointer += 20 - } - - // bits [1..3] => nonceSize - const nonceSize = (globalFlag >> 1) & 0x07 - let nonce = 0n - if (nonceSize > 0) { - if (pointer + nonceSize > packed.length) { - throw new Error('Invalid packed data: not enough bytes for nonce') - } - nonce = Bytes.toBigInt(packed.slice(pointer, pointer + nonceSize)) - pointer += nonceSize - } - - // bit [4] => singleCallFlag - let callsCount = 1 - const singleCallFlag = (globalFlag & 0x10) === 0x10 - if (!singleCallFlag) { - // bit [5] => callsCountSizeFlag => 1 => 2 bytes, 0 => 1 byte - const callsCountSizeFlag = (globalFlag & 0x20) === 0x20 - const countSize = callsCountSizeFlag ? 2 : 1 - if (pointer + countSize > packed.length) { - throw new Error('Invalid packed data: not enough bytes for callsCount') - } - callsCount = Bytes.toNumber(packed.slice(pointer, pointer + countSize)) - pointer += countSize - } - - const calls: Call[] = [] - for (let i = 0; i < callsCount; i++) { - if (pointer + 1 > packed.length) { - throw new Error('Invalid packed data: missing call flags') - } - const flags = Bytes.toNumber(packed.slice(pointer, pointer + 1)) - pointer += 1 - - // bit 0 => toSelf - let to: Address.Address - if ((flags & 0x01) === 0x01) { - if (!self) { - throw new Error('Missing "self" address for toSelf call') - } - to = self - } else { - if (pointer + 20 > packed.length) { - throw new Error('Invalid packed data: not enough bytes for address') - } - to = Bytes.toHex(packed.slice(pointer, pointer + 20)) as Address.Address - pointer += 20 - } - - // bit 1 => hasValue - let value = 0n - if ((flags & 0x02) === 0x02) { - if (pointer + 32 > packed.length) { - throw new Error('Invalid packed data: not enough bytes for value') - } - value = Bytes.toBigInt(packed.slice(pointer, pointer + 32)) - pointer += 32 - } - - // bit 2 => hasData - let data = Bytes.fromHex('0x') - if ((flags & 0x04) === 0x04) { - if (pointer + 3 > packed.length) { - throw new Error('Invalid packed data: not enough bytes for data length') - } - const dataLen = Bytes.toNumber(packed.slice(pointer, pointer + 3)) - pointer += 3 - if (pointer + dataLen > packed.length) { - throw new Error('Invalid packed data: not enough bytes for call data') - } - data = packed.slice(pointer, pointer + dataLen) - pointer += dataLen - } - - // bit 3 => hasGasLimit - let gasLimit = 0n - if ((flags & 0x08) === 0x08) { - if (pointer + 32 > packed.length) { - throw new Error('Invalid packed data: not enough bytes for gasLimit') - } - gasLimit = Bytes.toBigInt(packed.slice(pointer, pointer + 32)) - pointer += 32 - } - - // bits 4..5 => delegateCall, onlyFallback - const delegateCall = (flags & 0x10) === 0x10 - const onlyFallback = (flags & 0x20) === 0x20 - - // bits 6..7 => behaviorOnError - const behaviorCode = (flags & 0xc0) >> 6 - const behaviorOnError = decodeBehaviorOnError(behaviorCode) - - calls.push({ - to, - value, - data: Bytes.toHex(data), - gasLimit, - delegateCall, - onlyFallback, - behaviorOnError, - }) - } - - return { - type: 'call', - space, - nonce, - calls, - } -} - -export function decodeBehaviorOnError(value: number): Call['behaviorOnError'] { - switch (value) { - case 0: - return 'ignore' - case 1: - return 'revert' - case 2: - return 'abort' - default: - throw new Error(`Invalid behaviorOnError value: ${value}`) - } -} - -function parseBehaviorOnError(behavior: number): 'ignore' | 'revert' | 'abort' { - switch (behavior) { - case BEHAVIOR_IGNORE_ERROR: - return 'ignore' - case BEHAVIOR_REVERT_ON_ERROR: - return 'revert' - case BEHAVIOR_ABORT_ON_ERROR: - return 'abort' - default: - throw new Error(`Unknown behavior: ${behavior}`) - } -} - -export function fromAbiFormat(decoded: SolidityDecoded): Parented { - if (decoded.kind === KIND_TRANSACTIONS) { - return { - type: 'call', - nonce: decoded.nonce, - space: decoded.space, - calls: decoded.calls.map((call) => ({ - to: Address.from(call.to), - value: call.value, - data: call.data as `0x${string}`, - gasLimit: call.gasLimit, - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: parseBehaviorOnError(Number(call.behaviorOnError)), - })), - parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)), - } - } - - if (decoded.kind === KIND_MESSAGE) { - return { - type: 'message', - message: decoded.message as `0x${string}`, - parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)), - } - } - - if (decoded.kind === KIND_CONFIG_UPDATE) { - return { - type: 'config-update', - imageHash: decoded.imageHash as `0x${string}`, - parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)), - } - } - - if (decoded.kind === KIND_DIGEST) { - return { - type: 'digest', - digest: decoded.digest as `0x${string}`, - parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)), - } - } - - throw new Error('Not implemented') -} - -export function toAbiFormat(payload: Parented): SolidityDecoded { - if (payload.type === 'call') { - return { - kind: KIND_TRANSACTIONS, - noChainId: false, - calls: payload.calls.map((call) => ({ - to: call.to, - value: call.value, - data: call.data, - gasLimit: call.gasLimit, - delegateCall: call.delegateCall, - onlyFallback: call.onlyFallback, - behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)), - })), - space: payload.space, - nonce: payload.nonce, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - } - - if (payload.type === 'message') { - return { - kind: KIND_MESSAGE, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: payload.message, - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - } - - if (payload.type === 'config-update') { - return { - kind: KIND_CONFIG_UPDATE, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: payload.imageHash, - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - } - - if (payload.type === 'digest') { - return { - kind: KIND_DIGEST, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: payload.digest, - parentWallets: payload.parentWallets ?? [], - } - } - - throw new Error('Invalid payload type') -} diff --git a/packages/wallet/primitives/src/permission.ts b/packages/wallet/primitives/src/permission.ts deleted file mode 100644 index c2909696d8..0000000000 --- a/packages/wallet/primitives/src/permission.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { AbiParameters, Address, Bytes } from 'ox' - -export enum ParameterOperation { - EQUAL = 0, - NOT_EQUAL = 1, - GREATER_THAN_OR_EQUAL = 2, - LESS_THAN_OR_EQUAL = 3, -} - -export type ParameterRule = { - cumulative: boolean - operation: ParameterOperation - value: Bytes.Bytes - offset: bigint - mask: Bytes.Bytes -} - -export type Permission = { - target: Address.Address - rules: ParameterRule[] -} - -export type SessionPermissions = { - signer: Address.Address - chainId: number - valueLimit: bigint - deadline: bigint // uint64 - permissions: Permission[] -} - -export const MAX_PERMISSIONS_COUNT = 2 ** 7 - 1 -export const MAX_RULES_COUNT = 2 ** 8 - 1 - -export const MASK = { - SELECTOR: Bytes.padRight(Bytes.fromHex('0xffffffff'), 32), // Select intentionally pads right. Other values should pad left - ADDRESS: Bytes.padLeft(Bytes.fromHex('0xffffffffffffffffffffffffffffffffffffffff'), 32), - BOOL: Bytes.padLeft(Bytes.fromHex('0x01'), 32), - // Bytes - BYTES1: Bytes.padLeft(Bytes.from(Array(1).fill(0xff)), 32), - BYTES2: Bytes.padLeft(Bytes.from(Array(2).fill(0xff)), 32), - BYTES4: Bytes.padLeft(Bytes.from(Array(4).fill(0xff)), 32), - BYTES8: Bytes.padLeft(Bytes.from(Array(8).fill(0xff)), 32), - BYTES16: Bytes.padLeft(Bytes.from(Array(16).fill(0xff)), 32), - BYTES32: Bytes.padLeft(Bytes.from(Array(32).fill(0xff)), 32), - // Ints - INT8: Bytes.padLeft(Bytes.from(Array(1).fill(0xff)), 32), - INT16: Bytes.padLeft(Bytes.from(Array(2).fill(0xff)), 32), - INT32: Bytes.padLeft(Bytes.from(Array(4).fill(0xff)), 32), - INT64: Bytes.padLeft(Bytes.from(Array(8).fill(0xff)), 32), - INT128: Bytes.padLeft(Bytes.from(Array(16).fill(0xff)), 32), - INT256: Bytes.padLeft(Bytes.from(Array(32).fill(0xff)), 32), - // Uints - UINT8: Bytes.padLeft(Bytes.from(Array(1).fill(0xff)), 32), - UINT16: Bytes.padLeft(Bytes.from(Array(2).fill(0xff)), 32), - UINT32: Bytes.padLeft(Bytes.from(Array(4).fill(0xff)), 32), - UINT64: Bytes.padLeft(Bytes.from(Array(8).fill(0xff)), 32), - UINT128: Bytes.padLeft(Bytes.from(Array(16).fill(0xff)), 32), - UINT256: Bytes.padLeft(Bytes.from(Array(32).fill(0xff)), 32), -} - -// Encoding - -export function encodeSessionPermissions(sessionPermissions: SessionPermissions): Bytes.Bytes { - if (sessionPermissions.permissions.length > MAX_PERMISSIONS_COUNT) { - throw new Error('Too many permissions') - } - - const encodedPermissions = sessionPermissions.permissions.map(encodePermission) - - return Bytes.concat( - Bytes.padLeft(Bytes.fromHex(sessionPermissions.signer), 20), - Bytes.padLeft(Bytes.fromNumber(sessionPermissions.chainId), 32), - Bytes.padLeft(Bytes.fromNumber(sessionPermissions.valueLimit), 32), - Bytes.padLeft(Bytes.fromNumber(sessionPermissions.deadline, { size: 8 }), 8), - Bytes.fromNumber(sessionPermissions.permissions.length, { size: 1 }), - Bytes.concat(...encodedPermissions), - ) -} - -export function encodePermission(permission: Permission): Bytes.Bytes { - if (permission.rules.length > MAX_RULES_COUNT) { - throw new Error('Too many rules') - } - - const encodedRules = permission.rules.map(encodeParameterRule) - return Bytes.concat( - Bytes.padLeft(Bytes.fromHex(permission.target), 20), - Bytes.fromNumber(permission.rules.length, { size: 1 }), - Bytes.concat(...encodedRules), - ) -} - -function encodeParameterRule(rule: ParameterRule): Bytes.Bytes { - // Combine operation and cumulative flag into a single byte - // 0x[operationx3][cumulative] - const operationCumulative = (Number(rule.operation) << 1) | (rule.cumulative ? 1 : 0) - - return Bytes.concat( - Bytes.fromNumber(operationCumulative), - Bytes.padLeft(rule.value, 32), - Bytes.padLeft(Bytes.fromNumber(rule.offset), 32), - Bytes.padLeft(rule.mask, 32), - ) -} - -// Decoding - -export function decodeSessionPermissions(bytes: Bytes.Bytes): SessionPermissions { - const signer = Bytes.toHex(bytes.slice(0, 20)) - const chainId = Bytes.toNumber(bytes.slice(20, 52)) - const valueLimit = Bytes.toBigInt(bytes.slice(52, 84)) - const deadline = Bytes.toBigInt(bytes.slice(84, 92)) - const permissionsLength = Number(bytes[92]!) - const permissions = [] - let pointer = 93 - for (let i = 0; i < permissionsLength; i++) { - // Pass the remaining bytes instead of a fixed slice length - const { permission, consumed } = decodePermission(bytes.slice(pointer)) - permissions.push(permission) - pointer += consumed - } - if (permissions.length === 0) { - throw new Error('No permissions') - } - return { - signer, - chainId, - valueLimit, - deadline, - permissions: permissions, - } -} - -// Returns the permission and the number of bytes consumed in the permission block -function decodePermission(bytes: Bytes.Bytes): { permission: Permission; consumed: number } { - const target = Bytes.toHex(bytes.slice(0, 20)) - const rulesLength = Number(bytes[20]!) - const rules = [] - let pointer = 21 - for (let i = 0; i < rulesLength; i++) { - const ruleBytes = bytes.slice(pointer, pointer + 97) - rules.push(decodeParameterRule(ruleBytes)) - pointer += 97 - } - return { - permission: { - target, - rules, - }, - consumed: pointer, - } -} - -function decodeParameterRule(bytes: Bytes.Bytes): ParameterRule { - const operationCumulative = Number(bytes[0]!) - const cumulative = (operationCumulative & 1) === 1 - const operation = operationCumulative >> 1 - const value = bytes.slice(1, 33) - const offset = Bytes.toBigInt(bytes.slice(33, 65)) - const mask = bytes.slice(65, 97) - return { - cumulative, - operation, - value, - offset, - mask, - } -} - -// ABI encode - -export const permissionStructAbi = { - internalType: 'struct Permission', - name: 'permission', - type: 'tuple', - components: [ - { internalType: 'address', name: 'target', type: 'address' }, - { - internalType: 'struct ParameterRule[]', - name: 'rules', - type: 'tuple[]', - components: [ - { internalType: 'bool', name: 'cumulative', type: 'bool' }, - { - internalType: 'enum ParameterOperation', - name: 'operation', - type: 'uint8', - }, - { internalType: 'bytes32', name: 'value', type: 'bytes32' }, - { internalType: 'uint256', name: 'offset', type: 'uint256' }, - { internalType: 'bytes32', name: 'mask', type: 'bytes32' }, - ], - }, - ], -} as const - -export function abiEncodePermission(permission: Permission): string { - return AbiParameters.encode( - [permissionStructAbi], - [ - { - target: permission.target, - rules: permission.rules.map((rule) => ({ - cumulative: rule.cumulative, - operation: rule.operation, - value: Bytes.toHex(rule.value), - offset: rule.offset, - mask: Bytes.toHex(rule.mask), - })), - }, - ], - ) -} - -// JSON - -export function sessionPermissionsToJson(sessionPermissions: SessionPermissions): string { - return JSON.stringify(encodeSessionPermissionsForJson(sessionPermissions)) -} - -export function encodeSessionPermissionsForJson(sessionPermissions: SessionPermissions): any { - return { - signer: sessionPermissions.signer.toString(), - chainId: sessionPermissions.chainId.toString(), - valueLimit: sessionPermissions.valueLimit.toString(), - deadline: sessionPermissions.deadline.toString(), - permissions: sessionPermissions.permissions.map(encodePermissionForJson), - } -} - -export function permissionToJson(permission: Permission): string { - return JSON.stringify(encodePermissionForJson(permission)) -} - -function encodePermissionForJson(permission: Permission): any { - return { - target: permission.target.toString(), - rules: permission.rules.map(encodeParameterRuleForJson), - } -} - -export function parameterRuleToJson(rule: ParameterRule): string { - return JSON.stringify(encodeParameterRuleForJson(rule)) -} - -function encodeParameterRuleForJson(rule: ParameterRule): any { - return { - cumulative: rule.cumulative, - operation: rule.operation, - value: Bytes.toHex(rule.value), - offset: rule.offset.toString(), - mask: Bytes.toHex(rule.mask), - } -} - -export function sessionPermissionsFromJson(json: string): SessionPermissions { - return sessionPermissionsFromParsed(JSON.parse(json)) -} - -export function sessionPermissionsFromParsed(parsed: any): SessionPermissions { - return { - signer: Address.from(parsed.signer), - chainId: Number(parsed.chainId), - valueLimit: BigInt(parsed.valueLimit), - deadline: BigInt(parsed.deadline), - permissions: parsed.permissions.map(permissionFromParsed), - } -} - -export function permissionFromJson(json: string): Permission { - return permissionFromParsed(JSON.parse(json)) -} - -function permissionFromParsed(parsed: any): Permission { - return { - target: Address.from(parsed.target), - rules: parsed.rules.map((decoded: any) => ({ - cumulative: decoded.cumulative, - operation: decoded.operation, - value: Bytes.fromHex(decoded.value), - offset: BigInt(decoded.offset), - mask: Bytes.fromHex(decoded.mask), - })), - } -} diff --git a/packages/wallet/primitives/src/precondition.ts b/packages/wallet/primitives/src/precondition.ts deleted file mode 100644 index 1c3e1c4c58..0000000000 --- a/packages/wallet/primitives/src/precondition.ts +++ /dev/null @@ -1,117 +0,0 @@ -export interface Precondition { - type: string -} - -export interface NativeBalancePrecondition extends Precondition { - type: 'native-balance' - address: string - min?: bigint - max?: bigint -} - -export interface Erc20BalancePrecondition extends Precondition { - type: 'erc20-balance' - address: string - token: string - min?: bigint - max?: bigint -} - -export interface Erc20ApprovalPrecondition extends Precondition { - type: 'erc20-approval' - address: string - token: string - operator: string - min: bigint -} - -export interface Erc721OwnershipPrecondition extends Precondition { - type: 'erc721-ownership' - address: string - token: string - tokenId: bigint - owned?: boolean -} - -export interface Erc721ApprovalPrecondition extends Precondition { - type: 'erc721-approval' - address: string - token: string - tokenId: bigint - operator: string -} - -export interface Erc1155BalancePrecondition extends Precondition { - type: 'erc1155-balance' - address: string - token: string - tokenId: bigint - min?: bigint - max?: bigint -} - -export interface Erc1155ApprovalPrecondition extends Precondition { - type: 'erc1155-approval' - address: string - token: string - tokenId: bigint - operator: string - min: bigint -} - -export type AnyPrecondition = - | NativeBalancePrecondition - | Erc20BalancePrecondition - | Erc20ApprovalPrecondition - | Erc721OwnershipPrecondition - | Erc721ApprovalPrecondition - | Erc1155BalancePrecondition - | Erc1155ApprovalPrecondition - -export function isValidPreconditionType(type: string): type is AnyPrecondition['type'] { - return [ - 'native-balance', - 'erc20-balance', - 'erc20-approval', - 'erc721-ownership', - 'erc721-approval', - 'erc1155-balance', - 'erc1155-approval', - ].includes(type) -} - -export function createPrecondition(precondition: T): T { - if (!precondition || typeof precondition.type !== 'string' || !isValidPreconditionType(precondition.type)) { - throw new Error(`Invalid precondition object: missing or invalid 'type' property.`) - } - - return precondition -} - -export interface IntentPrecondition { - type: T['type'] - data: Omit - chainId?: number -} - -export function createIntentPrecondition( - precondition: T, - chainId?: number, -): IntentPrecondition { - const { type, ...data } = precondition - - if (!isValidPreconditionType(type)) { - throw new Error(`Invalid precondition type: ${type}`) - } - - const intent: IntentPrecondition = { - type: type, - data: data as Omit, - } - - if (chainId !== undefined) { - intent.chainId = chainId - } - - return intent -} diff --git a/packages/wallet/primitives/src/session-config.ts b/packages/wallet/primitives/src/session-config.ts deleted file mode 100644 index 38ba2056ca..0000000000 --- a/packages/wallet/primitives/src/session-config.ts +++ /dev/null @@ -1,826 +0,0 @@ -import { Address, Bytes, Hash, Hex } from 'ox' -import * as GenericTree from './generic-tree.js' -import { - decodeSessionPermissions, - encodeSessionPermissions, - encodeSessionPermissionsForJson, - SessionPermissions, - sessionPermissionsFromParsed, -} from './permission.js' -import { minBytesFor } from './utils.js' - -//FIXME Reorder by expected usage -export const SESSIONS_FLAG_PERMISSIONS = 0 -export const SESSIONS_FLAG_NODE = 1 -export const SESSIONS_FLAG_BRANCH = 2 -export const SESSIONS_FLAG_BLACKLIST = 3 -export const SESSIONS_FLAG_IDENTITY_SIGNER = 4 - -export type ImplicitBlacklistLeaf = { - type: 'implicit-blacklist' - blacklist: Address.Address[] -} - -export type IdentitySignerLeaf = { - type: 'identity-signer' - identitySigner: Address.Address -} - -export type SessionPermissionsLeaf = SessionPermissions & { - type: 'session-permissions' -} - -export type SessionNode = Hex.Hex // Hashed leaf -export type SessionLeaf = SessionPermissionsLeaf | ImplicitBlacklistLeaf | IdentitySignerLeaf -export type SessionBranch = [SessionsTopology, SessionsTopology, ...SessionsTopology[]] -export type SessionsTopology = SessionBranch | SessionLeaf | SessionNode - -const SESSIONS_NODE_SIZE_BYTES = 32 - -function isSessionsNode(topology: any): topology is SessionNode { - return Hex.validate(topology) && Hex.size(topology) === SESSIONS_NODE_SIZE_BYTES -} - -function isImplicitBlacklist(topology: any): topology is ImplicitBlacklistLeaf { - return typeof topology === 'object' && topology !== null && 'blacklist' in topology -} - -function isIdentitySignerLeaf(topology: any): topology is IdentitySignerLeaf { - return typeof topology === 'object' && topology !== null && 'identitySigner' in topology -} - -function isSessionPermissions(topology: any): topology is SessionPermissionsLeaf { - return typeof topology === 'object' && topology !== null && 'signer' in topology -} - -function isSessionsLeaf(topology: any): topology is SessionLeaf { - return isImplicitBlacklist(topology) || isIdentitySignerLeaf(topology) || isSessionPermissions(topology) -} - -function isSessionsBranch(topology: any): topology is SessionBranch { - return Array.isArray(topology) && topology.length >= 2 && topology.every((child) => isSessionsTopology(child)) -} - -export function isSessionsTopology(topology: any): topology is SessionsTopology { - return isSessionsBranch(topology) || isSessionsLeaf(topology) || isSessionsNode(topology) -} - -/** - * Checks if the topology is complete. - * A complete topology has at least one identity signer and one blacklist. - * When performing encoding, exactly one identity signer is required. Others must be hashed into nodes. - * @param topology The topology to check - * @returns True if the topology is complete - */ -export function isCompleteSessionsTopology(topology: any): topology is SessionsTopology { - // Ensure the object is a sessions topology - if (!isSessionsTopology(topology)) { - return false - } - // Check the topology contains at least one identity signer and exactly one blacklist - const { identitySignerCount, blacklistCount } = checkIsCompleteSessionsBranch(topology) - return identitySignerCount >= 1 && blacklistCount === 1 -} - -function checkIsCompleteSessionsBranch(topology: SessionsTopology): { - identitySignerCount: number - blacklistCount: number -} { - let thisHasIdentitySigner = 0 - let thisHasBlacklist = 0 - if (isSessionsBranch(topology)) { - for (const child of topology) { - const { identitySignerCount, blacklistCount } = checkIsCompleteSessionsBranch(child) - thisHasIdentitySigner += identitySignerCount - thisHasBlacklist += blacklistCount - } - } - if (isIdentitySignerLeaf(topology)) { - thisHasIdentitySigner++ - } - if (isImplicitBlacklist(topology)) { - thisHasBlacklist++ - } - return { identitySignerCount: thisHasIdentitySigner, blacklistCount: thisHasBlacklist } -} - -/** - * Gets the identity signers from the topology. - * @param topology The topology to get the identity signer from - * @returns The identity signers - */ -export function getIdentitySigners(topology: SessionsTopology): Address.Address[] { - if (isIdentitySignerLeaf(topology)) { - // Got one - return [topology.identitySigner] - } - - if (isSessionsBranch(topology)) { - // Check branches - return topology.map(getIdentitySigners).flat() - } - - return [] -} - -/** - * Gets the implicit blacklist from the topology. - * @param topology The topology to get the implicit blacklist from - * @returns The implicit blacklist or null if it's not present - */ -export function getImplicitBlacklist(topology: SessionsTopology): Address.Address[] | null { - const blacklistNode = getImplicitBlacklistLeaf(topology) - if (!blacklistNode) { - return null - } - return blacklistNode.blacklist -} - -/** - * Gets the implicit blacklist leaf from the topology. - * @param topology The topology to get the implicit blacklist leaf from - * @returns The implicit blacklist leaf or null if it's not present - */ -export function getImplicitBlacklistLeaf(topology: SessionsTopology): ImplicitBlacklistLeaf | null { - if (isImplicitBlacklist(topology)) { - // Got it - return topology - } - - if (isSessionsBranch(topology)) { - // Check branches - const results = topology.map(getImplicitBlacklistLeaf).filter((t) => t !== null) - if (results.length > 1) { - throw new Error('Multiple blacklists') - } - if (results.length === 1) { - return results[0]! - } - } - - return null -} - -export function getSessionPermissions( - topology: SessionsTopology, - address: Address.Address, -): SessionPermissionsLeaf | null { - if (isSessionPermissions(topology)) { - if (Address.isEqual(topology.signer, address)) { - return topology - } - } - if (isSessionsBranch(topology)) { - for (const child of topology) { - const result = getSessionPermissions(child, address) - if (result) { - return result - } - } - } - return null -} - -export function getExplicitSigners(topology: SessionsTopology): Address.Address[] { - return getExplicitSignersFromBranch(topology, []) -} - -function getExplicitSignersFromBranch(topology: SessionsTopology, current: Address.Address[]): Address.Address[] { - if (isSessionPermissions(topology)) { - return [...current, topology.signer] - } - if (isSessionsBranch(topology)) { - const result: Address.Address[] = [...current] - for (const child of topology) { - result.push(...getExplicitSignersFromBranch(child, current)) - } - return result - } - return current -} - -// Encode / decode to configuration tree - -/** - * Encodes a leaf to bytes. - * This can be Hash.keccak256'd to convert to a node.. - * @param leaf The leaf to encode - * @returns The encoded leaf - */ -export function encodeLeafToGeneric(leaf: SessionLeaf): GenericTree.Leaf { - if (isSessionPermissions(leaf)) { - return { - type: 'leaf', - value: Bytes.concat(Bytes.fromNumber(SESSIONS_FLAG_PERMISSIONS), encodeSessionPermissions(leaf)), - } - } - if (isImplicitBlacklist(leaf)) { - return { - type: 'leaf', - value: Bytes.concat( - Bytes.fromNumber(SESSIONS_FLAG_BLACKLIST), - Bytes.concat(...leaf.blacklist.map((b) => Bytes.padLeft(Bytes.fromHex(b), 20))), - ), - } - } - if (isIdentitySignerLeaf(leaf)) { - return { - type: 'leaf', - value: Bytes.concat( - Bytes.fromNumber(SESSIONS_FLAG_IDENTITY_SIGNER), - Bytes.padLeft(Bytes.fromHex(leaf.identitySigner), 20), - ), - } - } - // Unreachable - throw new Error('Invalid leaf') -} - -export function decodeLeafFromBytes(bytes: Bytes.Bytes): SessionLeaf { - const flag = bytes[0]! - if (flag === SESSIONS_FLAG_BLACKLIST) { - const blacklist: `0x${string}`[] = [] - for (let i = 1; i < bytes.length; i += 20) { - blacklist.push(Bytes.toHex(bytes.slice(i, i + 20))) - } - return { type: 'implicit-blacklist', blacklist } - } - if (flag === SESSIONS_FLAG_IDENTITY_SIGNER) { - return { type: 'identity-signer', identitySigner: Bytes.toHex(bytes.slice(1, 21)) } - } - if (flag === SESSIONS_FLAG_PERMISSIONS) { - return { type: 'session-permissions', ...decodeSessionPermissions(bytes.slice(1)) } - } - throw new Error('Invalid leaf') -} - -export function sessionsTopologyToConfigurationTree(topology: SessionsTopology): GenericTree.Tree { - if (isSessionsBranch(topology)) { - return topology.map(sessionsTopologyToConfigurationTree) as GenericTree.Branch - } - if (isImplicitBlacklist(topology) || isIdentitySignerLeaf(topology) || isSessionPermissions(topology)) { - return encodeLeafToGeneric(topology) - } - if (isSessionsNode(topology)) { - // A node is already encoded and hashed - return topology - } - throw new Error('Invalid topology') -} - -export function configurationTreeToSessionsTopology(tree: GenericTree.Tree): SessionsTopology { - if (GenericTree.isBranch(tree)) { - return tree.map(configurationTreeToSessionsTopology) as SessionBranch - } - - if (GenericTree.isNode(tree)) { - throw new Error('Unknown in configuration tree') - } - - return decodeLeafFromBytes(tree.value) -} - -// Encoding for contract validation - -/** - * Encodes a topology into bytes for contract validation. - * @param topology The topology to encode - * @returns The encoded topology - */ -export function encodeSessionsTopology(topology: SessionsTopology): Bytes.Bytes { - if (isSessionsBranch(topology)) { - const encodedBranches = [] - for (const node of topology) { - encodedBranches.push(encodeSessionsTopology(node)) - } - const encoded = Bytes.concat(...encodedBranches) - const encodedSize = minBytesFor(BigInt(encoded.length)) - if (encodedSize > 15) { - throw new Error('Branch too large') - } - const flagByte = (SESSIONS_FLAG_BRANCH << 4) | encodedSize - return Bytes.concat( - Bytes.fromNumber(flagByte), - Bytes.padLeft(Bytes.fromNumber(encoded.length), encodedSize), - encoded, - ) - } - - if (isSessionPermissions(topology)) { - const flagByte = SESSIONS_FLAG_PERMISSIONS << 4 - const encodedLeaf = encodeSessionPermissions(topology) - return Bytes.concat(Bytes.fromNumber(flagByte), encodedLeaf) - } - - if (isSessionsNode(topology)) { - const flagByte = SESSIONS_FLAG_NODE << 4 - return Bytes.concat(Bytes.fromNumber(flagByte), Hex.toBytes(topology)) - } - - if (isImplicitBlacklist(topology)) { - const encoded = Bytes.concat(...topology.blacklist.map((b) => Bytes.fromHex(b))) - if (topology.blacklist.length >= 0x0f) { - // If the blacklist is too large, we can't encode the length into the flag byte. - // Instead we encode 0x0f and the length in the next 2 bytes. - if (topology.blacklist.length > 0xffff) { - throw new Error('Blacklist too large') - } - return Bytes.concat( - Bytes.fromNumber((SESSIONS_FLAG_BLACKLIST << 4) | 0x0f), - Bytes.fromNumber(topology.blacklist.length, { size: 2 }), - encoded, - ) - } - // Encode the size into the flag byte - const flagByte = (SESSIONS_FLAG_BLACKLIST << 4) | topology.blacklist.length - return Bytes.concat(Bytes.fromNumber(flagByte), encoded) - } - - if (isIdentitySignerLeaf(topology)) { - const flagByte = SESSIONS_FLAG_IDENTITY_SIGNER << 4 - return Bytes.concat(Bytes.fromNumber(flagByte), Bytes.padLeft(Bytes.fromHex(topology.identitySigner), 20)) - } - - throw new Error('Invalid topology') -} - -export function decodeSessionsTopology(bytes: Bytes.Bytes): SessionsTopology { - const { topology } = decodeSessionTopologyPointer(bytes) - return topology -} - -function decodeSessionTopologyPointer(bytes: Bytes.Bytes): { - topology: SessionsTopology - pointer: number -} { - if (bytes.length === 0) { - throw new Error('Empty topology bytes') - } - - const flagByte = bytes[0]! - const flag = (flagByte & 0xf0) >> 4 - const sizeSize = flagByte & 0x0f - - if (flag === SESSIONS_FLAG_BRANCH) { - // Branch - if (sizeSize === 0 || sizeSize > 15) { - throw new Error('Invalid branch size') - } - - let offset = 1 - const encodedLength = Bytes.toNumber(bytes.slice(offset, offset + sizeSize)) - offset += sizeSize - - const encodedBranches = bytes.slice(offset, offset + encodedLength) - const branches: SessionsTopology[] = [] - - let branchOffset = 0 - while (branchOffset < encodedBranches.length) { - const { topology: branchTopology, pointer: branchPointer } = decodeSessionTopologyPointer( - encodedBranches.slice(branchOffset), - ) - branches.push(branchTopology) - branchOffset += branchPointer - } - - return { topology: branches as SessionsTopology, pointer: offset + encodedLength } - } else if (flag === SESSIONS_FLAG_PERMISSIONS) { - // Permissions - const sessionPermissions = decodeSessionPermissions(bytes.slice(1)) - const nodeLength = 1 + encodeSessionPermissions(sessionPermissions).length - return { topology: { type: 'session-permissions', ...sessionPermissions }, pointer: nodeLength } - } else if (flag === SESSIONS_FLAG_NODE) { - // Node - const nodeLength = SESSIONS_NODE_SIZE_BYTES + 1 - if (bytes.length < nodeLength) { - throw new Error('Invalid node length') - } - return { topology: Hex.fromBytes(bytes.slice(1, nodeLength)), pointer: nodeLength } - } else if (flag === SESSIONS_FLAG_BLACKLIST) { - // Blacklist - let offset = 1 - let blacklistLength = sizeSize - if (sizeSize === 0x0f) { - // Size is encoded in the next 2 bytes - blacklistLength = Bytes.toNumber(bytes.slice(offset, offset + 2)) - offset += 2 - } - - const blacklist: Address.Address[] = [] - for (let i = 0; i < blacklistLength; i++) { - const addressBytes = bytes.slice(offset + i * 20, offset + (i + 1) * 20) - blacklist.push(Address.from(Hex.fromBytes(addressBytes))) - } - - return { topology: { type: 'implicit-blacklist', blacklist }, pointer: offset + blacklistLength * 20 } - } else if (flag === SESSIONS_FLAG_IDENTITY_SIGNER) { - // Identity signer - const nodeLength = 21 // Flag + address - if (bytes.length < nodeLength) { - throw new Error('Invalid identity signer length') - } - return { - topology: { type: 'identity-signer', identitySigner: Address.from(Hex.fromBytes(bytes.slice(1, nodeLength))) }, - pointer: nodeLength, - } - } else { - throw new Error(`Invalid topology flag: ${flag}`) - } -} - -// JSON - -export function sessionsTopologyToJson(topology: SessionsTopology): string { - return JSON.stringify(encodeSessionsTopologyForJson(topology)) -} - -function encodeSessionsTopologyForJson(topology: SessionsTopology): any { - if (isSessionsNode(topology)) { - return topology - } - - if (isSessionPermissions(topology)) { - return encodeSessionPermissionsForJson(topology) - } - - if (isImplicitBlacklist(topology) || isIdentitySignerLeaf(topology)) { - return topology // No encoding necessary - } - - if (isSessionsBranch(topology)) { - return topology.map((node) => encodeSessionsTopologyForJson(node)) - } - - throw new Error('Invalid topology') -} - -export function sessionsTopologyFromJson(json: string): SessionsTopology { - const parsed = JSON.parse(json) - return sessionsTopologyFromParsed(parsed) -} - -function sessionsTopologyFromParsed(parsed: any): SessionsTopology { - // Parse branch - if (Array.isArray(parsed)) { - const branches = parsed.map((node: any) => sessionsTopologyFromParsed(node)) - return branches as SessionBranch - } - - // Parse node - if (typeof parsed === 'string' && Hex.validate(parsed) && Hex.size(parsed) === 32) { - return parsed - } - - // Parse permissions - if ( - typeof parsed === 'object' && - parsed !== null && - 'signer' in parsed && - 'valueLimit' in parsed && - 'deadline' in parsed && - 'permissions' in parsed - ) { - return { type: 'session-permissions', ...sessionPermissionsFromParsed(parsed) } - } - - // Parse identity signer - if (typeof parsed === 'object' && parsed !== null && 'identitySigner' in parsed) { - const identitySigner = parsed.identitySigner as `0x${string}` - return { type: 'identity-signer', identitySigner } - } - - // Parse blacklist - if (typeof parsed === 'object' && parsed !== null && 'blacklist' in parsed) { - const blacklist = parsed.blacklist.map((address: any) => Address.from(address)) - return { type: 'implicit-blacklist', blacklist } - } - - throw new Error('Invalid topology') -} - -// Operations - -function removeLeaf(topology: SessionsTopology, leaf: SessionLeaf | SessionNode): SessionsTopology | null { - if (isSessionsLeaf(topology) && isSessionsLeaf(leaf)) { - if (topology.type === leaf.type) { - if (isSessionPermissions(topology) && isSessionPermissions(leaf)) { - if (Address.isEqual(topology.signer, leaf.signer)) { - return null - } - } else if (isImplicitBlacklist(topology) && isImplicitBlacklist(leaf)) { - // Remove blacklist items in leaf from topology - const newBlacklist = topology.blacklist.filter((b) => !leaf.blacklist.includes(b)) - if (newBlacklist.length === 0) { - return null - } - return { type: 'implicit-blacklist', blacklist: newBlacklist } - } else if (isIdentitySignerLeaf(topology) && isIdentitySignerLeaf(leaf)) { - // Remove identity signer from topology - if (Address.isEqual(topology.identitySigner, leaf.identitySigner)) { - return null - } - } - } - } else if (isSessionsNode(topology) && isSessionsNode(leaf)) { - if (Hex.isEqual(topology, leaf)) { - // Match, remove the node - return null - } - } - - // If it's a branch, recurse on each child: - if (isSessionsBranch(topology)) { - const newChildren: SessionsTopology[] = [] - for (const child of topology) { - const updatedChild = removeLeaf(child, leaf) - if (updatedChild != null) { - newChildren.push(updatedChild) - } - } - - // If no children remain, return null to remove entire branch - if (newChildren.length === 0) { - return null - } - - // If exactly one child remains, collapse upward - if (newChildren.length === 1) { - return newChildren[0]! - } - - // Otherwise, return the updated branch - return newChildren as SessionBranch - } - - // Other leaf, return unchanged - return topology -} - -/** - * Removes all explicit sessions (permissions leaf nodes) that match the given signer from the topology. - * Returns the updated topology or null if it becomes empty (for nesting). - * If the signer is not found, the topology is returned unchanged. - */ -export function removeExplicitSession( - topology: SessionsTopology, - signerAddress: `0x${string}`, -): SessionsTopology | null { - const explicitLeaf = getSessionPermissions(topology, signerAddress) - if (!explicitLeaf) { - // Not found, return unchanged - return topology - } - const removed = removeLeaf(topology, explicitLeaf) - if (!removed) { - // Empty, return null - return null - } - // Balance it - return balanceSessionsTopology(removed) -} - -export function addExplicitSession( - topology: SessionsTopology, - sessionPermissions: SessionPermissions, -): SessionsTopology { - // Find the session in the topology - if (getSessionPermissions(topology, sessionPermissions.signer)) { - throw new Error('Session already exists') - } - // Merge and balance - const merged = mergeSessionsTopologies(topology, { type: 'session-permissions', ...sessionPermissions }) - return balanceSessionsTopology(merged) -} - -export function removeIdentitySigner( - topology: SessionsTopology, - identitySigner: Address.Address, -): SessionsTopology | null { - const identityLeaf: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner, - } - // Remove the old identity signer and balance - const removed = removeLeaf(topology, identityLeaf) - if (!removed) { - // Empty, return null - return null - } - return balanceSessionsTopology(removed) -} - -export function addIdentitySigner(topology: SessionsTopology, identitySigner: Address.Address): SessionsTopology { - // Find the session in the topology - if (getIdentitySigners(topology).some((s) => Address.isEqual(s, identitySigner))) { - throw new Error('Identity signer already exists') - } - // Merge and balance - const merged = mergeSessionsTopologies(topology, { type: 'identity-signer', identitySigner }) - return balanceSessionsTopology(merged) -} - -/** - * Merges two topologies into a new branch of [a, b]. - */ -export function mergeSessionsTopologies(a: SessionsTopology, b: SessionsTopology): SessionsTopology { - return [a, b] -} - -/** - * Helper to flatten a topology into an array of leaves and nodes only. - * We ignore branches by recursing into them. - */ -function flattenSessionsTopology(topology: SessionsTopology): (SessionLeaf | SessionNode)[] { - if (isSessionsLeaf(topology) || isSessionsNode(topology)) { - return [topology] - } - // If it's a branch, flatten all children - const result: (SessionLeaf | SessionNode)[] = [] - for (const child of topology) { - result.push(...flattenSessionsTopology(child)) - } - return result -} - -/** - * Helper to build a balanced binary tree from an array of leaves/nodes. - * This function returns: - * - A single leaf/node if there's only 1 item - * - A branch of two subtrees otherwise - */ -function buildBalancedSessionsTopology(items: (SessionLeaf | SessionNode)[]): SessionsTopology { - if (items.length === 1) { - return items[0]! - } - if (items.length === 0) { - throw new Error('Cannot build a topology from an empty list') - } - const mid = Math.floor(items.length / 2) - const left = items.slice(0, mid) - const right = items.slice(mid) - // Recursively build subtrees - const leftTopo = buildBalancedSessionsTopology(left) - const rightTopo = buildBalancedSessionsTopology(right) - return [leftTopo, rightTopo] -} - -/** - * Balances the topology by flattening and rebuilding as a balanced binary tree. - */ -export function balanceSessionsTopology(topology: SessionsTopology): SessionsTopology { - return buildBalancedSessionsTopology(flattenSessionsTopology(topology)) -} - -/** - * Cleans a topology by removing leaves (SessionPermissions) whose deadline has expired. - * - currentTime is compared against `session.deadline`. - * - If a branch ends up with zero valid leaves, return `null`. - * - If it has one child, collapse that child upward. - */ -export function cleanSessionsTopology( - topology: SessionsTopology, - currentTime: bigint = BigInt(Math.floor(Date.now() / 1000)), -): SessionsTopology | null { - // If it's a node, just return it as is. - if (isSessionsNode(topology)) { - return topology - } - - // If it's a leaf, check the deadline - if (isSessionPermissions(topology)) { - if (topology.deadline < currentTime) { - // Expired => remove - return null - } - // Valid => keep - return topology - } - - if (isIdentitySignerLeaf(topology) || isImplicitBlacklist(topology)) { - return topology - } - - // If it's a branch, clean all children - const newChildren: SessionsTopology[] = [] - for (const child of topology) { - const cleanedChild = cleanSessionsTopology(child, currentTime) - if (cleanedChild !== null) { - newChildren.push(cleanedChild) - } - } - - // If no children remain, return null - if (newChildren.length === 0) { - return null - } - - // If exactly one child remains, collapse upward: - if (newChildren.length === 1) { - return newChildren[0]! - } - - // Otherwise, return a new branch with the cleaned children - return newChildren as SessionBranch -} - -/** - * Minimise the topology by rolling unused signers into nodes. - * @param topology The topology to minimise - * @param signers The list of signers to consider - * @returns The minimised topology - */ -export function minimiseSessionsTopology( - topology: SessionsTopology, - explicitSigners: Address.Address[] = [], - implicitSigners: Address.Address[] = [], - identitySigner?: Address.Address, -): SessionsTopology { - if (isSessionsBranch(topology)) { - const branches = topology.map((b) => minimiseSessionsTopology(b, explicitSigners, implicitSigners, identitySigner)) - // If all branches are nodes, the branch can be a node too - if (branches.every((b) => isSessionsNode(b))) { - return Hash.keccak256(Bytes.concat(...branches.map((b) => Hex.toBytes(b))), { as: 'Hex' }) - } - return branches as SessionBranch - } - if (isSessionPermissions(topology)) { - if (explicitSigners.includes(topology.signer)) { - // Don't role it up as signer permissions must be visible - return topology - } - return GenericTree.hash(encodeLeafToGeneric(topology)) - } - if (isImplicitBlacklist(topology)) { - if (implicitSigners.length === 0) { - // No implicit signers, so we can roll up the blacklist - return GenericTree.hash(encodeLeafToGeneric(topology)) - } - // If there are implicit signers, we can't roll up the blacklist - return topology - } - if (isIdentitySignerLeaf(topology)) { - if (identitySigner && !Address.isEqual(topology.identitySigner, identitySigner)) { - // Not the identity signer we're looking for, so roll it up - return GenericTree.hash(encodeLeafToGeneric(topology)) - } - // Return this identity signer leaf - return topology - } - if (isSessionsNode(topology)) { - // Node is already encoded and hashed - return topology - } - // Unreachable - throw new Error('Invalid topology') -} - -/** - * Adds an address to the implicit session's blacklist. - * If the address is not already in the blacklist, it is added and the list is sorted. - */ -export function addToImplicitBlacklist(topology: SessionsTopology, address: Address.Address): SessionsTopology { - const blacklistNode = getImplicitBlacklistLeaf(topology) - if (!blacklistNode) { - throw new Error('No blacklist found') - } - const { blacklist } = blacklistNode - if (blacklist.some((addr) => Address.isEqual(addr, address))) { - return topology - } - blacklist.push(address) - blacklist.sort((a, b) => (BigInt(a) < BigInt(b) ? -1 : 1)) // keep sorted so on-chain binary search works as expected - return topology -} - -/** - * Removes an address from the implicit session's blacklist. - */ -export function removeFromImplicitBlacklist(topology: SessionsTopology, address: Address.Address): SessionsTopology { - const blacklistNode = getImplicitBlacklistLeaf(topology) - if (!blacklistNode) { - throw new Error('No blacklist found') - } - const { blacklist } = blacklistNode - const newBlacklist = blacklist.filter((a) => a !== address) - blacklistNode.blacklist = newBlacklist - return topology -} - -/** - * Generate an empty sessions topology with the given identity signer. No session permission and an empty blacklist - */ -export function emptySessionsTopology( - identitySigner: Address.Address | [Address.Address, ...Address.Address[]], -): SessionsTopology { - if (!Array.isArray(identitySigner)) { - return emptySessionsTopology([identitySigner]) - } - const flattenedTopology: SessionLeaf[] = [ - { - type: 'implicit-blacklist', - blacklist: [], - }, - ...identitySigner.map((signer): IdentitySignerLeaf => ({ type: 'identity-signer', identitySigner: signer })), - ] - return buildBalancedSessionsTopology(flattenedTopology) -} diff --git a/packages/wallet/primitives/src/session-signature.ts b/packages/wallet/primitives/src/session-signature.ts deleted file mode 100644 index c3f67ca241..0000000000 --- a/packages/wallet/primitives/src/session-signature.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { Address, Bytes, Hash, Hex } from 'ox' -import { Attestation, Extensions, Payload } from './index.js' -import { MAX_PERMISSIONS_COUNT } from './permission.js' -import { - decodeSessionsTopology, - encodeSessionsTopology, - getIdentitySigners, - isCompleteSessionsTopology, - minimiseSessionsTopology, - SessionsTopology, -} from './session-config.js' -import { RSY } from './signature.js' -import { minBytesFor, packRSY, unpackRSY } from './utils.js' - -export type ImplicitSessionCallSignature = { - attestation: Attestation.Attestation - identitySignature: RSY - sessionSignature: RSY -} - -export type ExplicitSessionCallSignature = { - permissionIndex: bigint - sessionSignature: RSY -} - -export type SessionCallSignature = ImplicitSessionCallSignature | ExplicitSessionCallSignature - -export function isImplicitSessionCallSignature( - callSignature: SessionCallSignature, -): callSignature is ImplicitSessionCallSignature { - return 'attestation' in callSignature && 'identitySignature' in callSignature && 'sessionSignature' in callSignature -} - -export function isExplicitSessionCallSignature( - callSignature: SessionCallSignature, -): callSignature is ExplicitSessionCallSignature { - return 'permissionIndex' in callSignature && 'sessionSignature' in callSignature -} - -// JSON - -export function sessionCallSignatureToJson(callSignature: SessionCallSignature): string { - return JSON.stringify(encodeSessionCallSignatureForJson(callSignature)) -} - -export function encodeSessionCallSignatureForJson(callSignature: SessionCallSignature): any { - if (isImplicitSessionCallSignature(callSignature)) { - return { - attestation: Attestation.encodeForJson(callSignature.attestation), - identitySignature: rsyToRsvStr(callSignature.identitySignature), - sessionSignature: rsyToRsvStr(callSignature.sessionSignature), - } - } else if (isExplicitSessionCallSignature(callSignature)) { - return { - permissionIndex: callSignature.permissionIndex, - sessionSignature: rsyToRsvStr(callSignature.sessionSignature), - } - } else { - throw new Error('Invalid call signature') - } -} - -export function sessionCallSignatureFromJson(json: string): SessionCallSignature { - const decoded = JSON.parse(json) - return sessionCallSignatureFromParsed(decoded) -} - -export function sessionCallSignatureFromParsed(decoded: any): SessionCallSignature { - if (decoded.attestation) { - return { - attestation: Attestation.fromParsed(decoded.attestation), - identitySignature: rsyFromRsvStr(decoded.identitySignature), - sessionSignature: rsyFromRsvStr(decoded.sessionSignature), - } - } else if (decoded.permissionIndex) { - return { - permissionIndex: decoded.permissionIndex, - sessionSignature: rsyFromRsvStr(decoded.sessionSignature), - } - } else { - throw new Error('Invalid call signature') - } -} - -function rsyToRsvStr(sig: RSY): string { - return `${sig.r.toString()}:${sig.s.toString()}:${sig.yParity + 27}` -} - -function rsyFromRsvStr(sigStr: string): RSY { - const parts = sigStr.split(':') - if (parts.length !== 3) { - throw new Error('Signature must be in r:s:v format') - } - const [rStr, sStr, vStr] = parts - if (!rStr || !sStr || !vStr) { - throw new Error('Invalid signature format') - } - return { - r: Bytes.toBigInt(Bytes.fromHex(rStr as `0x${string}`, { size: 32 })), - s: Bytes.toBigInt(Bytes.fromHex(sStr as `0x${string}`, { size: 32 })), - yParity: parseInt(vStr, 10) - 27, - } -} - -// Usage - -/** - * Encodes a list of session call signatures into a bytes array for contract validation. - * @param callSignatures The list of session call signatures to encode. - * @param topology The complete session topology. - * @param explicitSigners The list of explicit signers to encode. Others will be hashed into nodes. - * @param implicitSigners The list of implicit signers to encode. Others will be hashed into nodes. - * @param identitySigner The identity signer to encode. Others will be hashed into nodes. - * @returns The encoded session call signatures. - */ -export function encodeSessionSignature( - callSignatures: SessionCallSignature[], - topology: SessionsTopology, - identitySigner: Address.Address, - explicitSigners: Address.Address[] = [], - implicitSigners: Address.Address[] = [], -): Bytes.Bytes { - const parts: Bytes.Bytes[] = [] - - // Validate the topology - if (!isCompleteSessionsTopology(topology)) { - // Refuse to encode incomplete topologies - throw new Error('Incomplete topology') - } - - // Check the topology contains the identity signer - const identitySigners = getIdentitySigners(topology) - if (!identitySigners.some((s) => Address.isEqual(s, identitySigner))) { - throw new Error('Identity signer not found') - } - - // Optimise the configuration tree by rolling unused signers into nodes. - topology = minimiseSessionsTopology(topology, explicitSigners, implicitSigners, identitySigner) - - // Session topology - const encodedTopology = encodeSessionsTopology(topology) - if (minBytesFor(BigInt(encodedTopology.length)) > 3) { - throw new Error('Session topology is too large') - } - parts.push(Bytes.fromNumber(encodedTopology.length, { size: 3 }), encodedTopology) - - // Create unique attestation list and maintain index mapping - const attestationMap = new Map() - const encodedAttestations: Bytes.Bytes[] = [] - - // Map each call signature to its attestation index - callSignatures.filter(isImplicitSessionCallSignature).forEach((callSig) => { - if (callSig.attestation) { - const attestationStr = Attestation.toJson(callSig.attestation) - if (!attestationMap.has(attestationStr)) { - attestationMap.set(attestationStr, encodedAttestations.length) - encodedAttestations.push( - Bytes.concat(Attestation.encode(callSig.attestation), packRSY(callSig.identitySignature)), - ) - } - } - }) - - // Add the attestations to the parts - if (encodedAttestations.length >= 128) { - throw new Error('Too many attestations') - } - parts.push(Bytes.fromNumber(encodedAttestations.length, { size: 1 }), Bytes.concat(...encodedAttestations)) - - // Call signature parts - for (const callSignature of callSignatures) { - if (isImplicitSessionCallSignature(callSignature)) { - // Implicit - const attestationStr = Attestation.toJson(callSignature.attestation) - const attestationIndex = attestationMap.get(attestationStr) - if (attestationIndex === undefined) { - // Unreachable - throw new Error('Failed to find attestation index') - } - const packedFlag = 0x80 | attestationIndex // Implicit flag (MSB) true + attestation index - parts.push(Bytes.fromNumber(packedFlag, { size: 1 }), packRSY(callSignature.sessionSignature)) - } else if (isExplicitSessionCallSignature(callSignature)) { - // Explicit - if (callSignature.permissionIndex > MAX_PERMISSIONS_COUNT) { - throw new Error('Permission index is too large') - } - const packedFlag = callSignature.permissionIndex // Implicit flag (MSB) false + permission index - parts.push(Bytes.fromNumber(packedFlag, { size: 1 }), packRSY(callSignature.sessionSignature)) - } else { - // Invalid call signature - throw new Error('Invalid call signature') - } - } - - return Bytes.concat(...parts) -} - -export function decodeSessionSignature(encodedSignatures: Bytes.Bytes): { - topology: SessionsTopology - callSignatures: SessionCallSignature[] -} { - let offset = 0 - - // Parse session topology length (3 bytes) - const topologyLength = Bytes.toNumber(encodedSignatures.slice(offset, offset + 3)) - offset += 3 - - // Parse session topology - const topologyBytes = encodedSignatures.slice(offset, offset + topologyLength) - offset += topologyLength - const topology = decodeSessionsTopology(topologyBytes) - - // Parse attestations count (1 byte) - const attestationsCount = Bytes.toNumber(encodedSignatures.slice(offset, offset + 1)) - offset += 1 - - // Parse attestations and identity signatures - const attestations: Attestation.Attestation[] = [] - const identitySignatures: RSY[] = [] - - for (let i = 0; i < attestationsCount; i++) { - // Parse attestation - const attestation = Attestation.decode(encodedSignatures.slice(offset)) - offset += Attestation.encode(attestation).length - attestations.push(attestation) - - // Parse identity signature (64 bytes) - const identitySignature = unpackRSY(encodedSignatures.slice(offset, offset + 64)) - offset += 64 - identitySignatures.push(identitySignature) - } - - // Parse call signatures - const callSignatures: SessionCallSignature[] = [] - - while (offset < encodedSignatures.length) { - // Parse flag byte - const flagByte = encodedSignatures[offset]! - offset += 1 - - // Parse session signature (64 bytes) - const sessionSignature = unpackRSY(encodedSignatures.slice(offset, offset + 64)) - offset += 64 - - // Check if implicit (MSB set) or explicit - if ((flagByte & 0x80) !== 0) { - // Implicit call signature - const attestationIndex = flagByte & 0x7f - if (attestationIndex >= attestations.length) { - throw new Error('Invalid attestation index') - } - - callSignatures.push({ - attestation: attestations[attestationIndex]!, - identitySignature: identitySignatures[attestationIndex]!, - sessionSignature, - }) - } else { - // Explicit call signature - const permissionIndex = flagByte - callSignatures.push({ - permissionIndex: BigInt(permissionIndex), - sessionSignature, - }) - } - } - - return { - topology, - callSignatures, - } -} - -// Call encoding - -/** - * Hashes a call with replay protection parameters. - * @param payload The payload to hash. - * @param callIdx The index of the call to hash. - * @param chainId The chain ID. Use 0 when noChainId enabled. - * @param sessionManagerAddress The session manager address to compile the hash for. Only required to support deprecated hash encodings for Dev1, Dev2 and Rc3. - * @returns The hash of the call with replay protection parameters for sessions. - */ -export function hashPayloadWithCallIdx( - wallet: Address.Address, - payload: Payload.Calls & Payload.Parent, - callIdx: number, - chainId: number, - sessionManagerAddress?: Address.Address, -): Hex.Hex { - // Support deprecated hashes for Dev1, Dev2 and Rc3 - const deprecatedHashing = - sessionManagerAddress && - (Address.isEqual(sessionManagerAddress, Extensions.Dev1.sessions) || - Address.isEqual(sessionManagerAddress, Extensions.Dev2.sessions) || - Address.isEqual(sessionManagerAddress, Extensions.Rc3.sessions)) - if (deprecatedHashing) { - const call = payload.calls[callIdx]! - const ignoreCallIdx = !Address.isEqual(sessionManagerAddress, Extensions.Rc3.sessions) - return Hex.fromBytes( - Hash.keccak256( - Bytes.concat( - Bytes.fromNumber(chainId, { size: 32 }), - Bytes.fromNumber(payload.space, { size: 32 }), - Bytes.fromNumber(payload.nonce, { size: 32 }), - ignoreCallIdx ? Bytes.from([]) : Bytes.fromNumber(callIdx, { size: 32 }), - Bytes.fromHex(Payload.hashCall(call)), - ), - ), - ) - } - // Current hashing scheme uses entire payload hash and call index (without last parent) - const parentWallets = payload.parentWallets - if (payload.parentWallets && payload.parentWallets.length > 0) { - payload.parentWallets.pop() - } - const payloadHash = Payload.hash(wallet, chainId, payload) - payload.parentWallets = parentWallets - return Hex.fromBytes(Hash.keccak256(Bytes.concat(payloadHash, Bytes.fromNumber(callIdx, { size: 32 })))) -} diff --git a/packages/wallet/primitives/src/signature.ts b/packages/wallet/primitives/src/signature.ts deleted file mode 100644 index 6ba93f9c1b..0000000000 --- a/packages/wallet/primitives/src/signature.ts +++ /dev/null @@ -1,1401 +0,0 @@ -import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider, Secp256k1 } from 'ox' -import { - Config, - Leaf, - NestedLeaf, - SapientSignerLeaf, - SignerLeaf, - SubdigestLeaf, - AnyAddressSubdigestLeaf, - Topology, - hashConfiguration, - isNestedLeaf, - isNode, - isNodeLeaf, - isSapientSignerLeaf, - isSignerLeaf, - isSubdigestLeaf, - isAnyAddressSubdigestLeaf, - isTopology, -} from './config.js' -import { RECOVER_SAPIENT_SIGNATURE, RECOVER_SAPIENT_SIGNATURE_COMPACT, IS_VALID_SIGNATURE } from './constants.js' -import { wrap, decode } from './erc-6492.js' -import { fromConfigUpdate, hash, Parented } from './payload.js' -import { minBytesFor, packRSY, unpackRSY } from './utils.js' -import { Constants } from './index.js' - -export const FLAG_SIGNATURE_HASH = 0 -export const FLAG_ADDRESS = 1 -export const FLAG_SIGNATURE_ERC1271 = 2 -export const FLAG_NODE = 3 -export const FLAG_BRANCH = 4 -export const FLAG_SUBDIGEST = 5 -export const FLAG_NESTED = 6 -export const FLAG_SIGNATURE_ETH_SIGN = 7 -export const FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST = 8 -export const FLAG_SIGNATURE_SAPIENT = 9 -export const FLAG_SIGNATURE_SAPIENT_COMPACT = 10 - -export type RSY = { - r: bigint - s: bigint - yParity: number -} - -export type SignatureOfSignerLeafEthSign = { - type: 'eth_sign' -} & RSY - -export type SignatureOfSignerLeafHash = { - type: 'hash' -} & RSY - -export type SignatureOfSignerLeafErc1271 = { - type: 'erc1271' - address: `0x${string}` - data: Hex.Hex -} - -export type SignatureOfSignerLeaf = - | SignatureOfSignerLeafEthSign - | SignatureOfSignerLeafHash - | SignatureOfSignerLeafErc1271 - -export type SignatureOfSapientSignerLeaf = { - address: `0x${string}` - data: Hex.Hex - type: 'sapient' | 'sapient_compact' -} - -export type SignedSignerLeaf = SignerLeaf & { - signed: true - signature: SignatureOfSignerLeaf -} - -export type SignedSapientSignerLeaf = SapientSignerLeaf & { - signed: true - signature: SignatureOfSapientSignerLeaf -} - -export type RawSignerLeaf = { - type: 'unrecovered-signer' - weight: bigint - signature: SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf -} - -export type RawNestedLeaf = { - type: 'nested' - tree: RawTopology - weight: bigint - threshold: bigint -} - -export type RawLeaf = Leaf | RawSignerLeaf | RawNestedLeaf - -export type RawNode = [RawTopology, RawTopology] - -export type RawTopology = RawNode | RawLeaf - -export type RawConfig = { - threshold: bigint - checkpoint: bigint - topology: RawTopology - checkpointer?: Address.Address -} - -export type RawSignature = { - noChainId: boolean - checkpointerData?: Bytes.Bytes - configuration: RawConfig - suffix?: RawSignature[] - erc6492?: { to: Address.Address; data: Bytes.Bytes } -} - -export function isSignatureOfSapientSignerLeaf(signature: any): signature is SignatureOfSapientSignerLeaf { - return ( - 'type' in signature && - (signature.type === 'sapient_compact' || signature.type === 'sapient') && - typeof signature === 'object' && - 'address' in signature && - 'data' in signature - ) -} - -export function isRawSignature(signature: any): signature is RawSignature { - return ( - typeof signature === 'object' && - signature && - typeof signature.noChainId === 'boolean' && - (signature.checkpointerData === undefined || Bytes.validate(signature.checkpointerData)) && - isRawConfig(signature.configuration) && - (signature.suffix === undefined || - (Array.isArray(signature.suffix) && - signature.suffix.every( - (signature: any) => isRawSignature(signature) && signature.checkpointerData === undefined, - ))) - ) -} - -export function isRawConfig(configuration: any): configuration is RawConfig { - return ( - configuration && - typeof configuration === 'object' && - typeof configuration.threshold === 'bigint' && - typeof configuration.checkpoint === 'bigint' && - isRawTopology(configuration.topology) && - (configuration.checkpointer === undefined || Address.validate(configuration.checkpointer)) - ) -} - -export function isRawSignerLeaf(cand: any): cand is RawSignerLeaf { - return typeof cand === 'object' && 'weight' in cand && 'signature' in cand -} - -export function isSignedSignerLeaf(cand: any): cand is SignedSignerLeaf { - return isSignerLeaf(cand) && 'signature' in cand -} - -export function isSignedSapientSignerLeaf(cand: any): cand is SignedSapientSignerLeaf { - return isSapientSignerLeaf(cand) && 'signature' in cand -} - -export function isRawNode(cand: any): cand is RawNode { - return ( - Array.isArray(cand) && - cand.length === 2 && - (isRawTopology(cand[0]) || isTopology(cand[0])) && - (isRawTopology(cand[1]) || isTopology(cand[1])) - ) -} - -export function isRawTopology(cand: any): cand is RawTopology { - return isRawNode(cand) || isRawLeaf(cand) -} - -export function isRawLeaf(cand: any): cand is RawLeaf { - return typeof cand === 'object' && 'weight' in cand && !('tree' in cand) -} - -export function isRawNestedLeaf(cand: any): cand is RawNestedLeaf { - return typeof cand === 'object' && 'tree' in cand && 'weight' in cand && 'threshold' in cand -} - -export function decodeSignature(erc6492Signature: Bytes.Bytes): RawSignature { - const { signature, erc6492 } = decode(erc6492Signature) - - if (signature.length < 1) { - throw new Error('Signature is empty') - } - - const flag = signature[0]! - let index = 1 - - const noChainId = (flag & 0x02) === 0x02 - - let checkpointerAddress: Address.Address | undefined - let checkpointerData: Bytes.Bytes | undefined - - // bit [6] => checkpointer address + data - if ((flag & 0x40) === 0x40) { - if (index + 20 > signature.length) { - throw new Error('Not enough bytes for checkpointer address') - } - checkpointerAddress = Bytes.toHex(signature.slice(index, index + 20)) - index += 20 - - if (index + 3 > signature.length) { - throw new Error('Not enough bytes for checkpointer data size') - } - const checkpointerDataSize = Bytes.toNumber(signature.slice(index, index + 3)) - index += 3 - - if (index + checkpointerDataSize > signature.length) { - throw new Error('Not enough bytes for checkpointer data') - } - checkpointerData = signature.slice(index, index + checkpointerDataSize) - index += checkpointerDataSize - } - - // bits [2..4] => checkpoint size - const checkpointSize = (flag & 0x1c) >> 2 - if (index + checkpointSize > signature.length) { - throw new Error('Not enough bytes for checkpoint') - } - const checkpoint = Bytes.toBigInt(signature.slice(index, index + checkpointSize)) - index += checkpointSize - - // bit [5] => threshold size offset - const thresholdSize = ((flag & 0x20) >> 5) + 1 - if (index + thresholdSize > signature.length) { - throw new Error('Not enough bytes for threshold') - } - const threshold = Bytes.toBigInt(signature.slice(index, index + thresholdSize)) - index += thresholdSize - - // If bit 1 is set => chained signature - if ((flag & 0x01) === 0x01) { - const subsignatures: Array = [] - - while (index < signature.length) { - if (index + 3 > signature.length) { - throw new Error('Not enough bytes for chained subsignature size') - } - const subsignatureSize = Bytes.toNumber(signature.subarray(index, index + 3)) - index += 3 - - if (index + subsignatureSize > signature.length) { - throw new Error('Not enough bytes for chained subsignature') - } - const subsignature = decodeSignature(signature.subarray(index, index + subsignatureSize)) - index += subsignatureSize - - if (subsignature.checkpointerData) { - throw new Error('Chained subsignature has checkpointer data') - } - - subsignatures.push({ ...subsignature, checkpointerData: undefined }) - } - - if (subsignatures.length === 0) { - throw new Error('Chained signature has no subsignatures') - } - - return { ...subsignatures[0]!, suffix: subsignatures.slice(1), erc6492 } - } - - const { nodes, leftover } = parseBranch(signature.slice(index)) - if (leftover.length !== 0) { - throw new Error('Leftover bytes in signature') - } - - const topology = foldNodes(nodes) - - return { - noChainId, - checkpointerData, - configuration: { threshold, checkpoint, topology, checkpointer: checkpointerAddress }, - erc6492, - } -} - -export function parseBranch(signature: Bytes.Bytes): { - nodes: RawTopology[] - leftover: Bytes.Bytes -} { - const nodes: RawTopology[] = [] - let index = 0 - - while (index < signature.length) { - const firstByte = signature[index]! - index++ - - const flag = (firstByte & 0xf0) >> 4 - - // FLAG_SIGNATURE_HASH = 0 => bottom nibble is weight - // Then read 64 bytes => r, yParityAndS => top bit => yParity => s is the rest => v=27+yParity - if (flag === FLAG_SIGNATURE_HASH) { - let weight = firstByte & 0x0f - if (weight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for dynamic weight') - } - weight = signature[index]! - index++ - } - if (index + 64 > signature.length) { - throw new Error('Not enough bytes for hash signature (r + yParityAndS)') - } - const unpackedRSY = unpackRSY(signature.slice(index, index + 64)) - index += 64 - - nodes.push({ - type: 'unrecovered-signer', - weight: BigInt(weight), - signature: { - type: 'hash', - ...unpackedRSY, - }, - } as RawSignerLeaf) - continue - } - - // FLAG_ADDRESS = 1 => bottom nibble is weight => read 20 bytes => no signature - if (flag === FLAG_ADDRESS) { - let weight = firstByte & 0x0f - if (weight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for address weight') - } - weight = signature[index]! - index++ - } - if (index + 20 > signature.length) { - throw new Error('Not enough bytes for address leaf') - } - const addr = Bytes.toHex(signature.slice(index, index + 20)) - index += 20 - - nodes.push({ - type: 'signer', - address: addr, - weight: BigInt(weight), - } as SignerLeaf) - continue - } - - // FLAG_SIGNATURE_ERC1271 = 2 => bottom 2 bits => weight, next 2 bits => sizeSize - if (flag === FLAG_SIGNATURE_ERC1271) { - let weight = firstByte & 0x03 - if (weight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for ERC1271 weight') - } - weight = signature[index]! - index++ - } - if (index + 20 > signature.length) { - throw new Error('Not enough bytes for ERC1271 signer address') - } - const signer = Bytes.toHex(signature.slice(index, index + 20)) - index += 20 - - const sizeSize = (firstByte & 0x0c) >> 2 - if (index + sizeSize > signature.length) { - throw new Error('Not enough bytes for ERC1271 sizeSize') - } - const dataSize = Bytes.toNumber(signature.slice(index, index + sizeSize)) - index += sizeSize - - if (index + dataSize > signature.length) { - throw new Error('Not enough bytes for ERC1271 data') - } - const subSignature = signature.slice(index, index + dataSize) - index += dataSize - - nodes.push({ - type: 'unrecovered-signer', - weight: BigInt(weight), - signature: { - type: 'erc1271', - address: signer, - data: Bytes.toHex(subSignature), - }, - } as RawSignerLeaf) - continue - } - - // FLAG_NODE = 3 => read 32 bytes as a node hash - if (flag === FLAG_NODE) { - if (index + 32 > signature.length) { - throw new Error('Not enough bytes for node leaf') - } - const node = signature.slice(index, index + 32) - index += 32 - - nodes.push(Bytes.toHex(node)) - continue - } - - // FLAG_BRANCH = 4 => next nibble => sizeSize => read size => parse sub-branch - if (flag === FLAG_BRANCH) { - const sizeSize = firstByte & 0x0f - if (index + sizeSize > signature.length) { - throw new Error('Not enough bytes for branch sizeSize') - } - const size = Bytes.toNumber(signature.slice(index, index + sizeSize)) - index += sizeSize - - if (index + size > signature.length) { - throw new Error('Not enough bytes in sub-branch') - } - const branchBytes = signature.slice(index, index + size) - index += size - - const { nodes: subNodes, leftover } = parseBranch(branchBytes) - if (leftover.length > 0) { - throw new Error('Leftover bytes in sub-branch') - } - const subTree = foldNodes(subNodes) - nodes.push(subTree) - continue - } - - // FLAG_SUBDIGEST = 5 => read 32 bytes => push subdigest leaf - if (flag === FLAG_SUBDIGEST) { - if (index + 32 > signature.length) { - throw new Error('Not enough bytes for subdigest') - } - const hardcoded = signature.slice(index, index + 32) - index += 32 - nodes.push({ - type: 'subdigest', - digest: Bytes.toHex(hardcoded), - } as SubdigestLeaf) - continue - } - - // FLAG_NESTED = 6 => read externalWeight + internalThreshold, then read 3 bytes => parse subtree - if (flag === FLAG_NESTED) { - // bits [3..2] => external weight - let externalWeight = (firstByte & 0x0c) >> 2 - if (externalWeight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for nested weight') - } - externalWeight = signature[index]! - index++ - } - - // bits [1..0] => internal threshold - let internalThreshold = firstByte & 0x03 - if (internalThreshold === 0) { - if (index + 2 > signature.length) { - throw new Error('Not enough bytes for nested threshold') - } - internalThreshold = Bytes.toNumber(signature.slice(index, index + 2)) - index += 2 - } - - if (index + 3 > signature.length) { - throw new Error('Not enough bytes for nested sub-tree size') - } - const size = Bytes.toNumber(signature.slice(index, index + 3)) - index += 3 - - if (index + size > signature.length) { - throw new Error('Not enough bytes for nested sub-tree') - } - const nestedTreeBytes = signature.slice(index, index + size) - index += size - - const { nodes: subNodes, leftover } = parseBranch(nestedTreeBytes) - if (leftover.length > 0) { - throw new Error('Leftover bytes in nested sub-tree') - } - const subTree = foldNodes(subNodes) - - nodes.push({ - type: 'nested', - tree: subTree, - weight: BigInt(externalWeight), - threshold: BigInt(internalThreshold), - } as RawNestedLeaf) - continue - } - - // FLAG_SIGNATURE_ETH_SIGN = 7 => parse it same as hash, but interpret the subdigest as an Ethereum Signed Message - if (flag === FLAG_SIGNATURE_ETH_SIGN) { - let weight = firstByte & 0x0f - if (weight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for dynamic weight in eth_sign') - } - weight = signature[index]! - index++ - } - if (index + 64 > signature.length) { - throw new Error('Not enough bytes for eth_sign signature') - } - const unpackedRSY = unpackRSY(signature.slice(index, index + 64)) - index += 64 - - nodes.push({ - type: 'unrecovered-signer', - weight: BigInt(weight), - signature: { - type: 'eth_sign', - ...unpackedRSY, - }, - } as RawSignerLeaf) - continue - } - - // FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST = 8 => read 32 bytes => push any address subdigest leaf - if (flag === FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST) { - if (index + 32 > signature.length) { - throw new Error('Not enough bytes for any address subdigest') - } - const anyAddressSubdigest = signature.slice(index, index + 32) - index += 32 - nodes.push({ - type: 'any-address-subdigest', - digest: Bytes.toHex(anyAddressSubdigest), - } as AnyAddressSubdigestLeaf) - continue - } - - if (flag === FLAG_SIGNATURE_SAPIENT || flag === FLAG_SIGNATURE_SAPIENT_COMPACT) { - let addrWeight = firstByte & 0x03 - if (addrWeight === 0) { - if (index >= signature.length) { - throw new Error('Not enough bytes for sapient weight') - } - addrWeight = signature[index]! - index++ - } - if (index + 20 > signature.length) { - throw new Error('Not enough bytes for sapient signer address') - } - const address = Bytes.toHex(signature.slice(index, index + 20)) - index += 20 - - const sizeSize = (firstByte & 0x0c) >> 2 - if (index + sizeSize > signature.length) { - throw new Error('Not enough bytes for sapient signature size') - } - const dataSize = Bytes.toNumber(signature.slice(index, index + sizeSize)) - index += sizeSize - - if (index + dataSize > signature.length) { - throw new Error('Not enough bytes for sapient signature data') - } - const subSignature = signature.slice(index, index + dataSize) - index += dataSize - - nodes.push({ - type: 'unrecovered-signer', - weight: BigInt(addrWeight), - signature: { - address, - data: Bytes.toHex(subSignature), - type: flag === FLAG_SIGNATURE_SAPIENT ? 'sapient' : 'sapient_compact', - }, - } as RawSignerLeaf) - continue - } - - throw new Error(`Invalid signature flag: 0x${flag.toString(16)}`) - } - - return { nodes, leftover: signature.slice(index) } -} - -export function fillLeaves( - topology: Topology, - signatureFor: ( - leaf: SignerLeaf | SapientSignerLeaf, - ) => SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf | undefined, -): Topology { - if (isNode(topology)) { - return [fillLeaves(topology[0]!, signatureFor), fillLeaves(topology[1]!, signatureFor)] as Topology - } - - if (isSignerLeaf(topology)) { - const signature = signatureFor(topology) - if (!signature) { - return topology - } - return { ...topology, signature } as SignedSignerLeaf - } - - if (isSapientSignerLeaf(topology)) { - const signature = signatureFor(topology) - if (!signature) { - return topology - } - return { ...topology, signature } as SignedSapientSignerLeaf - } - - if (isSubdigestLeaf(topology)) { - return topology - } - - if (isAnyAddressSubdigestLeaf(topology)) { - return topology - } - - if (isNestedLeaf(topology)) { - return { ...topology, tree: fillLeaves(topology.tree, signatureFor) } as NestedLeaf - } - - if (isNodeLeaf(topology)) { - return topology - } - - throw new Error('Invalid topology') -} - -export function encodeChainedSignature(signatures: RawSignature[]): Uint8Array { - let flag = 0x01 - - const sigForCheckpointer = signatures[signatures.length - 1] - - if (sigForCheckpointer?.configuration.checkpointer) { - flag |= 0x40 - } - - let output = Bytes.fromNumber(flag) - if (sigForCheckpointer?.configuration.checkpointer) { - output = Bytes.concat(output, Bytes.padLeft(Bytes.fromHex(sigForCheckpointer.configuration.checkpointer), 20)) - const checkpointerDataSize = sigForCheckpointer.checkpointerData?.length ?? 0 - if (checkpointerDataSize > 16777215) { - throw new Error('Checkpointer data too large') - } - const checkpointerDataSizeBytes = Bytes.padLeft(Bytes.fromNumber(checkpointerDataSize), 3) - output = Bytes.concat(output, checkpointerDataSizeBytes, sigForCheckpointer.checkpointerData ?? Bytes.fromArray([])) - } - - for (let i = 0; i < signatures.length; i++) { - const signature = signatures[i]! - const encoded = encodeSignature(signature, true, i === signatures.length - 1) - if (encoded.length > 16777215) { - throw new Error('Chained signature too large') - } - const encodedSize = Bytes.padLeft(Bytes.fromNumber(encoded.length), 3) - output = Bytes.concat(output, encodedSize, encoded) - } - - return output -} - -export function encodeSignature( - signature: RawSignature, - skipCheckpointerData?: boolean, - skipCheckpointerAddress?: boolean, -): Uint8Array { - const { noChainId, checkpointerData, configuration: config, suffix, erc6492 } = signature - - if (suffix?.length) { - const chainedSig = encodeChainedSignature([{ ...signature, suffix: undefined, erc6492: undefined }, ...suffix]) - return erc6492 ? wrap(chainedSig, erc6492) : chainedSig - } - - let flag = 0 - - if (noChainId) { - flag |= 0x02 - } - - const bytesForCheckpoint = minBytesFor(config.checkpoint) - if (bytesForCheckpoint > 7) { - throw new Error('Checkpoint too large') - } - flag |= bytesForCheckpoint << 2 - - let bytesForThreshold = minBytesFor(config.threshold) - bytesForThreshold = bytesForThreshold === 0 ? 1 : bytesForThreshold - if (bytesForThreshold > 2) { - throw new Error('Threshold too large') - } - flag |= bytesForThreshold == 2 ? 0x20 : 0x00 - - if (config.checkpointer && !skipCheckpointerAddress) { - flag |= 0x40 - } - - let output = Bytes.fromNumber(flag) - - if (config.checkpointer && !skipCheckpointerAddress) { - output = Bytes.concat(output, Bytes.padLeft(Bytes.fromHex(config.checkpointer), 20)) - if (!skipCheckpointerData) { - const checkpointerDataSize = checkpointerData?.length ?? 0 - if (checkpointerDataSize > 16777215) { - throw new Error('Checkpointer data too large') - } - - const checkpointerDataSizeBytes = Bytes.padLeft(Bytes.fromNumber(checkpointerDataSize), 3) - output = Bytes.concat(output, checkpointerDataSizeBytes, checkpointerData ?? Bytes.fromArray([])) - } - } - - const checkpointBytes = Bytes.padLeft(Bytes.fromNumber(config.checkpoint), bytesForCheckpoint) - output = Bytes.concat(output, checkpointBytes) - - const thresholdBytes = Bytes.padLeft(Bytes.fromNumber(config.threshold), bytesForThreshold) - output = Bytes.concat(output, thresholdBytes) - - const topologyBytes = encodeTopology(config.topology, signature) - output = Bytes.concat(output, topologyBytes) - - return erc6492 ? wrap(output, erc6492) : output -} - -export function encodeTopology( - topology: Topology | RawTopology, - options: { - noChainId?: boolean - checkpointerData?: Uint8Array - } = {}, -): Uint8Array { - if (isNode(topology) || isRawNode(topology)) { - const encoded0 = encodeTopology(topology[0]!, options) - const encoded1 = encodeTopology(topology[1]!, options) - const isBranching = isNode(topology[1]!) || isRawNode(topology[1]!) - - if (isBranching) { - const encoded1Size = minBytesFor(BigInt(encoded1.length)) - if (encoded1Size > 15) { - throw new Error('Branch too large') - } - - const flag = (FLAG_BRANCH << 4) | encoded1Size - return Bytes.concat( - encoded0, - Bytes.fromNumber(flag), - Bytes.padLeft(Bytes.fromNumber(encoded1.length), encoded1Size), - encoded1, - ) - } else { - return Bytes.concat(encoded0, encoded1) - } - } - - if (isNestedLeaf(topology) || isRawNestedLeaf(topology)) { - const nested = encodeTopology(topology.tree, options) - - // - XX00 : Weight (00 = dynamic, 01 = 1, 10 = 2, 11 = 3) - // - 00XX : Threshold (00 = dynamic, 01 = 1, 10 = 2, 11 = 3) - let flag = FLAG_NESTED << 4 - - let weightBytes = Bytes.fromArray([]) - if (topology.weight <= 3n && topology.weight > 0n) { - flag |= Number(topology.weight) << 2 - } else if (topology.weight <= 255n) { - weightBytes = Bytes.fromNumber(Number(topology.weight)) - } else { - throw new Error('Weight too large') - } - - let thresholdBytes = Bytes.fromArray([]) - if (topology.threshold <= 3n && topology.threshold > 0n) { - flag |= Number(topology.threshold) - } else if (topology.threshold <= 65535n) { - thresholdBytes = Bytes.padLeft(Bytes.fromNumber(Number(topology.threshold)), 2) - } else { - throw new Error('Threshold too large') - } - - if (nested.length > 16777215) { - throw new Error('Nested tree too large') - } - - return Bytes.concat( - Bytes.fromNumber(flag), - weightBytes, - thresholdBytes, - Bytes.padLeft(Bytes.fromNumber(nested.length), 3), - nested, - ) - } - - if (isNodeLeaf(topology)) { - return Bytes.concat(Bytes.fromNumber(FLAG_NODE << 4), Bytes.fromHex(topology)) - } - - if (isSignedSignerLeaf(topology) || isRawSignerLeaf(topology)) { - if (topology.signature.type === 'hash' || topology.signature.type === 'eth_sign') { - let flag = (topology.signature.type === 'hash' ? FLAG_SIGNATURE_HASH : FLAG_SIGNATURE_ETH_SIGN) << 4 - let weightBytes = Bytes.fromArray([]) - if (topology.weight <= 15n && topology.weight > 0n) { - flag |= Number(topology.weight) - } else if (topology.weight <= 255n) { - weightBytes = Bytes.fromNumber(Number(topology.weight)) - } else { - throw new Error('Weight too large') - } - - const packedRSY = packRSY(topology.signature) - return Bytes.concat(Bytes.fromNumber(flag), weightBytes, packedRSY) - } else if (topology.signature.type === 'erc1271') { - let flag = FLAG_SIGNATURE_ERC1271 << 4 - - const bytesForSignatureSize = minBytesFor(BigInt(topology.signature.data.length)) - if (bytesForSignatureSize > 3) { - throw new Error('Signature too large') - } - - flag |= bytesForSignatureSize << 2 - - let weightBytes = Bytes.fromArray([]) - if (topology.weight <= 3n && topology.weight > 0n) { - flag |= Number(topology.weight) - } else if (topology.weight <= 255n) { - weightBytes = Bytes.fromNumber(Number(topology.weight)) - } else { - throw new Error('Weight too large') - } - - return Bytes.concat( - Bytes.fromNumber(flag), - weightBytes, - Bytes.padLeft(Bytes.fromHex(topology.signature.address), 20), - Bytes.padLeft(Bytes.fromNumber(Bytes.fromHex(topology.signature.data).length), bytesForSignatureSize), - Bytes.fromHex(topology.signature.data), - ) - } else if (topology.signature.type === 'sapient' || topology.signature.type === 'sapient_compact') { - let flag = (topology.signature.type === 'sapient' ? FLAG_SIGNATURE_SAPIENT : FLAG_SIGNATURE_SAPIENT_COMPACT) << 4 - - const signatureBytes = Bytes.fromHex(topology.signature.data) - const bytesForSignatureSize = minBytesFor(BigInt(signatureBytes.length)) - if (bytesForSignatureSize > 3) { - throw new Error('Signature too large') - } - - flag |= bytesForSignatureSize << 2 - - let weightBytes = Bytes.fromArray([]) - if (topology.weight <= 3n && topology.weight > 0n) { - flag |= Number(topology.weight) - } else if (topology.weight <= 255n) { - weightBytes = Bytes.fromNumber(Number(topology.weight)) - } else { - throw new Error('Weight too large') - } - - return Bytes.concat( - Bytes.fromNumber(flag), - weightBytes, - Bytes.padLeft(Bytes.fromHex(topology.signature.address), 20), - Bytes.padLeft(Bytes.fromNumber(signatureBytes.length), bytesForSignatureSize), - signatureBytes, - ) - } else { - throw new Error(`Invalid signature type: ${topology.signature.type}`) - } - } - - if (isSignerLeaf(topology)) { - let flag = FLAG_ADDRESS << 4 - let weightBytes = Bytes.fromArray([]) - if (topology.weight <= 15n && topology.weight > 0n) { - flag |= Number(topology.weight) - } else if (topology.weight <= 255n) { - weightBytes = Bytes.fromNumber(Number(topology.weight)) - } else { - throw new Error('Weight too large') - } - - return Bytes.concat(Bytes.fromNumber(flag), weightBytes, Bytes.padLeft(Bytes.fromHex(topology.address), 20)) - } - - if (isSapientSignerLeaf(topology)) { - // Encode as node directly - const hash = hashConfiguration(topology) - return Bytes.concat(Bytes.fromNumber(FLAG_NODE << 4), hash) - } - - if (isSubdigestLeaf(topology)) { - return Bytes.concat(Bytes.fromNumber(FLAG_SUBDIGEST << 4), Bytes.fromHex(topology.digest)) - } - - if (isAnyAddressSubdigestLeaf(topology)) { - return Bytes.concat(Bytes.fromNumber(FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST << 4), Bytes.fromHex(topology.digest)) - } - - throw new Error('Invalid topology') -} - -function foldNodes(nodes: RawTopology[]): RawTopology { - if (nodes.length === 0) { - throw new Error('Empty signature tree') - } - - if (nodes.length === 1) { - return nodes[0]! - } - - let tree: RawTopology = nodes[0]! - for (let i = 1; i < nodes.length; i++) { - tree = [tree, nodes[i]!] as RawNode - } - return tree -} - -export function rawSignatureToJson(signature: RawSignature): string { - return JSON.stringify(rawSignatureToJsonParsed(signature)) -} - -function rawSignatureToJsonParsed(signature: RawSignature): any { - return { - noChainId: signature.noChainId, - checkpointerData: signature.checkpointerData ? Bytes.toHex(signature.checkpointerData) : undefined, - configuration: { - threshold: signature.configuration.threshold.toString(), - checkpoint: signature.configuration.checkpoint.toString(), - topology: rawTopologyToJson(signature.configuration.topology), - checkpointer: signature.configuration.checkpointer, - }, - suffix: signature.suffix ? signature.suffix.map((sig) => rawSignatureToJsonParsed(sig)) : undefined, - } -} - -function rawTopologyToJson(top: RawTopology): any { - if (Array.isArray(top)) { - return [rawTopologyToJson(top[0]), rawTopologyToJson(top[1])] - } - if (typeof top === 'object' && top !== null) { - if ('type' in top) { - switch (top.type) { - case 'signer': - return { - type: 'signer', - address: top.address, - weight: top.weight.toString(), - } - case 'sapient-signer': - return { - type: 'sapient-signer', - address: top.address, - weight: top.weight.toString(), - imageHash: top.imageHash, - } - case 'subdigest': - return { - type: 'subdigest', - digest: top.digest, - } - case 'any-address-subdigest': - return { - type: 'any-address-subdigest', - digest: top.digest, - } - case 'nested': - return { - type: 'nested', - tree: rawTopologyToJson(top.tree), - weight: top.weight.toString(), - threshold: top.threshold.toString(), - } - case 'unrecovered-signer': - return { - type: 'unrecovered-signer', - weight: top.weight.toString(), - signature: rawSignatureOfLeafToJson(top.signature), - } - default: - throw new Error('Invalid raw topology type') - } - } - } - if (typeof top === 'string') { - return top - } - throw new Error('Invalid raw topology format') -} - -function rawSignatureOfLeafToJson(sig: SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf): any { - if (sig.type === 'eth_sign' || sig.type === 'hash') { - return { - type: sig.type, - r: Hex.fromNumber(sig.r, { size: 32 }), - s: Hex.fromNumber(sig.s, { size: 32 }), - yParity: sig.yParity, - } - } - if (sig.type === 'erc1271') { - return { - type: sig.type, - address: sig.address, - data: sig.data, - } - } - if (sig.type === 'sapient' || sig.type === 'sapient_compact') { - return { - type: sig.type, - address: sig.address, - data: sig.data, - } - } - throw new Error('Unknown signature type in raw signature') -} - -export function rawSignatureFromJson(json: string): RawSignature { - const parsed = JSON.parse(json) - return rawSignatureFromParsed(parsed) -} - -function rawSignatureFromParsed(parsed: any): RawSignature { - return { - noChainId: parsed.noChainId, - checkpointerData: parsed.checkpointerData ? Bytes.fromHex(parsed.checkpointerData) : undefined, - configuration: { - threshold: BigInt(parsed.configuration.threshold), - checkpoint: BigInt(parsed.configuration.checkpoint), - topology: rawTopologyFromJson(parsed.configuration.topology), - checkpointer: parsed.configuration.checkpointer, - }, - suffix: parsed.suffix ? parsed.suffix.map((sig: any) => rawSignatureFromParsed(sig)) : undefined, - } -} - -function rawTopologyFromJson(obj: any): RawTopology { - if (Array.isArray(obj)) { - if (obj.length !== 2) { - throw new Error('Invalid raw topology node') - } - return [rawTopologyFromJson(obj[0]), rawTopologyFromJson(obj[1])] - } - if (typeof obj === 'object' && obj !== null) { - if ('type' in obj) { - switch (obj.type) { - case 'signer': - return { - type: 'signer', - address: obj.address, - weight: BigInt(obj.weight), - } - case 'sapient-signer': - return { - type: 'sapient-signer', - address: obj.address, - weight: BigInt(obj.weight), - imageHash: obj.imageHash, - } - case 'subdigest': - return { - type: 'subdigest', - digest: obj.digest, - } - case 'any-address-subdigest': - return { - type: 'any-address-subdigest', - digest: obj.digest, - } - case 'nested': - return { - type: 'nested', - tree: rawTopologyFromJson(obj.tree), - weight: BigInt(obj.weight), - threshold: BigInt(obj.threshold), - } - case 'unrecovered-signer': - return { - type: 'unrecovered-signer', - weight: BigInt(obj.weight), - signature: rawSignatureOfLeafFromJson(obj.signature), - } - default: - throw new Error('Invalid raw topology type') - } - } - } - if (typeof obj === 'string') { - return obj as Hex.Hex - } - throw new Error('Invalid raw topology format') -} - -function rawSignatureOfLeafFromJson(obj: any): SignatureOfSignerLeaf | SignatureOfSapientSignerLeaf { - switch (obj.type) { - case 'eth_sign': - case 'hash': - return { - type: obj.type, - r: Hex.toBigInt(obj.r), - s: Hex.toBigInt(obj.s), - yParity: obj.yParity, - } - case 'erc1271': - return { - type: 'erc1271', - address: obj.address, - data: obj.data, - } - case 'sapient': - case 'sapient_compact': - return { - type: obj.type, - address: obj.address, - data: obj.data, - } - default: - throw new Error('Invalid signature type in raw signature') - } -} - -export async function recover( - signature: RawSignature, - wallet: Address.Address, - chainId: number, - payload: Parented, - options?: { - provider?: Provider.Provider | { provider: Provider.Provider; block: number } | 'assume-valid' | 'assume-invalid' - }, -): Promise<{ configuration: Config; weight: bigint }> { - if (signature.suffix?.length) { - let invalid = false - - let { configuration, weight } = await recover( - { ...signature, suffix: undefined }, - wallet, - chainId, - payload, - options, - ) - - invalid ||= weight < configuration.threshold - - for (const subsignature of signature.suffix) { - const recovered = await recover( - subsignature, - wallet, - subsignature.noChainId ? 0 : chainId, - fromConfigUpdate(Bytes.toHex(hashConfiguration(configuration))), - options, - ) - - invalid ||= recovered.weight < recovered.configuration.threshold - invalid ||= recovered.configuration.checkpoint >= configuration.checkpoint - - configuration = recovered.configuration - weight = recovered.weight - } - - return { configuration, weight: invalid ? 0n : weight } - } - - const { topology, weight } = await recoverTopology( - signature.configuration.topology, - wallet, - chainId, - payload, - options, - ) - - return { configuration: { ...signature.configuration, topology }, weight } -} - -async function recoverTopology( - topology: RawTopology, - wallet: Address.Address, - chainId: number, - payload: Parented, - options?: { - provider?: Provider.Provider | { provider: Provider.Provider; block: number } | 'assume-valid' | 'assume-invalid' - throw?: boolean - }, -): Promise<{ topology: Topology; weight: bigint }> { - const digest = hash(wallet, chainId, payload) - - if (isRawSignerLeaf(topology)) { - switch (topology.signature.type) { - case 'eth_sign': - case 'hash': - return { - topology: { - type: 'signer', - address: Secp256k1.recoverAddress({ - payload: - topology.signature.type === 'eth_sign' - ? Hash.keccak256( - AbiParameters.encodePacked( - ['string', 'bytes32'], - ['\x19Ethereum Signed Message:\n32', Bytes.toHex(digest)], - ), - ) - : digest, - signature: topology.signature, - }), - weight: topology.weight, - signed: true, - signature: topology.signature, - }, - weight: topology.weight, - } - - case 'erc1271': - switch (options?.provider) { - case undefined: - case 'assume-invalid': - if (options?.throw !== false) { - throw new Error(`unable to validate signer ${topology.signature.address} erc-1271 signature`) - } else { - return { - topology: { type: 'signer', address: topology.signature.address, weight: topology.weight }, - weight: 0n, - } - } - - case 'assume-valid': - return { - topology: { - type: 'signer', - address: topology.signature.address, - weight: topology.weight, - signed: true, - signature: topology.signature, - }, - weight: topology.weight, - } - - default: { - const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider - const block = 'block' in options!.provider ? options!.provider.block : undefined - - const call = { - to: topology.signature.address, - data: AbiFunction.encodeData(IS_VALID_SIGNATURE, [Bytes.toHex(digest), topology.signature.data]), - } - - const response = await provider.request({ - method: 'eth_call', - params: block === undefined ? [call, 'latest'] : [call, Hex.fromNumber(block)], - }) - const decodedResult = AbiFunction.decodeResult(IS_VALID_SIGNATURE, response) - - if (Hex.isEqual(decodedResult, AbiFunction.getSelector(IS_VALID_SIGNATURE))) { - return { - topology: { - type: 'signer', - address: topology.signature.address, - weight: topology.weight, - signed: true, - signature: topology.signature, - }, - weight: topology.weight, - } - } else { - if (options?.throw !== false) { - throw new Error(`invalid signer ${topology.signature.address} erc-1271 signature`) - } else { - return { - topology: { type: 'signer', address: topology.signature.address, weight: topology.weight }, - weight: 0n, - } - } - } - } - } - - case 'sapient': - case 'sapient_compact': - switch (options?.provider) { - case undefined: - case 'assume-invalid': - case 'assume-valid': - throw new Error(`unable to validate sapient signer ${topology.signature.address} signature`) - - default: { - const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider - const block = 'block' in options!.provider ? options!.provider.block : undefined - - const call = { - to: topology.signature.address, - data: - topology.signature.type === 'sapient' - ? AbiFunction.encodeData(RECOVER_SAPIENT_SIGNATURE, [ - encode(chainId, payload), - topology.signature.data, - ]) - : AbiFunction.encodeData(RECOVER_SAPIENT_SIGNATURE_COMPACT, [ - Bytes.toHex(digest), - topology.signature.data, - ]), - } - - const response = await provider.request({ - method: 'eth_call', - params: block === undefined ? [call, 'latest'] : [call, Hex.fromNumber(block)], - }) - - return { - topology: { - type: 'sapient-signer', - address: topology.signature.address, - weight: topology.weight, - imageHash: response, - signed: true, - signature: topology.signature, - }, - weight: topology.weight, - } - } - } - } - } else if (isRawNestedLeaf(topology)) { - const { topology: tree, weight } = await recoverTopology(topology.tree, wallet, chainId, payload, options) - return { topology: { ...topology, tree }, weight: weight >= topology.threshold ? topology.weight : 0n } - } else if (isSignerLeaf(topology)) { - return { topology, weight: 0n } - } else if (isSapientSignerLeaf(topology)) { - return { topology, weight: 0n } - } else if (isSubdigestLeaf(topology)) { - return { - topology, - weight: Bytes.isEqual(Bytes.fromHex(topology.digest), digest) - ? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn - : 0n, - } - } else if (isAnyAddressSubdigestLeaf(topology)) { - const anyAddressOpHash = hash(Constants.ZeroAddress, chainId, payload) - return { - topology, - weight: Bytes.isEqual(Bytes.fromHex(topology.digest), anyAddressOpHash) - ? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn - : 0n, - } - } else if (isNodeLeaf(topology)) { - return { topology, weight: 0n } - } else { - const [left, right] = await Promise.all( - topology.map((topology) => recoverTopology(topology, wallet, chainId, payload, options)), - ) - return { topology: [left!.topology, right!.topology], weight: left!.weight + right!.weight } - } -} - -function encode( - chainId: number, - payload: Parented, -): Exclude, []>[0][0] { - switch (payload.type) { - case 'call': - return { - kind: 0, - noChainId: !chainId, - calls: payload.calls.map((call) => ({ - ...call, - data: call.data, - behaviorOnError: call.behaviorOnError === 'ignore' ? 0n : call.behaviorOnError === 'revert' ? 1n : 2n, - })), - space: payload.space, - nonce: payload.nonce, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - - case 'message': - return { - kind: 1, - noChainId: !chainId, - calls: [], - space: 0n, - nonce: 0n, - message: payload.message, - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - - case 'config-update': - return { - kind: 2, - noChainId: !chainId, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: payload.imageHash, - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: payload.parentWallets ?? [], - } - - case 'digest': - return { - kind: 3, - noChainId: !chainId, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: payload.digest, - parentWallets: payload.parentWallets ?? [], - } - - default: - throw new Error('Invalid payload type') - } -} diff --git a/packages/wallet/primitives/src/utils.ts b/packages/wallet/primitives/src/utils.ts deleted file mode 100644 index 3a2b28d468..0000000000 --- a/packages/wallet/primitives/src/utils.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Bytes } from 'ox' - -export function minBytesFor(val: bigint): number { - return Math.ceil(val.toString(16).length / 2) -} - -// ERC-2098 -export function packRSY({ r, s, yParity }: { r: bigint; s: bigint; yParity: number }): Bytes.Bytes { - const rBytes = Bytes.padLeft(Bytes.fromNumber(r), 32) - const sBytes = Bytes.padLeft(Bytes.fromNumber(s), 32) - if (yParity % 2 === 1) { - sBytes[0]! |= 0x80 - } - - return Bytes.concat(rBytes, sBytes) -} - -export function unpackRSY(rsy: Bytes.Bytes): { r: bigint; s: bigint; yParity: number } { - const r = Bytes.toBigInt(rsy.slice(0, 32)) - const yParityAndS = rsy.slice(32, 64) - const yParity = (yParityAndS[0]! & 0x80) !== 0 ? 1 : 0 - const sBytes = new Uint8Array(yParityAndS) - sBytes[0] = sBytes[0]! & 0x7f - const s = Bytes.toBigInt(sBytes) - return { r, s, yParity } -} - -/** - * Creates a replacer function for JSON.stringify that handles BigInt and Uint8Array serialization - * Converts BigInt values to objects with format { __bigint: "0x..." } - * Converts Uint8Array values to objects with format { __uint8array: [...] } - * @param customReplacer Optional custom replacer function to apply after BigInt/Uint8Array handling - */ -export function createJSONReplacer( - customReplacer?: (key: string, value: any) => any, -): (key: string, value: any) => any { - return (key: string, value: any) => { - // Handle BigInt conversion first - if (typeof value === 'bigint') { - return { - __bigint: '0x' + value.toString(16), - } - } - // Handle Uint8Array conversion - if (value instanceof Uint8Array) { - return { - __uint8array: Array.from(value), - } - } - // Then apply custom replacer if provided - return customReplacer ? customReplacer(key, value) : value - } -} - -/** - * Creates a reviver function for JSON.parse that handles BigInt and Uint8Array deserialization - * Converts objects with { __bigint: "0x..." } format back to BigInt - * Converts objects with { __uint8array: [...] } format back to Uint8Array - * @param customReviver Optional custom reviver function to apply after BigInt/Uint8Array handling - */ -export function createJSONReviver(customReviver?: (key: string, value: any) => any): (key: string, value: any) => any { - return (key: string, value: any) => { - // Handle BigInt conversion - if (value && typeof value === 'object' && '__bigint' in value && Object.keys(value).length === 1) { - const hex = value.__bigint - if (typeof hex === 'string' && hex.startsWith('0x')) { - return BigInt(hex) - } - } - // Handle Uint8Array conversion - if (value && typeof value === 'object' && '__uint8array' in value && Object.keys(value).length === 1) { - const arr = value.__uint8array - if (Array.isArray(arr)) { - return new Uint8Array(arr) - } - } - // Then apply custom reviver if provided - return customReviver ? customReviver(key, value) : value - } -} - -/** - * Serializes data to JSON string with BigInt and Uint8Array support - * Converts BigInt values to objects with format { __bigint: "0x..." } - * Converts Uint8Array values to objects with format { __uint8array: [...] } - * @param obj The object to serialize - * @param space Adds indentation, white space, and line break characters to the return-value JSON text - * @param replacer A function that transforms the results or an array of strings and numbers that acts as an approved list for selecting the object properties - */ -export function toJSON( - obj: any, - replacer?: (number | string)[] | null | ((this: any, key: string, value: any) => any), - space?: string | number, -): string { - const finalReplacer = replacer instanceof Function ? createJSONReplacer(replacer) : createJSONReplacer() - return JSON.stringify(obj, finalReplacer, space) -} - -/** - * Deserializes JSON string with BigInt and Uint8Array support - * Converts objects with { __bigint: "0x..." } format back to BigInt - * Converts objects with { __uint8array: [...] } format back to Uint8Array - * @param text The string to parse as JSON - * @param reviver A function that transforms the results - */ -export function fromJSON(text: string, reviver?: (this: any, key: string, value: any) => any): any { - const finalReviver = reviver ? createJSONReviver(reviver) : createJSONReviver() - return JSON.parse(text, finalReviver) -} diff --git a/packages/wallet/primitives/test/address.test.ts b/packages/wallet/primitives/test/address.test.ts deleted file mode 100644 index b004dc96d4..0000000000 --- a/packages/wallet/primitives/test/address.test.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Address, Bytes, Hash, Hex } from 'ox' - -import { from } from '../src/address.js' -import { Context, Dev1, Dev2, Rc3, Rc4, Rc5 } from '../src/context.js' -import { Config, hashConfiguration } from '../src/config.js' - -describe('Address', () => { - const mockContext: Omit = { - factory: '0xe828630697817291140D6B7A42a2c3b7277bE45a', - stage1: '0x2a4fB19F66F1427A5E363Bf1bB3be27b9A9ACC39', - creationCode: '0x603e600e3d39601e805130553df33d3d34601c57363d3d373d363d30545af43d82803e903d91601c57fd5bf3', - } - - const sampleConfig: Config = { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - } - - describe('from', () => { - it('should generate deterministic address from Config object', () => { - const address = from(sampleConfig, mockContext) - - // Should return a valid address - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be deterministic - same inputs should produce same output - const address2 = from(sampleConfig, mockContext) - expect(address).toBe(address2) - }) - - it('should generate deterministic address from bytes configuration', () => { - const configHash = hashConfiguration(sampleConfig) - const address = from(configHash, mockContext) - - // Should return a valid address - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should produce same address as Config object - const addressFromConfig = from(sampleConfig, mockContext) - expect(address).toBe(addressFromConfig) - }) - - it('should generate different addresses for different configurations', () => { - const config1: Config = { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - } - - const config2: Config = { - threshold: 2n, // Different threshold - checkpoint: 0n, - topology: { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - } - - const address1 = from(config1, mockContext) - const address2 = from(config2, mockContext) - - expect(address1).not.toBe(address2) - }) - - it('should generate different addresses for different contexts', () => { - const address1 = from(sampleConfig, mockContext) - const address2 = from(sampleConfig, { - factory: '0xFE14B91dE3c5Ca74c4D24608EBcD4B2848aA6010', - stage1: '0x300E98ae5bEA4A7291d62Eb0b9feD535E10095dD', - creationCode: - '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', - }) - - expect(address1).not.toBe(address2) - }) - - it('should work with Dev1 context', () => { - const { stage2: _, ...dev1Context } = Dev1 - const address = from(sampleConfig, dev1Context) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should work with Dev2 context', () => { - const { stage2: _stage2_1, ...dev2Context } = Dev2 - const address = from(sampleConfig, dev2Context) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be different from Dev1 - const { stage2: _stage2_2, ...dev1Context } = Dev1 - const dev1Address = from(sampleConfig, dev1Context) - expect(address).not.toBe(dev1Address) - }) - - it('should work with Rc3 context', () => { - const { stage2: _stage2_1, ...rc3Context } = Rc3 - const address = from(sampleConfig, rc3Context) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be different from Dev2 - const { stage2: _stage2_2, ...dev2Context } = Dev2 - const dev2Address = from(sampleConfig, dev2Context) - expect(address).not.toBe(dev2Address) - }) - - it('should work with Rc4 context', () => { - const { stage2: _stage2_1, ...rc4Context } = Rc4 - const address = from(sampleConfig, rc4Context) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be different from Dev2 - const { stage2: _stage2_2, ...dev2Context } = Dev2 - const dev2Address = from(sampleConfig, dev2Context) - expect(address).not.toBe(dev2Address) - }) - - it('should work with Rc5 context', () => { - const { stage2: _stage2_1, ...rc5Context } = Rc5 - const address = from(sampleConfig, rc5Context) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be different from Dev2 - const { stage2: _stage2_2, ...dev2Context } = Dev2 - const dev2Address = from(sampleConfig, dev2Context) - expect(address).not.toBe(dev2Address) - }) - - it('should handle complex topology configurations', () => { - const complexConfig: Config = { - threshold: 2n, - checkpoint: 42n, - topology: [ - { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - { - type: 'signer', - address: '0x8ba1f109551bD432803012645aac136c776056C0', - weight: 1n, - }, - ], - } - - const address = from(complexConfig, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should handle nested topology configurations', () => { - const nestedConfig: Config = { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'nested', - weight: 1n, - threshold: 1n, - tree: { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - }, - } - - const address = from(nestedConfig, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should handle sapient signer configurations', () => { - const sapientConfig: Config = { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'sapient-signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - imageHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - }, - } - - const address = from(sapientConfig, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should handle configurations with checkpointer', () => { - const configWithCheckpointer: Config = { - threshold: 1n, - checkpoint: 100n, - checkpointer: '0x1234567890123456789012345678901234567890', - topology: { - type: 'signer', - address: '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1', - weight: 1n, - }, - } - - const address = from(configWithCheckpointer, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - - // Should be different from config without checkpointer - const configWithoutCheckpointer = { ...configWithCheckpointer } - delete configWithoutCheckpointer.checkpointer - const addressWithoutCheckpointer = from(configWithoutCheckpointer, mockContext) - expect(address).not.toBe(addressWithoutCheckpointer) - }) - - it('should handle zero hash input', () => { - const zeroHash = new Uint8Array(32).fill(0) - const address = from(zeroHash, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should handle maximum hash input', () => { - const maxHash = new Uint8Array(32).fill(255) - const address = from(maxHash, mockContext) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should produce different addresses for different factory addresses', () => { - const context1 = { - ...mockContext, - factory: '0x1111111111111111111111111111111111111111' as Address.Address, - } - - const context2 = { - ...mockContext, - factory: '0x2222222222222222222222222222222222222222' as Address.Address, - } - - const address1 = from(sampleConfig, context1) - const address2 = from(sampleConfig, context2) - - expect(address1).not.toBe(address2) - }) - - it('should produce different addresses for different stage1 addresses', () => { - const context1 = { - ...mockContext, - stage1: '0x1111111111111111111111111111111111111111' as Address.Address, - } - - const context2 = { - ...mockContext, - stage1: '0x2222222222222222222222222222222222222222' as Address.Address, - } - - const address1 = from(sampleConfig, context1) - const address2 = from(sampleConfig, context2) - - expect(address1).not.toBe(address2) - }) - - it('should produce different addresses for different creation code', () => { - const context1 = { - ...mockContext, - creationCode: '0x1111' as Hex.Hex, - } - - const context2 = { - ...mockContext, - creationCode: '0x2222' as Hex.Hex, - } - - const address1 = from(sampleConfig, context1) - const address2 = from(sampleConfig, context2) - - expect(address1).not.toBe(address2) - }) - - it('should implement CREATE2 address generation correctly', () => { - // This test verifies the CREATE2 formula: keccak256(0xff ++ factory ++ salt ++ keccak256(creationCode ++ stage1))[12:] - const configHash = hashConfiguration(sampleConfig) - - // Manual computation to verify the algorithm - const initCodeHash = Hash.keccak256( - Bytes.concat(Bytes.from(mockContext.creationCode), Bytes.padLeft(Bytes.from(mockContext.stage1), 32)), - ) - - const addressHash = Hash.keccak256( - Bytes.concat(Bytes.from('0xff'), Bytes.from(mockContext.factory), configHash, initCodeHash), - { as: 'Bytes' }, - ) - - const expectedAddress = Bytes.toHex(addressHash.subarray(12)) - const actualAddress = from(sampleConfig, mockContext) - - expect(actualAddress).toBe(expectedAddress) - }) - - it('should handle empty creation code', () => { - const contextWithEmptyCode = { - ...mockContext, - creationCode: '0x' as Hex.Hex, - } - - const address = from(sampleConfig, contextWithEmptyCode) - - expect(() => Address.assert(address)).not.toThrow() - expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) - }) - - it('should be consistent across multiple calls with same inputs', () => { - const addresses = Array.from({ length: 10 }, () => from(sampleConfig, mockContext)) - - // All addresses should be identical - addresses.forEach((address) => { - expect(address).toBe(addresses[0]) - }) - }) - }) -}) diff --git a/packages/wallet/primitives/test/attestation.test.ts b/packages/wallet/primitives/test/attestation.test.ts deleted file mode 100644 index 3708132a73..0000000000 --- a/packages/wallet/primitives/test/attestation.test.ts +++ /dev/null @@ -1,419 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Bytes, Hash } from 'ox' - -import { - Attestation, - AuthData, - encode, - encodeAuthData, - decode, - decodeAuthData, - hash, - toJson, - encodeForJson, - fromJson, - fromParsed, - ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX, - generateImplicitRequestMagic, -} from '../src/attestation.js' - -describe('Attestation', () => { - const sampleAuthData: AuthData = { - redirectUrl: 'https://example.com/callback', - issuedAt: 1234567890n, - } - - const sampleAttestation: Attestation = { - approvedSigner: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - identityType: Bytes.fromHex('0x12345678'), - issuerHash: Bytes.fromHex('0x1111111111111111111111111111111111111111111111111111111111111111'), - audienceHash: Bytes.fromHex('0x2222222222222222222222222222222222222222222222222222222222222222'), - applicationData: Bytes.fromString('test-app-data'), - authData: sampleAuthData, - } - - describe('AuthData encoding/decoding', () => { - it('should encode AuthData correctly', () => { - const encoded = encodeAuthData(sampleAuthData) - - // Should be deterministic - const encoded2 = encodeAuthData(sampleAuthData) - expect(Bytes.isEqual(encoded, encoded2)).toBe(true) - - // Should have correct structure: 3 bytes length + url + 8 bytes timestamp - const expectedLength = 3 + sampleAuthData.redirectUrl.length + 8 - expect(encoded.length).toBe(expectedLength) - }) - - it('should decode AuthData correctly', () => { - const encoded = encodeAuthData(sampleAuthData) - const decoded = decodeAuthData(encoded) - - expect(decoded.redirectUrl).toBe(sampleAuthData.redirectUrl) - expect(decoded.issuedAt).toBe(sampleAuthData.issuedAt) - }) - - it('should handle round-trip encoding/decoding for AuthData', () => { - const encoded = encodeAuthData(sampleAuthData) - const decoded = decodeAuthData(encoded) - const reencoded = encodeAuthData(decoded) - - expect(Bytes.isEqual(encoded, reencoded)).toBe(true) - }) - - it('should handle empty redirect URL', () => { - const authDataWithEmptyUrl: AuthData = { - redirectUrl: '', - issuedAt: 123n, - } - - const encoded = encodeAuthData(authDataWithEmptyUrl) - const decoded = decodeAuthData(encoded) - - expect(decoded.redirectUrl).toBe('') - expect(decoded.issuedAt).toBe(123n) - }) - - it('should handle long redirect URLs', () => { - const longUrl = 'https://example.com/very/long/path/with/many/segments/' + 'a'.repeat(100) - const authDataWithLongUrl: AuthData = { - redirectUrl: longUrl, - issuedAt: 456n, - } - - const encoded = encodeAuthData(authDataWithLongUrl) - const decoded = decodeAuthData(encoded) - - expect(decoded.redirectUrl).toBe(longUrl) - expect(decoded.issuedAt).toBe(456n) - }) - - it('should handle maximum timestamp values', () => { - const maxTimestamp = BigInt('18446744073709551615') // 2^64 - 1 - const authDataWithMaxTimestamp: AuthData = { - redirectUrl: 'https://example.com', - issuedAt: maxTimestamp, - } - - const encoded = encodeAuthData(authDataWithMaxTimestamp) - const decoded = decodeAuthData(encoded) - - expect(decoded.issuedAt).toBe(maxTimestamp) - }) - }) - - describe('Attestation encoding/decoding', () => { - it('should encode Attestation correctly', () => { - const encoded = encode(sampleAttestation) - - // Should be deterministic - const encoded2 = encode(sampleAttestation) - expect(Bytes.isEqual(encoded, encoded2)).toBe(true) - - // Should contain all expected parts - expect(encoded.length).toBeGreaterThan(20 + 4 + 32 + 32 + 3) // Minimum size - }) - - it('should decode Attestation correctly', () => { - const encoded = encode(sampleAttestation) - const decoded = decode(encoded) - - expect(decoded.approvedSigner).toBe(sampleAttestation.approvedSigner) - expect(Bytes.isEqual(decoded.identityType, sampleAttestation.identityType)).toBe(true) - expect(Bytes.isEqual(decoded.issuerHash, sampleAttestation.issuerHash)).toBe(true) - expect(Bytes.isEqual(decoded.audienceHash, sampleAttestation.audienceHash)).toBe(true) - expect(Bytes.isEqual(decoded.applicationData, sampleAttestation.applicationData)).toBe(true) - expect(decoded.authData.redirectUrl).toBe(sampleAttestation.authData.redirectUrl) - expect(decoded.authData.issuedAt).toBe(sampleAttestation.authData.issuedAt) - }) - - it('should handle round-trip encoding/decoding for Attestation', () => { - const encoded = encode(sampleAttestation) - const decoded = decode(encoded) - const reencoded = encode(decoded) - - expect(Bytes.isEqual(encoded, reencoded)).toBe(true) - }) - - it('should handle identity type truncation', () => { - const attestationWithLongIdentityType: Attestation = { - ...sampleAttestation, - identityType: Bytes.fromHex('0x123456789abcdef0'), // 8 bytes, should be truncated to 4 - } - - const encoded = encode(attestationWithLongIdentityType) - const decoded = decode(encoded) - - // Should be truncated to first 4 bytes - expect(decoded.identityType.length).toBe(4) - expect(Bytes.toHex(decoded.identityType)).toBe('0x12345678') - }) - - it('should handle empty application data', () => { - const attestationWithEmptyAppData: Attestation = { - ...sampleAttestation, - applicationData: new Uint8Array(0), - } - - const encoded = encode(attestationWithEmptyAppData) - const decoded = decode(encoded) - - expect(decoded.applicationData.length).toBe(0) - }) - - it('should handle large application data', () => { - const largeAppData = new Uint8Array(1000).fill(0xaa) - const attestationWithLargeAppData: Attestation = { - ...sampleAttestation, - applicationData: largeAppData, - } - - const encoded = encode(attestationWithLargeAppData) - const decoded = decode(encoded) - - expect(Bytes.isEqual(decoded.applicationData, largeAppData)).toBe(true) - }) - - it('should handle different address formats', () => { - const attestationWithDifferentAddress: Attestation = { - ...sampleAttestation, - approvedSigner: '0x8ba1f109551bd432803012645aac136c776056c0', - } - - const encoded = encode(attestationWithDifferentAddress) - const decoded = decode(encoded) - - expect(decoded.approvedSigner).toBe(attestationWithDifferentAddress.approvedSigner) - }) - }) - - describe('hash function', () => { - it('should generate consistent hash for same attestation', () => { - const hash1 = hash(sampleAttestation) - const hash2 = hash(sampleAttestation) - - expect(Bytes.isEqual(hash1, hash2)).toBe(true) - expect(hash1.length).toBe(32) // keccak256 produces 32 bytes - }) - - it('should generate different hashes for different attestations', () => { - const differentAttestation: Attestation = { - ...sampleAttestation, - approvedSigner: '0x8ba1f109551bd432803012645aac136c776056c0', - } - - const hash1 = hash(sampleAttestation) - const hash2 = hash(differentAttestation) - - expect(Bytes.isEqual(hash1, hash2)).toBe(false) - }) - - it('should match manual hash calculation', () => { - const encoded = encode(sampleAttestation) - const manualHash = Hash.keccak256(encoded) - const functionHash = hash(sampleAttestation) - - expect(Bytes.isEqual(manualHash, functionHash)).toBe(true) - }) - }) - - describe('JSON serialization', () => { - it('should encode for JSON correctly', () => { - const jsonObj = encodeForJson(sampleAttestation) - - expect(jsonObj.approvedSigner).toBe(sampleAttestation.approvedSigner) - expect(jsonObj.identityType).toBe(Bytes.toHex(sampleAttestation.identityType)) - expect(jsonObj.issuerHash).toBe(Bytes.toHex(sampleAttestation.issuerHash)) - expect(jsonObj.audienceHash).toBe(Bytes.toHex(sampleAttestation.audienceHash)) - expect(jsonObj.applicationData).toBe(Bytes.toHex(sampleAttestation.applicationData)) - expect(jsonObj.authData.redirectUrl).toBe(sampleAttestation.authData.redirectUrl) - expect(jsonObj.authData.issuedAt).toBe(sampleAttestation.authData.issuedAt.toString()) - }) - - it('should convert to JSON string correctly', () => { - const jsonString = toJson(sampleAttestation) - - expect(typeof jsonString).toBe('string') - expect(() => JSON.parse(jsonString)).not.toThrow() - - const parsed = JSON.parse(jsonString) - expect(parsed.approvedSigner).toBe(sampleAttestation.approvedSigner) - }) - - it('should convert to JSON string with indentation', () => { - const jsonString = toJson(sampleAttestation, 2) - - expect(jsonString).toContain('\n') // Should have newlines due to indentation - expect(jsonString).toContain(' ') // Should have 2-space indentation - }) - - it('should parse from JSON string correctly', () => { - const jsonString = toJson(sampleAttestation) - const parsed = fromJson(jsonString) - - expect(parsed.approvedSigner).toBe(sampleAttestation.approvedSigner) - expect(Bytes.isEqual(parsed.identityType, sampleAttestation.identityType)).toBe(true) - expect(Bytes.isEqual(parsed.issuerHash, sampleAttestation.issuerHash)).toBe(true) - expect(Bytes.isEqual(parsed.audienceHash, sampleAttestation.audienceHash)).toBe(true) - expect(Bytes.isEqual(parsed.applicationData, sampleAttestation.applicationData)).toBe(true) - expect(parsed.authData.redirectUrl).toBe(sampleAttestation.authData.redirectUrl) - expect(parsed.authData.issuedAt).toBe(sampleAttestation.authData.issuedAt) - }) - - it('should parse from parsed object correctly', () => { - const jsonObj = encodeForJson(sampleAttestation) - const parsed = fromParsed(jsonObj) - - expect(parsed.approvedSigner).toBe(sampleAttestation.approvedSigner) - expect(Bytes.isEqual(parsed.identityType, sampleAttestation.identityType)).toBe(true) - expect(Bytes.isEqual(parsed.issuerHash, sampleAttestation.issuerHash)).toBe(true) - expect(Bytes.isEqual(parsed.audienceHash, sampleAttestation.audienceHash)).toBe(true) - expect(Bytes.isEqual(parsed.applicationData, sampleAttestation.applicationData)).toBe(true) - expect(parsed.authData.redirectUrl).toBe(sampleAttestation.authData.redirectUrl) - expect(parsed.authData.issuedAt).toBe(sampleAttestation.authData.issuedAt) - }) - - it('should handle round-trip JSON serialization', () => { - const jsonString = toJson(sampleAttestation) - const parsed = fromJson(jsonString) - const reencoded = toJson(parsed) - - expect(jsonString).toBe(reencoded) - }) - }) - - describe('Library functions', () => { - it('should have correct ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX', () => { - const expectedPrefix = Hash.keccak256(Bytes.fromString('acceptImplicitRequest')) - - expect(Bytes.isEqual(ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX, expectedPrefix)).toBe(true) - expect(ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX.length).toBe(32) - }) - - it('should generate implicit request magic correctly', () => { - const wallet = '0x1234567890123456789012345678901234567890' - const magic = generateImplicitRequestMagic(sampleAttestation, wallet) - - expect(magic.length).toBe(32) // keccak256 produces 32 bytes - - // Should be deterministic - const magic2 = generateImplicitRequestMagic(sampleAttestation, wallet) - expect(Bytes.isEqual(magic, magic2)).toBe(true) - }) - - it('should generate different magic for different wallets', () => { - const wallet1 = '0x1111111111111111111111111111111111111111' - const wallet2 = '0x2222222222222222222222222222222222222222' - - const magic1 = generateImplicitRequestMagic(sampleAttestation, wallet1) - const magic2 = generateImplicitRequestMagic(sampleAttestation, wallet2) - - expect(Bytes.isEqual(magic1, magic2)).toBe(false) - }) - - it('should generate different magic for different attestations', () => { - const wallet = '0x1234567890123456789012345678901234567890' - const differentAttestation: Attestation = { - ...sampleAttestation, - audienceHash: Bytes.fromHex('0x3333333333333333333333333333333333333333333333333333333333333333'), - } - - const magic1 = generateImplicitRequestMagic(sampleAttestation, wallet) - const magic2 = generateImplicitRequestMagic(differentAttestation, wallet) - - expect(Bytes.isEqual(magic1, magic2)).toBe(false) - }) - - it('should generate magic matching manual calculation', () => { - const wallet = '0x1234567890123456789012345678901234567890' - - const manualMagic = Hash.keccak256( - Bytes.concat( - ACCEPT_IMPLICIT_REQUEST_MAGIC_PREFIX, - Bytes.fromHex(wallet, { size: 20 }), - sampleAttestation.audienceHash, - sampleAttestation.issuerHash, - ), - ) - - const functionMagic = generateImplicitRequestMagic(sampleAttestation, wallet) - - expect(Bytes.isEqual(manualMagic, functionMagic)).toBe(true) - }) - }) - - describe('Edge cases and error conditions', () => { - it('should handle attestation with minimal data', () => { - const minimalAttestation: Attestation = { - approvedSigner: '0x0000000000000000000000000000000000000000', - identityType: new Uint8Array(4), - issuerHash: new Uint8Array(32), - audienceHash: new Uint8Array(32), - applicationData: new Uint8Array(0), - authData: { - redirectUrl: '', - issuedAt: 0n, - }, - } - - const encoded = encode(minimalAttestation) - const decoded = decode(encoded) - - expect(decoded.approvedSigner).toBe(minimalAttestation.approvedSigner) - expect(decoded.authData.issuedAt).toBe(0n) - }) - - it('should handle attestation with maximum application data size', () => { - // 3 bytes can represent up to 16,777,215 (0xFFFFFF) - const maxAppDataSize = 0xffffff - const largeAppData = new Uint8Array(Math.min(maxAppDataSize, 10000)) // Use smaller size for test performance - largeAppData.fill(0x42) - - const attestationWithMaxData: Attestation = { - ...sampleAttestation, - applicationData: largeAppData, - } - - const encoded = encode(attestationWithMaxData) - const decoded = decode(encoded) - - expect(Bytes.isEqual(decoded.applicationData, largeAppData)).toBe(true) - }) - - it('should handle ASCII redirect URLs', () => { - const asciiUrlAttestation: Attestation = { - ...sampleAttestation, - authData: { - redirectUrl: 'https://example.com/callback?param=value&other=test', - issuedAt: 1234567890n, - }, - } - - const encoded = encode(asciiUrlAttestation) - const decoded = decode(encoded) - - expect(decoded.authData.redirectUrl).toBe(asciiUrlAttestation.authData.redirectUrl) - }) - - it('should maintain byte precision in round-trip operations', () => { - // Test with specific byte patterns - const precisionAttestation: Attestation = { - approvedSigner: '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', - identityType: Bytes.fromHex('0xCAFEBABE'), - issuerHash: Bytes.fromHex('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'), - audienceHash: Bytes.fromHex('0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210'), - applicationData: Bytes.fromHex('0x00010203040506070809'), - authData: { - redirectUrl: 'https://test.example', - issuedAt: 0x123456789abcdef0n, - }, - } - - const encoded = encode(precisionAttestation) - const decoded = decode(encoded) - const reencoded = encode(decoded) - - expect(Bytes.isEqual(encoded, reencoded)).toBe(true) - }) - }) -}) diff --git a/packages/wallet/primitives/test/config.test.ts b/packages/wallet/primitives/test/config.test.ts deleted file mode 100644 index b4ff8e6d91..0000000000 --- a/packages/wallet/primitives/test/config.test.ts +++ /dev/null @@ -1,995 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Bytes } from 'ox' - -import { - Config, - Topology, - SignerLeaf, - SapientSignerLeaf, - SubdigestLeaf, - AnyAddressSubdigestLeaf, - NestedLeaf, - NodeLeaf, - Node, - isSignerLeaf, - isSapientSignerLeaf, - isSubdigestLeaf, - isAnyAddressSubdigestLeaf, - isNodeLeaf, - isNestedLeaf, - isNode, - isConfig, - isLeaf, - isTopology, - getSigners, - findSignerLeaf, - getWeight, - hashConfiguration, - flatLeavesToTopology, - configToJson, - configFromJson, - mergeTopology, - hasInvalidValues, - maximumDepth, - evaluateConfigurationSafety, - normalizeSignerSignature, - replaceAddress, -} from '../src/config.js' - -describe('Config', () => { - const testAddress1 = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' - const replacementAddress = '0x1111111111111111111111111111111111111111' - const testImageHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const testDigest = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' - - const sampleSignerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - } - - const sampleSapientSignerLeaf: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress2, - weight: 2n, - imageHash: testImageHash, - } - - const sampleSubdigestLeaf: SubdigestLeaf = { - type: 'subdigest', - digest: testDigest, - } - - const sampleAnyAddressSubdigestLeaf: AnyAddressSubdigestLeaf = { - type: 'any-address-subdigest', - digest: testDigest, - } - - const sampleNodeLeaf: NodeLeaf = '0x1111111111111111111111111111111111111111111111111111111111111111' - - const sampleNestedLeaf: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 3n, - threshold: 1n, - } - - const sampleNode: Node = [sampleSignerLeaf, sampleSapientSignerLeaf] - - const sampleConfig: Config = { - threshold: 2n, - checkpoint: 100n, - topology: sampleNode, - checkpointer: testAddress1, - } - - const sampleConfigWithNestedLeaf: Config = { - threshold: 2n, - checkpoint: 100n, - topology: sampleNestedLeaf, - checkpointer: testAddress1, - } - - describe('Type Guards', () => { - describe('isSignerLeaf', () => { - it('should return true for valid signer leaf', () => { - expect(isSignerLeaf(sampleSignerLeaf)).toBe(true) - }) - - it('should return false for other types', () => { - expect(isSignerLeaf(sampleSapientSignerLeaf)).toBe(false) - expect(isSignerLeaf(sampleSubdigestLeaf)).toBe(false) - expect(isSignerLeaf(sampleNode)).toBe(false) - expect(isSignerLeaf(null)).toBe(false) - expect(isSignerLeaf(undefined)).toBe(false) - expect(isSignerLeaf('string')).toBe(false) - }) - }) - - describe('isSapientSignerLeaf', () => { - it('should return true for valid sapient signer leaf', () => { - expect(isSapientSignerLeaf(sampleSapientSignerLeaf)).toBe(true) - }) - - it('should return false for other types', () => { - expect(isSapientSignerLeaf(sampleSignerLeaf)).toBe(false) - expect(isSapientSignerLeaf(sampleSubdigestLeaf)).toBe(false) - expect(isSapientSignerLeaf(sampleNode)).toBe(false) - expect(isSapientSignerLeaf(null)).toBe(false) - }) - }) - - describe('isSubdigestLeaf', () => { - it('should return true for valid subdigest leaf', () => { - expect(isSubdigestLeaf(sampleSubdigestLeaf)).toBe(true) - }) - - it('should return false for other types', () => { - expect(isSubdigestLeaf(sampleSignerLeaf)).toBe(false) - expect(isSubdigestLeaf(sampleNode)).toBe(false) - expect(isSubdigestLeaf(null)).toBe(false) - }) - }) - - describe('isAnyAddressSubdigestLeaf', () => { - it('should return true for valid any-address-subdigest leaf', () => { - expect(isAnyAddressSubdigestLeaf(sampleAnyAddressSubdigestLeaf)).toBe(true) - }) - - it('should return false for other types', () => { - expect(isAnyAddressSubdigestLeaf(sampleSubdigestLeaf)).toBe(false) - expect(isAnyAddressSubdigestLeaf(sampleSignerLeaf)).toBe(false) - expect(isAnyAddressSubdigestLeaf(null)).toBe(false) - }) - }) - - describe('isNodeLeaf', () => { - it('should return true for valid node leaf (66 char hex)', () => { - expect(isNodeLeaf(sampleNodeLeaf)).toBe(true) - }) - - it('should return false for invalid hex or wrong length', () => { - expect(isNodeLeaf('0x1234')).toBe(false) // Too short - expect(isNodeLeaf('not-hex')).toBe(false) - expect(isNodeLeaf(sampleSignerLeaf)).toBe(false) - expect(isNodeLeaf(null)).toBe(false) - }) - }) - - describe('isNestedLeaf', () => { - it('should return true for valid nested leaf', () => { - expect(isNestedLeaf(sampleNestedLeaf)).toBe(true) - }) - - it('should return false for other types', () => { - expect(isNestedLeaf(sampleSignerLeaf)).toBe(false) - expect(isNestedLeaf(sampleNode)).toBe(false) - expect(isNestedLeaf(null)).toBe(false) - }) - }) - - describe('isNode', () => { - it('should return true for valid node (array of 2 topologies)', () => { - expect(isNode(sampleNode)).toBe(true) - }) - - it('should return false for invalid nodes', () => { - expect(isNode([sampleSignerLeaf])).toBe(false) // Wrong length - expect(isNode([sampleSignerLeaf, sampleSignerLeaf, sampleSignerLeaf])).toBe(false) // Wrong length - expect(isNode(['invalid', 'invalid'])).toBe(false) // Invalid topologies - expect(isNode(sampleSignerLeaf)).toBe(false) - expect(isNode(null)).toBe(false) - }) - }) - - describe('isConfig', () => { - it('should return true for valid config', () => { - expect(isConfig(sampleConfig)).toBe(true) - }) - - it('should return false for invalid configs', () => { - expect(isConfig({ threshold: 1n })).toBe(false) // Missing fields - expect(isConfig(sampleSignerLeaf)).toBe(false) - expect(isConfig(undefined)).toBe(false) - // Note: null would trigger a bug in isConfig function - 'in' operator used without null check - }) - }) - - describe('isLeaf', () => { - it('should return true for all leaf types', () => { - expect(isLeaf(sampleSignerLeaf)).toBe(true) - expect(isLeaf(sampleSapientSignerLeaf)).toBe(true) - expect(isLeaf(sampleSubdigestLeaf)).toBe(true) - expect(isLeaf(sampleAnyAddressSubdigestLeaf)).toBe(true) - expect(isLeaf(sampleNodeLeaf)).toBe(true) - expect(isLeaf(sampleNestedLeaf)).toBe(true) - }) - - it('should return false for nodes', () => { - expect(isLeaf(sampleNode)).toBe(false) - }) - }) - - describe('isTopology', () => { - it('should return true for all topology types', () => { - expect(isTopology(sampleNode)).toBe(true) - expect(isTopology(sampleSignerLeaf)).toBe(true) - expect(isTopology(sampleSapientSignerLeaf)).toBe(true) - expect(isTopology(sampleSubdigestLeaf)).toBe(true) - expect(isTopology(sampleNestedLeaf)).toBe(true) - }) - - it('should return false for invalid topologies', () => { - expect(isTopology(null)).toBe(false) - expect(isTopology('invalid')).toBe(false) - expect(isTopology({})).toBe(false) - }) - }) - }) - - describe('getSigners', () => { - it('should extract signers from simple topology', () => { - const result = getSigners(sampleSignerLeaf) - - expect(result.signers).toEqual([testAddress1]) - expect(result.sapientSigners).toEqual([]) - expect(result.isComplete).toBe(true) - }) - - it('should extract sapient signers', () => { - const result = getSigners(sampleSapientSignerLeaf) - - expect(result.signers).toEqual([]) - expect(result.sapientSigners).toEqual([{ address: testAddress2, imageHash: testImageHash }]) - expect(result.isComplete).toBe(true) - }) - - it('should handle complex node topology', () => { - const result = getSigners(sampleNode) - - expect(result.signers).toEqual([testAddress1]) - expect(result.sapientSigners).toEqual([{ address: testAddress2, imageHash: testImageHash }]) - expect(result.isComplete).toBe(true) - }) - - it('should handle config input', () => { - const result = getSigners(sampleConfig) - - expect(result.signers).toEqual([testAddress1]) - expect(result.sapientSigners).toEqual([{ address: testAddress2, imageHash: testImageHash }]) - expect(result.isComplete).toBe(true) - }) - - it('should handle nested topology', () => { - const result = getSigners(sampleNestedLeaf) - - expect(result.signers).toEqual([testAddress1]) - expect(result.sapientSigners).toEqual([]) - expect(result.isComplete).toBe(true) - }) - - it('should mark incomplete when node leaf present', () => { - const result = getSigners(sampleNodeLeaf) - - expect(result.signers).toEqual([]) - expect(result.sapientSigners).toEqual([]) - expect(result.isComplete).toBe(false) - }) - - it('should ignore zero weight signers', () => { - const zeroWeightSigner: SignerLeaf = { ...sampleSignerLeaf, weight: 0n } - const result = getSigners(zeroWeightSigner) - - expect(result.signers).toEqual([]) - expect(result.isComplete).toBe(true) - }) - }) - - describe('findSignerLeaf', () => { - it('should find signer in simple topology', () => { - const result = findSignerLeaf(sampleSignerLeaf, testAddress1) - expect(result).toEqual(sampleSignerLeaf) - }) - - it('should find signer in node topology', () => { - const result = findSignerLeaf(sampleNode, testAddress1) - expect(result).toEqual(sampleSignerLeaf) - }) - - it('should find sapient signer in node topology', () => { - const result = findSignerLeaf(sampleNode, testAddress2) - expect(result).toEqual(sampleSapientSignerLeaf) - }) - - it('should find signer in nested topology', () => { - const result = findSignerLeaf(sampleConfigWithNestedLeaf, testAddress1) - expect(result).toEqual(sampleSignerLeaf) - }) - - it('should return undefined for non-existent signer', () => { - const result = findSignerLeaf(sampleSignerLeaf, testAddress2) - expect(result).toBeUndefined() - }) - - it('should work with config input', () => { - const result = findSignerLeaf(sampleConfig, testAddress1) - expect(result).toEqual(sampleSignerLeaf) - }) - }) - - describe('replaceAddress', () => { - it('should replace signer leaf addresses', () => { - const signerLeaf: SignerLeaf = { ...sampleSignerLeaf } - - const result = replaceAddress(signerLeaf, testAddress1, replacementAddress) - - expect(result).toEqual({ ...sampleSignerLeaf, address: replacementAddress }) - expect(result).not.toBe(signerLeaf) - expect(signerLeaf.address).toBe(testAddress1) - }) - - it('should replace sapient signer leaf addresses', () => { - const sapientLeaf: SapientSignerLeaf = { ...sampleSapientSignerLeaf } - - const result = replaceAddress(sapientLeaf, testAddress2, replacementAddress) - - expect(result).toEqual({ ...sampleSapientSignerLeaf, address: replacementAddress }) - expect(result).not.toBe(sapientLeaf) - expect(sapientLeaf.address).toBe(testAddress2) - }) - - it('should recurse through nodes and nested leaves', () => { - const nestedSapient: SapientSignerLeaf = { ...sampleSapientSignerLeaf, address: testAddress1 } - const nestedLeaf: NestedLeaf = { - type: 'nested', - tree: [nestedSapient, sampleSubdigestLeaf], - weight: 5n, - threshold: 1n, - } - const topology: Topology = [{ ...sampleSignerLeaf }, nestedLeaf] - - const result = replaceAddress(topology, testAddress1, replacementAddress) - - expect(result).toEqual([ - { ...sampleSignerLeaf, address: replacementAddress }, - { - ...nestedLeaf, - tree: [{ ...nestedSapient, address: replacementAddress }, sampleSubdigestLeaf], - }, - ]) - expect(nestedSapient.address).toBe(testAddress1) - expect((nestedLeaf.tree as Node)[1]).toBe(sampleSubdigestLeaf) - }) - - it('should return the original topology when no address matches', () => { - const sapientLeaf: SapientSignerLeaf = { ...sampleSapientSignerLeaf } - - const result = replaceAddress(sapientLeaf, replacementAddress, testAddress1) - - expect(result).toBe(sapientLeaf) - }) - - it('should leave non-signer leaves unchanged', () => { - expect(replaceAddress(sampleSubdigestLeaf, testAddress1, replacementAddress)).toBe(sampleSubdigestLeaf) - expect(replaceAddress(sampleAnyAddressSubdigestLeaf, testAddress1, replacementAddress)).toBe( - sampleAnyAddressSubdigestLeaf, - ) - expect(replaceAddress(sampleNodeLeaf, testAddress1, replacementAddress)).toBe(sampleNodeLeaf) - }) - }) - - describe('getWeight', () => { - it('should return correct weight for signer leaf with canSign true', () => { - const result = getWeight(sampleSignerLeaf, () => true) - expect(result.weight).toBe(0n) // Not signed - expect(result.maxWeight).toBe(1n) - }) - - it('should return zero weight when canSign false', () => { - const result = getWeight(sampleSignerLeaf, () => false) - expect(result.weight).toBe(0n) - expect(result.maxWeight).toBe(0n) - }) - - it('should handle node topology', () => { - const result = getWeight(sampleNode, () => true) - expect(result.weight).toBe(0n) // No signed signers - expect(result.maxWeight).toBe(3n) // 1 + 2 - }) - - it('should handle nested topology', () => { - const result = getWeight(sampleNestedLeaf, () => true) - expect(result.weight).toBe(0n) // Threshold not met - expect(result.maxWeight).toBe(3n) // Weight of nested leaf - }) - - it('should handle subdigest leaf', () => { - const result = getWeight(sampleSubdigestLeaf, () => true) - expect(result.weight).toBe(0n) - expect(result.maxWeight).toBe(0n) - }) - - it('should handle node leaf', () => { - const result = getWeight(sampleNodeLeaf, () => true) - expect(result.weight).toBe(0n) - expect(result.maxWeight).toBe(0n) - }) - }) - - describe('hashConfiguration', () => { - it('should hash signer leaf correctly', () => { - const hash = hashConfiguration(sampleSignerLeaf) - - // Should be deterministic - const hash2 = hashConfiguration(sampleSignerLeaf) - expect(Bytes.isEqual(hash, hash2)).toBe(true) - expect(hash.length).toBe(32) - }) - - it('should hash sapient signer leaf correctly', () => { - const hash = hashConfiguration(sampleSapientSignerLeaf) - expect(hash.length).toBe(32) - }) - - it('should hash subdigest leaf correctly', () => { - const hash = hashConfiguration(sampleSubdigestLeaf) - expect(hash.length).toBe(32) - }) - - it('should hash any-address-subdigest leaf correctly', () => { - const hash = hashConfiguration(sampleAnyAddressSubdigestLeaf) - expect(hash.length).toBe(32) - }) - - it('should hash node leaf correctly', () => { - const hash = hashConfiguration(sampleNodeLeaf) - expect(Bytes.isEqual(hash, Bytes.fromHex(sampleNodeLeaf))).toBe(true) - }) - - it('should hash nested leaf correctly', () => { - const hash = hashConfiguration(sampleNestedLeaf) - expect(hash.length).toBe(32) - }) - - it('should hash node correctly', () => { - const hash = hashConfiguration(sampleNode) - expect(hash.length).toBe(32) - }) - - it('should hash config correctly', () => { - const hash = hashConfiguration(sampleConfig) - expect(hash.length).toBe(32) - }) - - it('should produce different hashes for different configs', () => { - const config2: Config = { ...sampleConfig, threshold: 3n } - const hash1 = hashConfiguration(sampleConfig) - const hash2 = hashConfiguration(config2) - expect(Bytes.isEqual(hash1, hash2)).toBe(false) - }) - - it('should throw for invalid topology', () => { - expect(() => hashConfiguration({} as any)).toThrow('Invalid topology') - }) - }) - - describe('flatLeavesToTopology', () => { - it('should handle single leaf', () => { - const result = flatLeavesToTopology([sampleSignerLeaf]) - expect(result).toBe(sampleSignerLeaf) - }) - - it('should handle two leaves', () => { - const result = flatLeavesToTopology([sampleSignerLeaf, sampleSapientSignerLeaf]) - expect(result).toEqual([sampleSignerLeaf, sampleSapientSignerLeaf]) - }) - - it('should handle multiple leaves', () => { - const leaves = [sampleSignerLeaf, sampleSapientSignerLeaf, sampleSubdigestLeaf, sampleNodeLeaf] - const result = flatLeavesToTopology(leaves) - expect(isNode(result)).toBe(true) - }) - - it('should throw for empty array', () => { - expect(() => flatLeavesToTopology([])).toThrow('Cannot create topology from empty leaves') - }) - }) - - describe('JSON serialization', () => { - it('should serialize config to JSON', () => { - const json = configToJson(sampleConfig) - expect(typeof json).toBe('string') - expect(() => JSON.parse(json)).not.toThrow() - }) - - it('should deserialize config from JSON', () => { - const json = configToJson(sampleConfig) - const config = configFromJson(json) - - expect(config.threshold).toBe(sampleConfig.threshold) - expect(config.checkpoint).toBe(sampleConfig.checkpoint) - expect(config.checkpointer).toBe(sampleConfig.checkpointer) - }) - - it('should handle round-trip serialization', () => { - const json = configToJson(sampleConfig) - const config = configFromJson(json) - const json2 = configToJson(config) - - expect(json).toBe(json2) - }) - - it('should handle complex topologies', () => { - const complexConfig: Config = { - threshold: 2n, - checkpoint: 0n, - topology: { - type: 'nested', - weight: 2n, - threshold: 1n, - tree: [sampleSignerLeaf, sampleSapientSignerLeaf], - }, - } - - const json = configToJson(complexConfig) - const parsed = configFromJson(json) - - expect(parsed.threshold).toBe(complexConfig.threshold) - expect(isNestedLeaf(parsed.topology)).toBe(true) - }) - }) - - describe('mergeTopology', () => { - it('should merge identical leaves', () => { - const result = mergeTopology(sampleSignerLeaf, sampleSignerLeaf) - expect(result).toEqual(sampleSignerLeaf) - }) - - it('should merge nodes recursively', () => { - const result = mergeTopology(sampleNode, sampleNode) - expect(result).toEqual(sampleNode) - }) - - it('should merge node with matching node leaf', () => { - const nodeHash = Bytes.toHex(hashConfiguration(sampleNode)) - const result = mergeTopology(sampleNode, nodeHash) - expect(result).toEqual(sampleNode) - }) - - it('should throw for mismatched node hash', () => { - const wrongHash = '0x0000000000000000000000000000000000000000000000000000000000000000' - expect(() => mergeTopology(sampleNode, wrongHash)).toThrow('Topology mismatch') - }) - - it('should throw for incompatible leaf types', () => { - expect(() => mergeTopology(sampleSignerLeaf, sampleSapientSignerLeaf)).toThrow('Topology mismatch') - }) - - it('should merge matching subdigest leaves', () => { - const result = mergeTopology(sampleSubdigestLeaf, sampleSubdigestLeaf) - expect(result).toEqual(sampleSubdigestLeaf) - }) - - it('should throw for different subdigest values', () => { - const differentSubdigest: SubdigestLeaf = { - type: 'subdigest', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - } - expect(() => mergeTopology(sampleSubdigestLeaf, differentSubdigest)).toThrow('Topology mismatch') - }) - }) - - describe('hasInvalidValues', () => { - it('should return false for valid config', () => { - expect(hasInvalidValues(sampleConfig)).toBe(false) - }) - - it('should return true for threshold too large', () => { - const invalidConfig: Config = { ...sampleConfig, threshold: 65536n } - expect(hasInvalidValues(invalidConfig)).toBe(true) - }) - - it('should return true for checkpoint too large', () => { - const invalidConfig: Config = { ...sampleConfig, checkpoint: 72057594037927936n } - expect(hasInvalidValues(invalidConfig)).toBe(true) - }) - - it('should return true for weight too large', () => { - const invalidLeaf: SignerLeaf = { ...sampleSignerLeaf, weight: 256n } - expect(hasInvalidValues(invalidLeaf)).toBe(true) - }) - - it('should return false for valid topology', () => { - expect(hasInvalidValues(sampleSignerLeaf)).toBe(false) - expect(hasInvalidValues(sampleNode)).toBe(false) - }) - - it('should check nested topology recursively', () => { - const invalidNested: NestedLeaf = { - type: 'nested', - tree: { ...sampleSignerLeaf, weight: 256n }, - weight: 1n, - threshold: 1n, - } - expect(hasInvalidValues(invalidNested)).toBe(true) - }) - }) - - describe('maximumDepth', () => { - it('should return 0 for leaves', () => { - expect(maximumDepth(sampleSignerLeaf)).toBe(0) - expect(maximumDepth(sampleSapientSignerLeaf)).toBe(0) - expect(maximumDepth(sampleSubdigestLeaf)).toBe(0) - expect(maximumDepth(sampleNodeLeaf)).toBe(0) - }) - - it('should return 1 for simple node', () => { - expect(maximumDepth(sampleNode)).toBe(1) - }) - - it('should return correct depth for nested topology', () => { - expect(maximumDepth(sampleNestedLeaf)).toBe(1) - }) - - it('should handle deep nesting', () => { - const deepNested: NestedLeaf = { - type: 'nested', - tree: sampleNestedLeaf, - weight: 1n, - threshold: 1n, - } - expect(maximumDepth(deepNested)).toBe(2) - }) - - it('should handle asymmetric trees', () => { - const asymmetric: Node = [sampleSignerLeaf, [sampleSapientSignerLeaf, sampleSubdigestLeaf]] - expect(maximumDepth(asymmetric)).toBe(2) - }) - }) - - describe('evaluateConfigurationSafety', () => { - it('should not throw for safe config', () => { - expect(() => evaluateConfigurationSafety(sampleConfig)).not.toThrow() - }) - - it('should throw for zero threshold', () => { - const unsafeConfig: Config = { ...sampleConfig, threshold: 0n } - expect(() => evaluateConfigurationSafety(unsafeConfig)).toThrow('unsafe-threshold-0') - }) - - it('should throw for invalid values', () => { - const unsafeConfig: Config = { ...sampleConfig, threshold: 65536n } - expect(() => evaluateConfigurationSafety(unsafeConfig)).toThrow('unsafe-invalid-values') - }) - - it('should throw for excessive depth', () => { - // Create a deeply nested config - let deepTopology: Topology = sampleSignerLeaf - for (let i = 0; i < 35; i++) { - deepTopology = { - type: 'nested', - tree: deepTopology, - weight: 1n, - threshold: 1n, - } - } - const unsafeConfig: Config = { ...sampleConfig, topology: deepTopology } - expect(() => evaluateConfigurationSafety(unsafeConfig)).toThrow('unsafe-depth') - }) - - it('should throw for unreachable threshold', () => { - const unsafeConfig: Config = { ...sampleConfig, threshold: 100n } // Higher than max weight - expect(() => evaluateConfigurationSafety(unsafeConfig)).toThrow('unsafe-threshold') - }) - }) - - describe('normalizeSignerSignature', () => { - it('should handle direct value', () => { - const value = 'test-signature' - const result = normalizeSignerSignature(value) - expect(result.signature).toBeInstanceOf(Promise) - }) - - it('should handle Promise value', () => { - const promise = Promise.resolve('test-signature') - const result = normalizeSignerSignature(promise) - expect(result.signature).toBe(promise) - }) - - it('should handle signature object', () => { - const sigObj = { - signature: Promise.resolve('test-signature'), - onSignerSignature: () => {}, - onCancel: () => {}, - } - const result = normalizeSignerSignature(sigObj) - expect(result).toBe(sigObj) - }) - }) - - describe('Edge cases and error conditions', () => { - it('should handle empty node arrays correctly', () => { - expect(isNode([])).toBe(false) - expect(isNode([sampleSignerLeaf, sampleSignerLeaf, sampleSignerLeaf])).toBe(false) - }) - - it('should handle malformed JSON in configFromJson', () => { - expect(() => configFromJson('invalid json')).toThrow() - }) - - it('should handle malformed topology in decodeTopology', () => { - const invalidJson = JSON.stringify({ - threshold: '1', - checkpoint: '0', - topology: { type: 'invalid-type' }, - }) - expect(() => configFromJson(invalidJson)).toThrow('Invalid type in topology JSON') - }) - - it('should handle invalid node structure in JSON', () => { - const invalidJson = JSON.stringify({ - threshold: '1', - checkpoint: '0', - topology: [{ type: 'signer', address: testAddress1, weight: '1' }], // Only one element - converted to string - }) - expect(() => configFromJson(invalidJson)).toThrow('Invalid node structure in JSON') - }) - - it('should handle very large numbers in BigInt conversion', () => { - const largeNumberConfig = { - threshold: '999999999999999999999999999999', - checkpoint: '999999999999999999999999999999', - topology: { - type: 'signer', - address: testAddress1, - weight: '999999999999999999999999999999', - }, - } - const json = JSON.stringify(largeNumberConfig) - const config = configFromJson(json) - expect(typeof config.threshold).toBe('bigint') - }) - }) - - describe('mergeLeaf function (internal)', () => { - it('should merge identical node leaves', () => { - const nodeLeaf1 = '0x1111111111111111111111111111111111111111111111111111111111111111' - const nodeLeaf2 = '0x1111111111111111111111111111111111111111111111111111111111111111' - - // Use mergeTopology to indirectly test mergeLeaf - const result = mergeTopology(nodeLeaf1, nodeLeaf2) - expect(result).toBe(nodeLeaf1) - }) - - it('should throw for different node leaves', () => { - const nodeLeaf1 = '0x1111111111111111111111111111111111111111111111111111111111111111' - const nodeLeaf2 = '0x2222222222222222222222222222222222222222222222222222222222222222' - - expect(() => mergeTopology(nodeLeaf1, nodeLeaf2)).toThrow('Topology mismatch: different node leaves') - }) - - it('should merge node leaf with matching topology hash', () => { - const topology = sampleSignerLeaf - const topologyHash = Bytes.toHex(hashConfiguration(topology)) - - const result = mergeTopology(topologyHash, topology) - expect(result).toEqual(topology) - }) - - it('should merge topology with matching node leaf hash', () => { - const topology = sampleSignerLeaf - const topologyHash = Bytes.toHex(hashConfiguration(topology)) - - const result = mergeTopology(topology, topologyHash) - expect(result).toEqual(topology) - }) - - it('should throw when node leaf hash does not match topology', () => { - const topology = sampleSignerLeaf - const wrongHash = '0x0000000000000000000000000000000000000000000000000000000000000000' - - expect(() => mergeTopology(wrongHash, topology)).toThrow('Topology mismatch: node leaf hash does not match') - expect(() => mergeTopology(topology, wrongHash)).toThrow('Topology mismatch: node leaf hash does not match') - }) - - it('should merge identical signer leaves', () => { - const signer1: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - } - const signer2: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - } - - const result = mergeTopology(signer1, signer2) - expect(result).toEqual(signer1) - }) - - it('should throw for signer leaves with different addresses', () => { - const signer1: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - } - const signer2: SignerLeaf = { - type: 'signer', - address: testAddress2, - weight: 1n, - } - - expect(() => mergeTopology(signer1, signer2)).toThrow('Topology mismatch: signer fields differ') - }) - - it('should throw for signer leaves with different weights', () => { - const signer1: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - } - const signer2: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 2n, - } - - expect(() => mergeTopology(signer1, signer2)).toThrow('Topology mismatch: signer fields differ') - }) - - it('should throw for signer leaves with different signature states', () => { - const signer1: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - signed: true, - } - const signer2: SignerLeaf = { - type: 'signer', - address: testAddress1, - weight: 1n, - signed: false, - } - - expect(() => mergeTopology(signer1, signer2)).toThrow('Topology mismatch: signer signature fields differ') - }) - - it('should merge identical sapient signer leaves', () => { - const result = mergeTopology(sampleSapientSignerLeaf, sampleSapientSignerLeaf) - expect(result).toEqual(sampleSapientSignerLeaf) - }) - - it('should throw for sapient signers with different addresses', () => { - const sapient1: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress1, - weight: 1n, - imageHash: testImageHash, - } - const sapient2: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress2, - weight: 1n, - imageHash: testImageHash, - } - - expect(() => mergeTopology(sapient1, sapient2)).toThrow('Topology mismatch: sapient signer fields differ') - }) - - it('should throw for sapient signers with different image hashes', () => { - const sapient1: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress1, - weight: 1n, - imageHash: testImageHash, - } - const sapient2: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress1, - weight: 1n, - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - } - - expect(() => mergeTopology(sapient1, sapient2)).toThrow('Topology mismatch: sapient signer fields differ') - }) - - it('should throw for sapient signers with different signature states', () => { - const sapient1: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress1, - weight: 1n, - imageHash: testImageHash, - signed: true, - } - const sapient2: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress1, - weight: 1n, - imageHash: testImageHash, - signed: false, - } - - expect(() => mergeTopology(sapient1, sapient2)).toThrow('Topology mismatch: sapient signature fields differ') - }) - - it('should merge identical any-address-subdigest leaves', () => { - const result = mergeTopology(sampleAnyAddressSubdigestLeaf, sampleAnyAddressSubdigestLeaf) - expect(result).toEqual(sampleAnyAddressSubdigestLeaf) - }) - - it('should throw for any-address-subdigest leaves with different digests', () => { - const subdigest1: AnyAddressSubdigestLeaf = { - type: 'any-address-subdigest', - digest: testDigest, - } - const subdigest2: AnyAddressSubdigestLeaf = { - type: 'any-address-subdigest', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - } - - expect(() => mergeTopology(subdigest1, subdigest2)).toThrow( - 'Topology mismatch: any-address-subdigest fields differ', - ) - }) - - it('should merge nested leaves recursively', () => { - const nested1: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 2n, - threshold: 1n, - } - const nested2: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 2n, - threshold: 1n, - } - - const result = mergeTopology(nested1, nested2) - expect(result).toEqual(nested1) - }) - - it('should throw for nested leaves with different weights', () => { - const nested1: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 1n, - threshold: 1n, - } - const nested2: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 2n, - threshold: 1n, - } - - expect(() => mergeTopology(nested1, nested2)).toThrow('Topology mismatch: nested leaf fields differ') - }) - - it('should throw for nested leaves with different thresholds', () => { - const nested1: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 1n, - threshold: 1n, - } - const nested2: NestedLeaf = { - type: 'nested', - tree: sampleSignerLeaf, - weight: 1n, - threshold: 2n, - } - - expect(() => mergeTopology(nested1, nested2)).toThrow('Topology mismatch: nested leaf fields differ') - }) - - it('should throw for completely incompatible leaf types', () => { - expect(() => mergeTopology(sampleSignerLeaf, sampleSubdigestLeaf)).toThrow( - 'Topology mismatch: incompatible leaf types', - ) - }) - }) -}) diff --git a/packages/wallet/primitives/test/erc-6492.test.ts b/packages/wallet/primitives/test/erc-6492.test.ts deleted file mode 100644 index 7398f58db9..0000000000 --- a/packages/wallet/primitives/test/erc-6492.test.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { Address, Bytes, Hex, Provider } from 'ox' -import { SignatureErc6492 } from 'ox/erc6492' - -import { deploy, wrap, decode, isValid } from '../src/erc-6492.js' -import { Context } from '../src/context.js' - -describe('ERC-6492', () => { - const mockContext: Context = { - factory: '0x1234567890123456789012345678901234567890' as Address.Address, - stage1: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' as Address.Address, // Fixed: 40 hex chars - stage2: '0x9876543210987654321098765432109876543210' as Address.Address, - creationCode: '0x608060405234801561001057600080fd5b50', - } - - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' as Address.Address - const testMessageHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const testSignature = - '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789001' - const testDeployHash = '0x9999999999999999999999999999999999999999999999999999999999999999' // 32 bytes - - type DeployData = Parameters[1] - - describe('deploy', () => { - it('should create deploy call data with hex string', () => { - const result = deploy(testDeployHash, mockContext) - - expect(result.to).toBe(mockContext.factory) - expect(typeof result.data).toBe('string') - expect(result.data.startsWith('0x')).toBe(true) - - // Should contain the encoded function call with stage1 and deployHash - expect(result.data).toContain(mockContext.stage1.slice(2)) // Remove 0x prefix for contains check - }) - - it('should create deploy call data with bytes', () => { - const deployHashBytes = Hex.toBytes(testDeployHash) - const result = deploy(deployHashBytes, mockContext) - - expect(result.to).toBe(mockContext.factory) - expect(result.data).toBeInstanceOf(Uint8Array) - - // Convert to hex to check contents - const dataHex = Bytes.toHex(result.data) - expect(dataHex).toContain(mockContext.stage1.slice(2)) - }) - - it('should return same type as input for deploy hash', () => { - // Test with hex string - const hexResult = deploy(testDeployHash, mockContext) - expect(typeof hexResult.data).toBe('string') - - // Test with bytes - const bytesResult = deploy(Hex.toBytes(testDeployHash), mockContext) - expect(bytesResult.data).toBeInstanceOf(Uint8Array) - }) - - it('should work with different contexts', () => { - const differentContext: Context = { - factory: '0x9999999999999999999999999999999999999999' as Address.Address, - stage1: '0x1111111111111111111111111111111111111111' as Address.Address, - stage2: '0x2222222222222222222222222222222222222222' as Address.Address, - creationCode: '0x6080604052', - } - - const result = deploy(testDeployHash, differentContext) - expect(result.to).toBe(differentContext.factory) - expect(result.data).toContain(differentContext.stage1.slice(2)) - }) - }) - - describe('wrap', () => { - const deployData: DeployData = { - to: testAddress, - data: '0x1234567890abcdef', - } - - it('should wrap signature with hex string', () => { - const result = wrap(testSignature, deployData) - - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - - // Should end with the magic bytes - expect(result.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) - - // Should contain the original signature data somewhere - expect(result.length).toBeGreaterThan(testSignature.length) - }) - - it('should wrap signature with bytes', () => { - const signatureBytes = Hex.toBytes(testSignature) - const result = wrap(signatureBytes, deployData) - - expect(result).toBeInstanceOf(Uint8Array) - - // Convert to hex to check magic bytes - const resultHex = Bytes.toHex(result) - expect(resultHex.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) - }) - - it('should return same type as input signature', () => { - // Test with hex string - const hexResult = wrap(testSignature, deployData) - expect(typeof hexResult).toBe('string') - - // Test with bytes - const bytesResult = wrap(Hex.toBytes(testSignature), deployData) - expect(bytesResult).toBeInstanceOf(Uint8Array) - }) - - it('should handle different deploy data formats', () => { - // Test with hex data - const hexDeployData: DeployData = { - to: testAddress, - data: '0xdeadbeef', - } - const hexResult = wrap(testSignature, hexDeployData) - expect(typeof hexResult).toBe('string') - - // Test with bytes data - const bytesDeployData: DeployData = { - to: testAddress, - data: Hex.toBytes('0xdeadbeef'), - } - const bytesResult = wrap(testSignature, bytesDeployData) - expect(typeof bytesResult).toBe('string') - }) - - it('should encode all parameters correctly', () => { - const result = wrap(testSignature, deployData) - - // The wrapped signature should contain encoded: address, bytes (data), bytes (signature) - expect(result.length).toBeGreaterThan(testSignature.length + deployData.data.length) - expect(result).toContain(testAddress.slice(2)) // Address without 0x - expect(result.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) - }) - }) - - describe('decode', () => { - it('should decode wrapped hex signature correctly', () => { - const deployData: DeployData = { - to: testAddress, - data: '0x1234567890abcdef', - } - - const wrapped = wrap(testSignature, deployData) - const result = decode(wrapped) - - expect(result.signature).toBe(testSignature) - expect(result.erc6492).toBeDefined() - expect(result.erc6492!.to).toBe(testAddress) - expect(result.erc6492!.data).toBe(deployData.data) - }) - - it('should decode wrapped bytes signature correctly', () => { - const deployData = { - to: testAddress, - data: Hex.toBytes('0x1234567890abcdef'), - } - - const signatureBytes = Hex.toBytes(testSignature) - const wrapped = wrap(signatureBytes, deployData) - const result = decode(wrapped) - - expect(Bytes.isEqual(result.signature, signatureBytes)).toBe(true) - expect(result.erc6492).toBeDefined() - expect(result.erc6492!.to).toBe(testAddress) - expect(Bytes.isEqual(result.erc6492!.data, deployData.data)).toBe(true) - }) - - it('should return original signature for non-wrapped hex signature', () => { - const result = decode(testSignature) - - expect(result.signature).toBe(testSignature) - expect(result.erc6492).toBeUndefined() - }) - - it('should return original signature for non-wrapped bytes signature', () => { - const signatureBytes = Hex.toBytes(testSignature) - const result = decode(signatureBytes) - - expect(Bytes.isEqual(result.signature, signatureBytes)).toBe(true) - expect(result.erc6492).toBeUndefined() - }) - - it('should handle round-trip wrap/decode correctly', () => { - const deployData: DeployData = { - to: testAddress, - data: '0xdeadbeefcafe', - } - - // Test hex string round-trip - const wrappedHex = wrap(testSignature, deployData) - const decodedHex = decode(wrappedHex) - - expect(decodedHex.signature).toBe(testSignature) - expect(decodedHex.erc6492!.to).toBe(testAddress) - expect(decodedHex.erc6492!.data).toBe(deployData.data) - - // Test bytes round-trip - const signatureBytes = Hex.toBytes(testSignature) - const wrappedBytes = wrap(signatureBytes, deployData) - const decodedBytes = decode(wrappedBytes) - - expect(Bytes.isEqual(decodedBytes.signature, signatureBytes)).toBe(true) - expect(decodedBytes.erc6492!.to).toBe(testAddress) - }) - - it('should handle malformed wrapped signature gracefully', () => { - // Create a signature that ends with magic bytes but has invalid encoding - const malformedSig = ('0x1234' + SignatureErc6492.magicBytes.slice(2)) as Hex.Hex - const result = decode(malformedSig) - - // Should return original signature when decoding fails - expect(result.signature).toBe(malformedSig) - expect(result.erc6492).toBeUndefined() - }) - - it('should preserve data types in decode results', () => { - const deployData: DeployData = { - to: testAddress, - data: '0x1234567890abcdef', - } - - // Test with hex input - const wrappedHex = wrap(testSignature, deployData) - const resultHex = decode(wrappedHex) - expect(typeof resultHex.signature).toBe('string') - expect(typeof resultHex.erc6492!.data).toBe('string') - - // Test with bytes input - const signatureBytes = Hex.toBytes(testSignature) - const wrappedBytes = wrap(signatureBytes, deployData) - const resultBytes = decode(wrappedBytes) - expect(resultBytes.signature).toBeInstanceOf(Uint8Array) - expect(resultBytes.erc6492!.data).toBeInstanceOf(Uint8Array) - }) - - it('should handle empty deploy data', () => { - const deployData: DeployData = { - to: testAddress, - data: '0x', - } - - const wrapped = wrap(testSignature, deployData) - const result = decode(wrapped) - - expect(result.signature).toBe(testSignature) - expect(result.erc6492!.data).toBe('0x') - }) - }) - - describe('isValid', () => { - const mockProvider = { - request: vi.fn(), - } as unknown as Provider.Provider - - it('should call provider with correct parameters', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000001') - - const result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - - expect(mockRequest).toHaveBeenCalledWith({ - method: 'eth_call', - params: [ - { - data: expect.stringMatching(/^0x[a-fA-F0-9]+$/), - }, - 'latest', - ], - }) - - expect(result).toBe(true) - }) - - it('should return true when provider returns 1', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000001') - - const result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - expect(result).toBe(true) - }) - - it('should return false when provider returns 0', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000000') - - const result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - expect(result).toBe(false) - }) - - it('should return false when provider returns other values', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000002') - - const result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - expect(result).toBe(false) - }) - - it('should handle bytes input parameters', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000001') - - const messageHashBytes = Hex.toBytes(testMessageHash) - const signatureBytes = Hex.toBytes(testSignature) - - const result = await isValid(testAddress, messageHashBytes, signatureBytes, mockProvider) - - expect(mockRequest).toHaveBeenCalled() - expect(result).toBe(true) - }) - - it('should include validation contract deployment code in call data', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000001') - - await isValid(testAddress, testMessageHash, testSignature, mockProvider) - - const callArgs = mockRequest.mock.calls[0]![0] - const callData = (callArgs as any).params[0].data - - // Call data should start with the ERC-6492 validation contract deployment code - expect(callData.startsWith('0x608060405234801561001057600080fd5b50')).toBe(true) - expect(callData.length).toBeGreaterThan(1000) // Should be quite long due to contract code - }) - - it('should handle provider request failure', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockRejectedValue(new Error('Network error')) - - await expect(isValid(testAddress, testMessageHash, testSignature, mockProvider)).rejects.toThrow('Network error') - }) - - it('should handle different hex formats in provider response', async () => { - const mockRequest = vi.mocked(mockProvider.request) - - // Test with short hex (should be 1) - mockRequest.mockResolvedValue('0x1') - let result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - expect(result).toBe(true) - - // Test with no 0x prefix (should still parse as 0) - mockRequest.mockResolvedValue('0') - result = await isValid(testAddress, testMessageHash, testSignature, mockProvider) - expect(result).toBe(false) - }) - - it('should encode parameters correctly in validation call data', async () => { - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x1') - - await isValid(testAddress, testMessageHash, testSignature, mockProvider) - - const callArgs = mockRequest.mock.calls[0]![0] - const callData = (callArgs as any).params[0].data - - // The call data should contain the encoded address, message hash, and signature - // Address is encoded as 32-byte value, so testAddress.slice(2) should appear - expect(callData).toContain(testAddress.slice(2).toLowerCase()) - // Message hash should appear in the call data - expect(callData).toContain(testMessageHash.slice(2).toLowerCase()) - }) - }) - - describe('Integration tests', () => { - it('should work with wrapped signatures in validation', async () => { - const mockProvider = { - request: vi.fn(), - } as unknown as Provider.Provider - const mockRequest = vi.mocked(mockProvider.request) - mockRequest.mockResolvedValue('0x1') - - const deployData: DeployData = { - to: testAddress, - data: '0x1234567890abcdef', - } - - const wrappedSignature = wrap(testSignature, deployData) - const result = await isValid(testAddress, testMessageHash, wrappedSignature, mockProvider) - - expect(result).toBe(true) - expect(mockRequest).toHaveBeenCalled() - }) - - it('should handle complete ERC-6492 workflow', () => { - // 1. Create deploy call data - const deployCall = deploy(testDeployHash, mockContext) - expect(deployCall.to).toBe(mockContext.factory) - - // 2. Wrap signature with deploy data - const wrappedSig = wrap(testSignature, deployCall) - expect(wrappedSig.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) - - // 3. Decode wrapped signature - const decoded = decode(wrappedSig) - expect(decoded.signature).toBe(testSignature) - expect(decoded.erc6492).toBeDefined() - expect(decoded.erc6492!.to).toBe(mockContext.factory) - }) - - it('should preserve type consistency throughout workflow', () => { - const deployCallBytes = deploy(Hex.toBytes(testDeployHash), mockContext) - expect(deployCallBytes.data).toBeInstanceOf(Uint8Array) - - const signatureBytes = Hex.toBytes(testSignature) - const wrappedBytes = wrap(signatureBytes, deployCallBytes) - expect(wrappedBytes).toBeInstanceOf(Uint8Array) - - const decodedBytes = decode(wrappedBytes) - expect(decodedBytes.signature).toBeInstanceOf(Uint8Array) - expect(decodedBytes.erc6492!.data).toBeInstanceOf(Uint8Array) - }) - - it('should handle edge case with minimal data', () => { - const minimalContext: Context = { - factory: '0x0000000000000000000000000000000000000000' as Address.Address, - stage1: '0x0000000000000000000000000000000000000000' as Address.Address, - stage2: '0x0000000000000000000000000000000000000000' as Address.Address, - creationCode: '0x', - } - - const deployCall = deploy('0x0000000000000000000000000000000000000000000000000000000000000000', minimalContext) - expect(deployCall.to).toBe(minimalContext.factory) - - const wrapped = wrap('0x00', deployCall) - const decoded = decode(wrapped) - - expect(decoded.signature).toBe('0x00') - expect(decoded.erc6492).toBeDefined() - }) - }) - - describe('Error handling and edge cases', () => { - it('should handle very long signatures', () => { - const longSignature = ('0x' + '00'.repeat(1000)) as Hex.Hex - const deployData: DeployData = { to: testAddress, data: '0x1234' } - - const wrapped = wrap(longSignature, deployData) - const decoded = decode(wrapped) - - expect(decoded.signature).toBe(longSignature) - expect(decoded.erc6492).toBeDefined() - }) - - it('should handle empty signatures', () => { - const emptySignature = '0x' - const deployData: DeployData = { to: testAddress, data: '0x' } - - const wrapped = wrap(emptySignature, deployData) - const decoded = decode(wrapped) - - expect(decoded.signature).toBe(emptySignature) - expect(decoded.erc6492).toBeDefined() - }) - - it('should handle signatures that accidentally contain magic bytes', () => { - // Create a signature that contains the magic bytes but isn't wrapped - const magicInSignature = (testSignature + SignatureErc6492.magicBytes.slice(2) + '1234') as Hex.Hex - const result = decode(magicInSignature) - - // Should try to decode, but if it fails, should return original - expect(result.signature).toBeDefined() - }) - - it('should handle different address formats', () => { - const checksumAddress = '0x742d35Cc6635C0532925a3b8D563A6b35B7f05f1' as Address.Address - const lowercaseAddress = checksumAddress.toLowerCase() - - const deployData1: DeployData = { to: checksumAddress, data: '0x1234' } - const deployData2: DeployData = { to: lowercaseAddress as Address.Address, data: '0x1234' } - - const wrapped1 = wrap(testSignature, deployData1) - const wrapped2 = wrap(testSignature, deployData2) - - const decoded1 = decode(wrapped1) - const decoded2 = decode(wrapped2) - - // Addresses may be normalized to lowercase in decode - expect(decoded1.erc6492!.to.toLowerCase()).toBe(checksumAddress.toLowerCase()) - expect(decoded2.erc6492!.to).toBe(lowercaseAddress) - }) - }) -}) diff --git a/packages/wallet/primitives/test/generic-tree.test.ts b/packages/wallet/primitives/test/generic-tree.test.ts deleted file mode 100644 index 3e8b24091e..0000000000 --- a/packages/wallet/primitives/test/generic-tree.test.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Bytes, Hash, Hex } from 'ox' - -import { Leaf, Node, Branch, Tree, isBranch, isLeaf, isTree, isNode, hash } from '../src/generic-tree.js' - -describe('Generic Tree', () => { - // Test data - const sampleLeaf1: Leaf = { - type: 'leaf', - value: Bytes.fromString('test-leaf-1'), - } - - const sampleLeaf2: Leaf = { - type: 'leaf', - value: Bytes.fromString('test-leaf-2'), - } - - const sampleLeaf3: Leaf = { - type: 'leaf', - value: Bytes.fromHex('0xdeadbeef'), - } - - const sampleNode: Node = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const sampleNode2: Node = ('0x' + 'ab'.repeat(32)) as Hex.Hex // Exactly 32 bytes - - const sampleBranch: Branch = [sampleLeaf1, sampleLeaf2] - const complexBranch: Branch = [sampleLeaf1, sampleNode, sampleLeaf2] - const nestedBranch: Branch = [sampleBranch, sampleLeaf3] - - describe('Type Guards', () => { - describe('isLeaf', () => { - it('should return true for valid leaf objects', () => { - expect(isLeaf(sampleLeaf1)).toBe(true) - expect(isLeaf(sampleLeaf2)).toBe(true) - expect(isLeaf(sampleLeaf3)).toBe(true) - }) - - it('should return true for leaf with empty bytes', () => { - const emptyLeaf: Leaf = { - type: 'leaf', - value: new Uint8Array(0), - } - expect(isLeaf(emptyLeaf)).toBe(true) - }) - - it('should return false for non-leaf objects', () => { - expect(isLeaf(sampleNode)).toBe(false) - expect(isLeaf(sampleBranch)).toBe(false) - expect(isLeaf({ type: 'not-leaf', value: Bytes.fromString('test') })).toBe(false) - expect(isLeaf({ type: 'leaf' })).toBe(false) // Missing value - expect(isLeaf({ value: Bytes.fromString('test') })).toBe(false) // Missing type - // Note: null and undefined cause isLeaf to throw because it tries to access .type - // This is expected behavior from the source code - expect(() => isLeaf(null)).toThrow() - expect(() => isLeaf(undefined)).toThrow() - expect(isLeaf('string')).toBe(false) - expect(isLeaf(123)).toBe(false) - }) - - it('should return false for leaf with invalid value', () => { - expect(isLeaf({ type: 'leaf', value: 'not-bytes' })).toBe(false) - expect(isLeaf({ type: 'leaf', value: null })).toBe(false) - expect(isLeaf({ type: 'leaf', value: undefined })).toBe(false) - }) - }) - - describe('isNode', () => { - it('should return true for valid 32-byte hex strings', () => { - expect(isNode(sampleNode)).toBe(true) - expect(isNode(sampleNode2)).toBe(true) - - // Test with all zeros - const zeroNode = '0x' + '00'.repeat(32) - expect(isNode(zeroNode)).toBe(true) - - // Test with all Fs - const maxNode = '0x' + 'FF'.repeat(32) - expect(isNode(maxNode)).toBe(true) - }) - - it('should return false for invalid hex strings', () => { - expect(isNode('not-hex')).toBe(false) - expect(isNode('0x123')).toBe(false) // Too short - expect(isNode('0x' + '00'.repeat(31))).toBe(false) // 31 bytes - expect(isNode('0x' + '00'.repeat(33))).toBe(false) // 33 bytes - // Note: Hex.validate in ox doesn't actually validate hex characters, only format - // So we test length validation instead - expect(isNode(sampleLeaf1)).toBe(false) - expect(isNode(sampleBranch)).toBe(false) - expect(isNode(null)).toBe(false) - expect(isNode(undefined)).toBe(false) - expect(isNode(123)).toBe(false) - }) - - it('should return false for hex without 0x prefix', () => { - expect(isNode('1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')).toBe(false) - }) - }) - - describe('isBranch', () => { - it('should return true for valid branches', () => { - expect(isBranch(sampleBranch)).toBe(true) - expect(isBranch(complexBranch)).toBe(true) - expect(isBranch(nestedBranch)).toBe(true) - }) - - it('should return true for branches with more than 2 elements', () => { - const largeBranch: Branch = [sampleLeaf1, sampleLeaf2, sampleLeaf3, sampleNode] - expect(isBranch(largeBranch)).toBe(true) - }) - - it('should return false for arrays with less than 2 elements', () => { - expect(isBranch([] as any)).toBe(false) - expect(isBranch([sampleLeaf1] as any)).toBe(false) - }) - - it('should return false for non-arrays', () => { - expect(isBranch(sampleLeaf1)).toBe(false) - expect(isBranch(sampleNode)).toBe(false) - expect(isBranch('string' as any)).toBe(false) - expect(isBranch(null as any)).toBe(false) - expect(isBranch(undefined as any)).toBe(false) - expect(isBranch({} as any)).toBe(false) - }) - - it('should return false for arrays containing invalid trees', () => { - expect(isBranch([sampleLeaf1, 'invalid' as any])).toBe(false) - expect(isBranch(['invalid' as any, sampleLeaf2])).toBe(false) - // Note: null values in arrays will cause isTree -> isLeaf to throw - expect(() => isBranch([sampleLeaf1, null as any])).toThrow() - expect(() => isBranch([undefined as any, sampleLeaf2])).toThrow() - }) - - it('should validate nested branches recursively', () => { - const validNested: Branch = [[sampleLeaf1, sampleLeaf2], sampleLeaf3] - expect(isBranch(validNested)).toBe(true) - - const invalidNested = [[sampleLeaf1, 'invalid' as any], sampleLeaf3] as any - expect(isBranch(invalidNested)).toBe(false) - }) - }) - - describe('isTree', () => { - it('should return true for all valid tree types', () => { - expect(isTree(sampleLeaf1)).toBe(true) - expect(isTree(sampleLeaf2)).toBe(true) - expect(isTree(sampleNode)).toBe(true) - expect(isTree(sampleBranch)).toBe(true) - expect(isTree(complexBranch)).toBe(true) - expect(isTree(nestedBranch)).toBe(true) - }) - - it('should return false for invalid objects', () => { - expect(isTree('string')).toBe(false) - expect(isTree(123)).toBe(false) - // Note: null and undefined cause isTree -> isLeaf to throw - expect(() => isTree(null)).toThrow() - expect(() => isTree(undefined)).toThrow() - expect(isTree({})).toBe(false) - expect(isTree([])).toBe(false) // Empty array - expect(isTree([sampleLeaf1])).toBe(false) // Single element array - }) - }) - }) - - describe('hash function', () => { - describe('Leaf hashing', () => { - it('should hash leaf values correctly', () => { - const result = hash(sampleLeaf1) - - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(Hex.size(result)).toBe(32) - - // Should be deterministic - const result2 = hash(sampleLeaf1) - expect(result).toBe(result2) - }) - - it('should produce different hashes for different leaves', () => { - const hash1 = hash(sampleLeaf1) - const hash2 = hash(sampleLeaf2) - - expect(hash1).not.toBe(hash2) - }) - - it('should hash empty leaf correctly', () => { - const emptyLeaf: Leaf = { - type: 'leaf', - value: new Uint8Array(0), - } - - const result = hash(emptyLeaf) - expect(Hex.size(result)).toBe(32) - - // Empty bytes should hash to the keccak256 of empty bytes - const expectedHash = Hash.keccak256(new Uint8Array(0), { as: 'Hex' }) - expect(result).toBe(expectedHash) - }) - - it('should handle large leaf values', () => { - const largeLeaf: Leaf = { - type: 'leaf', - value: new Uint8Array(1000).fill(0xab), - } - - const result = hash(largeLeaf) - expect(Hex.size(result)).toBe(32) - }) - }) - - describe('Node hashing', () => { - it('should return node value unchanged', () => { - const result = hash(sampleNode) - expect(result).toBe(sampleNode) - }) - - it('should work with different node values', () => { - const result1 = hash(sampleNode) - const result2 = hash(sampleNode2) - - expect(result1).toBe(sampleNode) - expect(result2).toBe(sampleNode2) - expect(result1).not.toBe(result2) - }) - }) - - describe('Branch hashing', () => { - it('should hash simple branch correctly', () => { - const result = hash(sampleBranch) - - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(Hex.size(result)).toBe(32) - }) - - it('should be deterministic for same branch', () => { - const result1 = hash(sampleBranch) - const result2 = hash(sampleBranch) - - expect(result1).toBe(result2) - }) - - it('should produce different hashes for different branches', () => { - const branch1: Branch = [sampleLeaf1, sampleLeaf2] - const branch2: Branch = [sampleLeaf2, sampleLeaf1] // Swapped order - - const hash1 = hash(branch1) - const hash2 = hash(branch2) - - expect(hash1).not.toBe(hash2) - }) - - it('should handle branches with more than 2 elements', () => { - const largeBranch: Branch = [sampleLeaf1, sampleLeaf2, sampleLeaf3] - const result = hash(largeBranch) - - expect(Hex.size(result)).toBe(32) - }) - - it('should handle mixed branch types', () => { - const mixedBranch: Branch = [sampleLeaf1, sampleNode, sampleLeaf2] - const result = hash(mixedBranch) - - expect(Hex.size(result)).toBe(32) - }) - - it('should handle nested branches', () => { - const nestedBranch: Branch = [sampleBranch, sampleLeaf3] - const result = hash(nestedBranch) - - expect(Hex.size(result)).toBe(32) - }) - - it('should implement sequential hashing correctly', () => { - // Manual calculation to verify the algorithm - const leaf1Hash = hash(sampleLeaf1) - const leaf2Hash = hash(sampleLeaf2) - - // Should be keccak256(hash1 || hash2) - const expectedHash = Hash.keccak256(Bytes.concat(Hex.toBytes(leaf1Hash), Hex.toBytes(leaf2Hash)), { as: 'Hex' }) - - const branchHash = hash(sampleBranch) - expect(branchHash).toBe(expectedHash) - }) - - it('should handle 3-element branch sequential hashing', () => { - const threeBranch: Branch = [sampleLeaf1, sampleLeaf2, sampleLeaf3] - - // Manual calculation: keccak256(keccak256(h1 || h2) || h3) - const h1 = hash(sampleLeaf1) - const h2 = hash(sampleLeaf2) - const h3 = hash(sampleLeaf3) - - const intermediate = Hash.keccak256(Bytes.concat(Hex.toBytes(h1), Hex.toBytes(h2)), { as: 'Hex' }) - - const expectedHash = Hash.keccak256(Bytes.concat(Hex.toBytes(intermediate), Hex.toBytes(h3)), { as: 'Hex' }) - - const branchHash = hash(threeBranch) - expect(branchHash).toBe(expectedHash) - }) - - it('should throw error for empty branch', () => { - // Empty branch goes to isBranch -> false, then isNode -> false, then isLeaf -> false - // So it's not actually a valid tree, but if we force it to be hashed... - const emptyBranch: Branch = [] as any - // The hash function will only throw if it gets to the branch hashing logic - // But an empty array fails the isBranch check, so it won't get there - // Let's test that an empty array is correctly identified as invalid - expect(isBranch(emptyBranch)).toBe(false) - expect(isTree(emptyBranch)).toBe(false) - }) - }) - - describe('Complex tree hashing', () => { - it('should handle deeply nested trees', () => { - const deepTree: Branch = [ - [sampleLeaf1, sampleLeaf2], - [sampleLeaf3, sampleNode], - ] - - const result = hash(deepTree) - expect(Hex.size(result)).toBe(32) - }) - - it('should handle asymmetric trees', () => { - const asymmetricTree: Branch = [sampleLeaf1, [sampleLeaf2, sampleLeaf3]] - - const result = hash(asymmetricTree) - expect(Hex.size(result)).toBe(32) - }) - - it('should handle very deep nesting', () => { - let deepTree: Tree = sampleLeaf1 - - // Create a 5-level deep tree - for (let i = 0; i < 5; i++) { - deepTree = [deepTree, sampleLeaf2] - } - - const result = hash(deepTree) - expect(Hex.size(result)).toBe(32) - }) - - it('should be consistent with manual calculations', () => { - // Test a specific tree structure with known values - const specificLeaf: Leaf = { - type: 'leaf', - value: Bytes.fromHex('0x1234'), - } - - const specificNode: Node = ('0x' + '00'.repeat(32)) as Hex.Hex - const tree: Branch = [specificLeaf, specificNode] - - // Manual calculation - const leafHash = Hash.keccak256(Bytes.fromHex('0x1234'), { as: 'Hex' }) - const expectedHash = Hash.keccak256(Bytes.concat(Hex.toBytes(leafHash), Hex.toBytes(specificNode)), { - as: 'Hex', - }) - - const treeHash = hash(tree) - expect(treeHash).toBe(expectedHash) - }) - }) - }) - - describe('Edge cases and error conditions', () => { - it('should handle trees with identical elements', () => { - const identicalBranch: Branch = [sampleLeaf1, sampleLeaf1] - const result = hash(identicalBranch) - - expect(Hex.size(result)).toBe(32) - }) - - it('should handle branches with only nodes', () => { - const nodeBranch: Branch = [sampleNode, sampleNode2] - const result = hash(nodeBranch) - - expect(Hex.size(result)).toBe(32) - }) - - it('should handle mixed content branches', () => { - const mixedBranch: Branch = [sampleLeaf1, sampleNode, [sampleLeaf2, sampleLeaf3], sampleNode2] - - const result = hash(mixedBranch) - expect(Hex.size(result)).toBe(32) - }) - - it('should validate all type guards work together', () => { - const validTrees: Tree[] = [sampleLeaf1, sampleNode, sampleBranch, nestedBranch] - - validTrees.forEach((tree) => { - expect(isTree(tree)).toBe(true) - - // Should be able to hash all valid trees - const result = hash(tree) - expect(Hex.size(result)).toBe(32) - }) - }) - }) - - describe('Type system integration', () => { - it('should work with TypeScript type narrowing', () => { - const unknownTree: unknown = sampleBranch - - if (isTree(unknownTree)) { - // TypeScript should narrow the type here - const result = hash(unknownTree) - expect(Hex.size(result)).toBe(32) - } - }) - - it('should distinguish between tree types correctly', () => { - const trees: Tree[] = [sampleLeaf1, sampleNode, sampleBranch] - - trees.forEach((tree) => { - const isLeafResult = isLeaf(tree) - const isNodeResult = isNode(tree) - const isBranchResult = isBranch(tree) - - // Exactly one should be true - const trueCount = [isLeafResult, isNodeResult, isBranchResult].filter(Boolean).length - expect(trueCount).toBe(1) - }) - }) - }) - - describe('Performance and consistency', () => { - it('should be consistent across multiple calls', () => { - const results: Hex.Hex[] = [] - - for (let i = 0; i < 10; i++) { - results.push(hash(complexBranch)) - } - - // All results should be identical - expect(new Set(results).size).toBe(1) - }) - - it('should handle large trees', () => { - // Create a larger tree with many elements - const largeBranch: Tree = Array(10) - .fill(null) - .map((_, i) => ({ - type: 'leaf' as const, - value: Bytes.fromString(`leaf-${i}`), - })) as Branch - - const result = hash(largeBranch) - expect(Hex.size(result)).toBe(32) - }) - }) -}) diff --git a/packages/wallet/primitives/test/passkeys.test.ts b/packages/wallet/primitives/test/passkeys.test.ts deleted file mode 100644 index ddfd59b9ea..0000000000 --- a/packages/wallet/primitives/test/passkeys.test.ts +++ /dev/null @@ -1,821 +0,0 @@ -import { describe, expect, it, vi, beforeEach, beforeAll, afterAll } from 'vitest' -import { Bytes, Hex } from 'ox' - -import { - PasskeyMetadata, - PublicKey, - metadataTree, - metadataNode, - toTree, - fromTree, - rootFor, - DecodedSignature, - encode, - decode, - isValidSignature, -} from '../src/extensions/passkeys.js' -import * as GenericTree from '../src/generic-tree.js' - -// Enhanced mock setup based on ox patterns -beforeAll(() => { - vi.stubGlobal('window', { - location: { - hostname: 'example.com', - origin: 'https://example.com', - }, - document: { - title: 'Passkey Test', - }, - }) -}) - -afterAll(() => { - vi.restoreAllMocks() -}) - -// Enhanced mock for WebAuthnP256 with more realistic behavior based on ox patterns -vi.mock('ox', async () => { - const actual = await vi.importActual('ox') - return { - ...actual, - WebAuthnP256: { - verify: vi.fn().mockImplementation(({ challenge, publicKey, signature, metadata }) => { - // More sophisticated verification logic based on ox patterns - if (!challenge || !publicKey || !signature || !metadata) return false - - // Validate basic structure - if (!metadata.authenticatorData || !metadata.clientDataJSON) return false - if (typeof metadata.challengeIndex !== 'number' || typeof metadata.typeIndex !== 'number') return false - - // Validate signature components - if (!signature.r || !signature.s || signature.r === 0n || signature.s === 0n) return false - - // Validate public key coordinates (should not be zero) - if (publicKey.x === 0n || publicKey.y === 0n) return false - - // Simulate WebAuthn validation logic - try { - // Parse client data JSON - const clientData = JSON.parse(metadata.clientDataJSON) - if (clientData.type !== 'webauthn.get') return false - - // Verify challenge extraction - const challengeFromJSON = clientData.challenge - if (!challengeFromJSON) return false - - // For test purposes, consider valid if structure is correct - return true - } catch { - return false - } - }), - }, - } -}) - -describe('Passkeys', () => { - // Real P-256 curve points that fit within 32 bytes (from ox WebAuthnP256 test data) - // These are actual valid secp256r1 coordinates that work with Hex.padLeft(32) - const testPublicKeyX = '0x62a31768d44f5eff222f8d70c4cb61abd5840b27d617a7fe8d11b72dd5e86fc1' as Hex.Hex // 32 bytes - const testPublicKeyY = '0x6611bae3f1e2cd38e405153776a7dcb6995b8254a1416ead102a096c45d80618' as Hex.Hex // 32 bytes - - // Valid secp256r1 signature components from ox test data (32 bytes each) - const validR = Bytes.fromHex('0x171c8c7b0c3fbea57a28027bc8cf2bbc8b3a22dc31e69e0e9c6b8b9c6b8b9c6b') - const validS = Bytes.fromHex('0x6729577e31f54b21dbf72c2c805e5a9e7d5b9e7e5e5e5e5e5e5e5e5e5e5e5e5e') - - const testCredentialId = 'test-credential-id-12345' - const testMetadataHash = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex // 32 bytes - const testChallenge = '0xf631058a3ba1116acce12396fad0a125b5041c43f8e15723709f81aa8d5f4ccf' as Hex.Hex // From ox tests - - const samplePasskeyMetadata: PasskeyMetadata = { - credentialId: testCredentialId, - } - - const samplePublicKey: PublicKey = { - requireUserVerification: true, - x: testPublicKeyX, - y: testPublicKeyY, - metadata: samplePasskeyMetadata, - } - - const samplePublicKeyWithoutMetadata: PublicKey = { - requireUserVerification: false, - x: testPublicKeyX, - y: testPublicKeyY, - } - - // Realistic authenticator data based on WebAuthn spec and ox patterns - // This represents actual WebAuthn authenticator data structure - const sampleAuthenticatorData = Bytes.fromHex( - '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000', - ) - - // Valid WebAuthn client data JSON structure based on ox patterns - const sampleClientDataJSON = - '{"type":"webauthn.get","challenge":"9jEFijuhEWrM4SOW-tChJbUEHEP44VcjcJ-Bqo1fTM8","origin":"https://example.com","crossOrigin":false}' - - const sampleDecodedSignature: DecodedSignature = { - publicKey: samplePublicKey, - r: validR, - s: validS, - authenticatorData: sampleAuthenticatorData, - clientDataJSON: sampleClientDataJSON, - embedMetadata: true, - } - - // Helper functions to create valid test data following ox patterns - const createValidPublicKey = (options: Partial = {}): PublicKey => ({ - requireUserVerification: false, - x: testPublicKeyX, - y: testPublicKeyY, - ...options, - }) - - const createValidSignature = (options: Partial = {}): DecodedSignature => ({ - publicKey: samplePublicKey, - r: validR, - s: validS, - authenticatorData: sampleAuthenticatorData, - clientDataJSON: sampleClientDataJSON, - embedMetadata: false, - ...options, - }) - - // Create WebAuthn metadata following ox patterns - const createValidMetadata = (overrides: any = {}) => ({ - authenticatorData: '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000' as Hex.Hex, - challengeIndex: 23, - clientDataJSON: - '{"type":"webauthn.get","challenge":"9jEFijuhEWrM4SOW-tChJbUEHEP44VcjcJ-Bqo1fTM8","origin":"https://example.com","crossOrigin":false}', - typeIndex: 1, - userVerificationRequired: true, - ...overrides, - }) - - describe('Metadata Operations', () => { - describe('metadataTree', () => { - it('should create tree from passkey metadata object', () => { - const tree = metadataTree(samplePasskeyMetadata) - expect(GenericTree.isLeaf(tree)).toBe(true) - if (GenericTree.isLeaf(tree)) { - expect(tree.type).toBe('leaf') - expect(tree.value).toBeInstanceOf(Uint8Array) - const decodedCredentialId = new TextDecoder().decode(tree.value) - expect(decodedCredentialId).toBe(testCredentialId) - } - }) - - it('should return hash directly for hex metadata', () => { - const tree = metadataTree(testMetadataHash) - expect(tree).toBe(testMetadataHash) - expect(typeof tree).toBe('string') - }) - - it('should handle different credential IDs', () => { - const metadata1: PasskeyMetadata = { credentialId: 'cred1' } - const metadata2: PasskeyMetadata = { credentialId: 'cred2' } - - const tree1 = metadataTree(metadata1) - const tree2 = metadataTree(metadata2) - - expect(tree1).not.toEqual(tree2) - }) - - it('should handle edge cases in credential IDs', () => { - const testCases = [ - { name: 'empty', credentialId: '' }, - { name: 'long', credentialId: 'a'.repeat(1000) }, - { name: 'unicode', credentialId: '测试凭证🔑' }, - { name: 'special chars', credentialId: '!@#$%^&*()_+{}|:"<>?[]\\;\',./' }, - ] - - testCases.forEach(({ credentialId }) => { - const metadata: PasskeyMetadata = { credentialId } - const tree = metadataTree(metadata) - expect(GenericTree.isLeaf(tree)).toBe(true) - - if (GenericTree.isLeaf(tree)) { - const decoded = new TextDecoder().decode(tree.value) - expect(decoded).toBe(credentialId) - } - }) - }) - }) - - describe('metadataNode', () => { - it('should create consistent hashes for same input', () => { - const node1 = metadataNode(samplePasskeyMetadata) - const node2 = metadataNode(samplePasskeyMetadata) - expect(node1).toBe(node2) - expect(node1).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(node1).toHaveLength(66) - }) - - it('should create different hashes for different inputs', () => { - const metadata1: PasskeyMetadata = { credentialId: 'cred1' } - const metadata2: PasskeyMetadata = { credentialId: 'cred2' } - - const node1 = metadataNode(metadata1) - const node2 = metadataNode(metadata2) - expect(node1).not.toBe(node2) - }) - - it('should handle hex metadata input', () => { - const node = metadataNode(testMetadataHash) - expect(node).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(node).toHaveLength(66) - }) - }) - }) - - describe('Tree Operations', () => { - describe('toTree', () => { - it('should create valid tree structure', () => { - const tree = toTree(samplePublicKey) - expect(GenericTree.isBranch(tree)).toBe(true) - if (GenericTree.isBranch(tree)) { - expect(tree).toHaveLength(2) - expect(GenericTree.isBranch(tree[0])).toBe(true) - expect(GenericTree.isBranch(tree[1])).toBe(true) - } - }) - - it('should handle public key without metadata', () => { - const tree = toTree(samplePublicKeyWithoutMetadata) - expect(GenericTree.isBranch(tree)).toBe(true) - if (GenericTree.isBranch(tree)) { - expect(tree).toHaveLength(2) - const [, p2] = tree - if (GenericTree.isBranch(p2)) { - expect(GenericTree.isNode(p2[1])).toBe(true) - expect(p2[1]).toBe('0x0000000000000000000000000000000000000000000000000000000000000000') - } - } - }) - - it('should properly pad coordinates', () => { - const shortCoordinateKey = createValidPublicKey({ - x: '0x1234' as Hex.Hex, - y: '0x5678' as Hex.Hex, - }) - - const tree = toTree(shortCoordinateKey) - expect(GenericTree.isBranch(tree)).toBe(true) - if (GenericTree.isBranch(tree)) { - const [p1] = tree - if (GenericTree.isBranch(p1)) { - expect(p1[0]).toBe('0x0000000000000000000000000000000000000000000000000000000000001234') - expect(p1[1]).toBe('0x0000000000000000000000000000000000000000000000000000000000005678') - } - } - }) - - it('should differentiate user verification states', () => { - const keyWithVerification = createValidPublicKey({ requireUserVerification: true }) - const keyWithoutVerification = createValidPublicKey({ requireUserVerification: false }) - - const tree1 = toTree(keyWithVerification) - const tree2 = toTree(keyWithoutVerification) - - expect(tree1).not.toEqual(tree2) - }) - }) - - describe('fromTree', () => { - it('should successfully roundtrip with toTree for simple key', () => { - const originalKey = samplePublicKeyWithoutMetadata - const tree = toTree(originalKey) - const reconstructedKey = fromTree(tree) - - expect(reconstructedKey.requireUserVerification).toBe(originalKey.requireUserVerification) - expect(reconstructedKey.x).toBe(originalKey.x) - expect(reconstructedKey.y).toBe(originalKey.y) - // Note: metadata becomes a zero node after roundtrip, not undefined - expect(reconstructedKey.metadata).toBe('0x0000000000000000000000000000000000000000000000000000000000000000') - }) - - it('should handle user verification flags correctly', () => { - const keyWithVerification = createValidPublicKey({ requireUserVerification: true }) - const keyWithoutVerification = createValidPublicKey({ requireUserVerification: false }) - - // Remove metadata to keep it simple - delete (keyWithVerification as any).metadata - delete (keyWithoutVerification as any).metadata - - const treeWith = toTree(keyWithVerification) - const treeWithout = toTree(keyWithoutVerification) - - const reconstructedWith = fromTree(treeWith) - const reconstructedWithout = fromTree(treeWithout) - - expect(reconstructedWith.requireUserVerification).toBe(true) - expect(reconstructedWithout.requireUserVerification).toBe(false) - }) - - it('should throw for invalid tree structure', () => { - expect(() => fromTree('invalid' as any)).toThrow('Invalid tree') - expect(() => fromTree([testPublicKeyX] as any)).toThrow('Invalid tree') - }) - - it('should throw for invalid x coordinate', () => { - const invalidTree = [ - [{ type: 'leaf', value: new Uint8Array([1, 2, 3]) }, testPublicKeyY], - testPublicKeyX, - ] as any - expect(() => fromTree(invalidTree)).toThrow('Invalid x bytes') - }) - - it('should throw for invalid y coordinate', () => { - const invalidTree = [ - [testPublicKeyX, { type: 'leaf', value: new Uint8Array([1, 2, 3]) }], - testPublicKeyY, - ] as any - expect(() => fromTree(invalidTree)).toThrow('Invalid y bytes') - }) - - it('should document structural limitations', () => { - // Document that passkey objects don't roundtrip due to toTree/fromTree mismatch - const originalKey = samplePublicKey - const tree = toTree(originalKey) - expect(() => fromTree(tree)).toThrow('Invalid metadata node') - - // Document that complex metadata structures can't be easily tested - // due to validation order in the implementation - expect(true).toBe(true) // Represents uncovered complex metadata parsing lines - }) - }) - - describe('rootFor', () => { - it('should generate consistent root hashes', () => { - const root1 = rootFor(samplePublicKey) - const root2 = rootFor(samplePublicKey) - expect(root1).toBe(root2) - expect(root1).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(root1).toHaveLength(66) - }) - - it('should produce different roots for different keys', () => { - const root1 = rootFor(samplePublicKey) - const root2 = rootFor(samplePublicKeyWithoutMetadata) - expect(root1).not.toBe(root2) - }) - - it('should match tree hash calculation', () => { - const tree = toTree(samplePublicKey) - const treeHash = GenericTree.hash(tree) - const root = rootFor(samplePublicKey) - expect(root).toBe(treeHash) - }) - }) - }) - - describe('Signature Encoding and Decoding', () => { - describe('encode', () => { - it('should encode signature with metadata', () => { - const encoded = encode(sampleDecodedSignature) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBeGreaterThan(100) // Should be substantial due to metadata - }) - - it('should encode signature without metadata', () => { - const signatureWithoutMetadata = createValidSignature({ - publicKey: samplePublicKeyWithoutMetadata, - embedMetadata: false, - }) - - const encoded = encode(signatureWithoutMetadata) - expect(encoded).toBeInstanceOf(Uint8Array) - - const encodedWithMetadata = encode(sampleDecodedSignature) - expect(encoded.length).toBeLessThan(encodedWithMetadata.length) - }) - - it('should handle user verification combinations', () => { - const testCases = [ - { requireUserVerification: true, embedMetadata: true }, - { requireUserVerification: false, embedMetadata: true }, - { requireUserVerification: true, embedMetadata: false }, - { requireUserVerification: false, embedMetadata: false }, - ] - - testCases.forEach(({ requireUserVerification, embedMetadata }) => { - const publicKey = createValidPublicKey({ - requireUserVerification, - ...(embedMetadata && { metadata: samplePasskeyMetadata }), - }) - - const signature = createValidSignature({ - publicKey, - embedMetadata, - }) - - const encoded = encode(signature) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBeGreaterThan(0) - }) - }) - - it('should validate size limits following WebAuthn spec', () => { - // Test authenticator data size limit - const tooLargeAuthData = new Uint8Array(65536) - const signatureWithLargeAuth = createValidSignature({ - authenticatorData: tooLargeAuthData, - }) - expect(() => encode(signatureWithLargeAuth)).toThrow('Authenticator data size is too large') - - // Test client data JSON size limit - const tooLargeClientDataJSON = 'a'.repeat(65536) - const signatureWithLargeJSON = createValidSignature({ - clientDataJSON: tooLargeClientDataJSON, - }) - expect(() => encode(signatureWithLargeJSON)).toThrow('Client data JSON size is too large') - }) - - it('should require metadata when embedMetadata is true', () => { - const signature = createValidSignature({ - publicKey: samplePublicKeyWithoutMetadata, - embedMetadata: true, - }) - - expect(() => encode(signature)).toThrow('Metadata is not present in the public key') - }) - }) - - describe('decode', () => { - it('should perform round-trip encoding/decoding', () => { - const encoded = encode(sampleDecodedSignature) - const decoded = decode(encoded) - - expect(decoded.publicKey.requireUserVerification).toBe(sampleDecodedSignature.publicKey.requireUserVerification) - expect(decoded.publicKey.x).toBe(sampleDecodedSignature.publicKey.x) - expect(decoded.publicKey.y).toBe(sampleDecodedSignature.publicKey.y) - expect(decoded.embedMetadata).toBe(sampleDecodedSignature.embedMetadata) - expect(decoded.clientDataJSON).toBe(sampleDecodedSignature.clientDataJSON) - - // Verify re-encoding produces same result - const reEncoded = encode(decoded) - expect(reEncoded).toEqual(encoded) - }) - - it('should decode signature without metadata', () => { - const signatureWithoutMetadata = createValidSignature({ - publicKey: samplePublicKeyWithoutMetadata, - embedMetadata: false, - }) - - const encoded = encode(signatureWithoutMetadata) - const decoded = decode(encoded) - - expect(decoded.embedMetadata).toBe(false) - expect(decoded.publicKey.metadata).toBeUndefined() - }) - - it('should handle various authenticator data sizes', () => { - const testSizes = [37, 100, 1000] // Minimum WebAuthn size and larger - - testSizes.forEach((size) => { - const authData = new Uint8Array(size).fill(0x42) - const signature = createValidSignature({ - authenticatorData: authData, - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - - expect(decoded.authenticatorData).toEqual(authData) - }) - }) - - it('should handle WebAuthn client data variations', () => { - const clientDataVariations = [ - '{"type":"webauthn.get","challenge":"dGVzdA","origin":"https://example.com"}', - '{"origin":"https://example.com","type":"webauthn.get","challenge":"dGVzdA"}', - '{"type":"webauthn.get","challenge":"dGVzdA","origin":"https://example.com","crossOrigin":false}', - '{"type":"webauthn.create","challenge":"Y3JlYXRl","origin":"https://example.com"}', - ] - - clientDataVariations.forEach((clientDataJSON) => { - const signature = createValidSignature({ - clientDataJSON, - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - - expect(decoded.challengeIndex).toBeGreaterThanOrEqual(0) - expect(decoded.typeIndex).toBeGreaterThanOrEqual(0) - expect(decoded.clientDataJSON).toBe(clientDataJSON) - }) - }) - - it('should throw for invalid flag combinations', () => { - const invalidData = new Uint8Array([]) - expect(() => decode(invalidData)).toThrow('Invalid flags') - }) - - it('should reject fallback flag', () => { - const dataWithFallbackFlag = new Uint8Array([0x20]) - expect(() => decode(dataWithFallbackFlag)).toThrow('Fallback to abi decode is not supported') - }) - }) - }) - - describe('Signature Validation', () => { - describe('isValidSignature', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('should validate correct signature structure', () => { - const result = isValidSignature(testChallenge, sampleDecodedSignature) - expect(result).toBe(true) - }) - - it('should handle different challenge formats', () => { - const challenges = [ - '0x0000000000000000000000000000000000000000000000000000000000000000' as Hex.Hex, - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as Hex.Hex, - testChallenge, - '0xf631058a3ba1116acce12396fad0a125b5041c43f8e15723709f81aa8d5f4ccf' as Hex.Hex, // From ox tests - ] - - challenges.forEach((challenge) => { - const result = isValidSignature(challenge, sampleDecodedSignature) - expect(typeof result).toBe('boolean') - }) - }) - - it('should validate user verification requirements', () => { - const withVerification = createValidSignature({ - publicKey: createValidPublicKey({ requireUserVerification: true }), - }) - const withoutVerification = createValidSignature({ - publicKey: createValidPublicKey({ requireUserVerification: false }), - }) - - const result1 = isValidSignature(testChallenge, withVerification) - const result2 = isValidSignature(testChallenge, withoutVerification) - - expect(typeof result1).toBe('boolean') - expect(typeof result2).toBe('boolean') - }) - - it('should handle invalid public key coordinates gracefully', () => { - const invalidPublicKey = createValidPublicKey({ - x: '0x0000000000000000000000000000000000000000000000000000000000000000' as Hex.Hex, - y: '0x0000000000000000000000000000000000000000000000000000000000000000' as Hex.Hex, - }) - - const signature = createValidSignature({ - publicKey: invalidPublicKey, - }) - - const result = isValidSignature(testChallenge, signature) - expect(typeof result).toBe('boolean') - expect(result).toBe(false) // Should be false for zero coordinates - }) - - it('should validate signature components following ox patterns', () => { - // Test with zero signature components (should fail) - const invalidSignature = createValidSignature({ - r: new Uint8Array(32).fill(0), - s: new Uint8Array(32).fill(0), - }) - - const result = isValidSignature(testChallenge, invalidSignature) - expect(result).toBe(false) - }) - - it('should handle malformed client data JSON', () => { - const malformedSignature = createValidSignature({ - clientDataJSON: 'invalid json', - }) - - const result = isValidSignature(testChallenge, malformedSignature) - expect(result).toBe(false) - }) - }) - }) - - describe('WebAuthn Spec Compliance', () => { - it('should handle authenticator data flag variations', () => { - // Test different authenticator data flags following WebAuthn spec - const flagVariations = [ - '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000' as Hex.Hex, // User present - '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630100000000' as Hex.Hex, // User verified - '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631500000000' as Hex.Hex, // Both flags - ] - - flagVariations.forEach((authenticatorData) => { - const signature = createValidSignature({ - authenticatorData: Bytes.fromHex(authenticatorData), - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - expect(decoded.authenticatorData).toEqual(Bytes.fromHex(authenticatorData)) - }) - }) - - it('should handle WebAuthn challenge encoding variations', () => { - // Test base64url encoded challenges as used in real WebAuthn - const challengeVariations = [ - 'ESIzRFVmd4iZqrvM3e7_', // Short challenge - '9jEFijuhEWrM4SOW-tChJbUEHEP44VcjcJ-Bqo1fTM8', // From ox tests - 'dGVzdC1jaGFsbGVuZ2UtZXhhbXBsZS0xMjM0NTY3ODkw', // Longer challenge - ] - - challengeVariations.forEach((challenge) => { - const clientDataJSON = `{"type":"webauthn.get","challenge":"${challenge}","origin":"https://example.com"}` - const signature = createValidSignature({ - clientDataJSON, - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - expect(decoded.clientDataJSON).toBe(clientDataJSON) - }) - }) - - it('should handle WebAuthn type variations', () => { - const typeVariations = [ - 'webauthn.get', // Authentication - 'webauthn.create', // Registration - ] - - typeVariations.forEach((type) => { - const clientDataJSON = `{"type":"${type}","challenge":"dGVzdA","origin":"https://example.com"}` - const signature = createValidSignature({ - clientDataJSON, - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - expect(decoded.clientDataJSON).toBe(clientDataJSON) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle minimal valid WebAuthn data structures', () => { - const minimalKey = createValidPublicKey() - const minimalSignature = createValidSignature({ - publicKey: minimalKey, - authenticatorData: new Uint8Array(37).fill(0x03), // Minimum WebAuthn size - clientDataJSON: '{"type":"webauthn.get","challenge":"abc","origin":"https://example.com"}', - embedMetadata: false, - }) - - const encoded = encode(minimalSignature) - const decoded = decode(encoded) - - expect(decoded.publicKey.requireUserVerification).toBe(minimalSignature.publicKey.requireUserVerification) - expect(decoded.clientDataJSON).toBe(minimalSignature.clientDataJSON) - }) - - it('should handle extreme coordinate values', () => { - const extremeKey = createValidPublicKey({ - x: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as Hex.Hex, - y: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' as Hex.Hex, - }) - - const tree = toTree(extremeKey) - const root = rootFor(extremeKey) - - expect(GenericTree.isBranch(tree)).toBe(true) - expect(root).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(GenericTree.hash(tree)).toBe(root) - }) - - it('should handle Unicode and special characters following WebAuthn spec', () => { - const specialCharTests = [ - { name: 'Unicode credential ID', credentialId: '测试凭证🔑' }, - { - name: 'Special chars in JSON', - clientData: - '{"type":"webauthn.get","challenge":"abc","origin":"https://example.com","extra":"quotes\\"and\\\\backslashes"}', - }, - ] - - specialCharTests.forEach(({ credentialId, clientData }) => { - if (credentialId) { - const unicodeMetadata: PasskeyMetadata = { credentialId } - const tree = metadataTree(unicodeMetadata) - expect(GenericTree.isLeaf(tree)).toBe(true) - - if (GenericTree.isLeaf(tree)) { - const decoded = new TextDecoder().decode(tree.value) - expect(decoded).toBe(credentialId) - } - } - - if (clientData) { - const signature = createValidSignature({ - clientDataJSON: clientData, - embedMetadata: false, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - expect(decoded.clientDataJSON).toBe(clientData) - } - }) - }) - }) - - describe('Integration Tests', () => { - it('should handle complete WebAuthn passkey workflow', () => { - // Simulate complete WebAuthn flow with realistic data - const publicKey = samplePublicKey - - // Generate tree representation - const tree = toTree(publicKey) - const root = rootFor(publicKey) - - // Verify tree consistency - expect(GenericTree.hash(tree)).toBe(root) - - // Test signature encoding/decoding with WebAuthn metadata - const signature = sampleDecodedSignature - const encoded = encode(signature) - const decoded = decode(encoded) - - // Verify signature consistency - expect(decoded.publicKey.x).toBe(signature.publicKey.x) - expect(decoded.publicKey.y).toBe(signature.publicKey.y) - - // Test signature validation - const isValid = isValidSignature(testChallenge, decoded) - expect(typeof isValid).toBe('boolean') - }) - - it('should handle metadata operations end-to-end', () => { - const passkeyMeta = samplePasskeyMetadata - const tree1 = metadataTree(passkeyMeta) - const node1 = metadataNode(passkeyMeta) - - const hexMeta = testMetadataHash - const tree2 = metadataTree(hexMeta) - const node2 = metadataNode(hexMeta) - - // Verify different types produce different results - expect(tree1).not.toEqual(tree2) - expect(node1).not.toBe(node2) - - // Verify consistency with tree hashing - expect(GenericTree.hash(tree1)).toBe(node1) - expect(GenericTree.hash(tree2)).toBe(node2) - }) - - it('should handle all WebAuthn flag combinations in encoding', () => { - const testCombinations = [ - { userVerification: false, metadata: false, description: 'No verification, no metadata' }, - { userVerification: true, metadata: false, description: 'User verification, no metadata' }, - { userVerification: false, metadata: true, description: 'No verification, with metadata' }, - { userVerification: true, metadata: true, description: 'User verification with metadata' }, - ] - - testCombinations.forEach(({ userVerification, metadata }) => { - const pubKey = createValidPublicKey({ - requireUserVerification: userVerification, - ...(metadata && { metadata: samplePasskeyMetadata }), - }) - - const signature = createValidSignature({ - publicKey: pubKey, - embedMetadata: metadata, - }) - - const encoded = encode(signature) - const decoded = decode(encoded) - - expect(decoded.publicKey.requireUserVerification).toBe(userVerification) - expect(decoded.embedMetadata).toBe(metadata) - if (metadata) { - expect(decoded.publicKey.metadata).toBeDefined() - } else { - expect(decoded.publicKey.metadata).toBeUndefined() - } - }) - }) - - it('should match ox WebAuthn patterns for signature verification', () => { - // Test using patterns similar to ox WebAuthnP256 tests - const metadata = createValidMetadata() - - // Create signature following ox test patterns - const signature = createValidSignature({ - clientDataJSON: metadata.clientDataJSON, - authenticatorData: Bytes.fromHex(metadata.authenticatorData), - }) - - const result = isValidSignature(testChallenge, signature) - expect(typeof result).toBe('boolean') - }) - }) -}) diff --git a/packages/wallet/primitives/test/payload.test.ts b/packages/wallet/primitives/test/payload.test.ts deleted file mode 100644 index 33884dbdb1..0000000000 --- a/packages/wallet/primitives/test/payload.test.ts +++ /dev/null @@ -1,1070 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Address, Bytes, Hex } from 'ox' - -import { - KIND_TRANSACTIONS, - KIND_MESSAGE, - KIND_CONFIG_UPDATE, - KIND_DIGEST, - BEHAVIOR_IGNORE_ERROR, - BEHAVIOR_REVERT_ON_ERROR, - BEHAVIOR_ABORT_ON_ERROR, - Call, - Calls, - Message, - ConfigUpdate, - Digest, - SessionImplicitAuthorize, - Calls4337_07, - Parented, - SolidityDecoded, - fromMessage, - fromConfigUpdate, - fromDigest, - fromCall, - isCalls, - isMessage, - isConfigUpdate, - isDigest, - isRecovery, - isCalls4337_07, - toRecovery, - isSessionImplicitAuthorize, - encode, - encodeSapient, - hash, - encode4337Nonce, - toTyped, - to4337UserOperation, - to4337Message, - encodeBehaviorOnError, - hashCall, - decode, - decodeBehaviorOnError, - fromAbiFormat, - toAbiFormat, -} from '../src/payload.js' -import * as Attestation from '../src/attestation.js' -import { ChainId } from '../src/network.js' - -describe('Payload', () => { - // Test data - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' as Address.Address - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' as Address.Address - const testChainId = ChainId.MAINNET - const testImageHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - const testDigest = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - const testMessage = '0x48656c6c6f20576f726c64' as Hex.Hex // "Hello World" in hex - - const sampleCall: Call = { - to: testAddress, - value: 1000n, - data: '0x1234567890abcdef', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const sampleCalls: Calls = { - type: 'call', - space: 0n, - nonce: 1n, - calls: [sampleCall], - } - - const sampleMessage: Message = { - type: 'message', - message: testMessage, - } - - const sampleConfigUpdate: ConfigUpdate = { - type: 'config-update', - imageHash: testImageHash, - } - - const sampleDigest: Digest = { - type: 'digest', - digest: testDigest, - } - - const sampleAttestation: Attestation.Attestation = { - approvedSigner: testAddress, - identityType: Bytes.fromHex('0x00000001'), - issuerHash: Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), - audienceHash: Bytes.fromHex('0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef'), - applicationData: Bytes.fromString('test application data'), - authData: { - redirectUrl: 'https://example.com/callback', - issuedAt: 123456789n, - }, - } - - const sampleSessionImplicitAuthorize: SessionImplicitAuthorize = { - type: 'session-implicit-authorize', - sessionAddress: testAddress, - attestation: sampleAttestation, - } - - const sampleCalls4337: Calls4337_07 = { - type: 'call_4337_07', - calls: [sampleCall], - entrypoint: testAddress2, - callGasLimit: 100000n, - maxFeePerGas: 20000000000n, - maxPriorityFeePerGas: 1000000000n, - space: 0n, - nonce: 1n, - preVerificationGas: 21000n, - verificationGasLimit: 100000n, - } - - describe('Constants', () => { - it('should have correct kind constants', () => { - expect(KIND_TRANSACTIONS).toBe(0x00) - expect(KIND_MESSAGE).toBe(0x01) - expect(KIND_CONFIG_UPDATE).toBe(0x02) - expect(KIND_DIGEST).toBe(0x03) - }) - - it('should have correct behavior constants', () => { - expect(BEHAVIOR_IGNORE_ERROR).toBe(0x00) - expect(BEHAVIOR_REVERT_ON_ERROR).toBe(0x01) - expect(BEHAVIOR_ABORT_ON_ERROR).toBe(0x02) - }) - }) - - describe('Factory Functions', () => { - describe('fromMessage', () => { - it('should create message payload', () => { - const result = fromMessage(testMessage) - expect(result).toEqual({ - type: 'message', - message: testMessage, - }) - }) - }) - - describe('fromConfigUpdate', () => { - it('should create config update payload', () => { - const result = fromConfigUpdate(testImageHash) - expect(result).toEqual({ - type: 'config-update', - imageHash: testImageHash, - }) - }) - }) - - describe('fromDigest', () => { - it('should create digest payload', () => { - const result = fromDigest(testDigest) - expect(result).toEqual({ - type: 'digest', - digest: testDigest, - }) - }) - }) - - describe('fromCall', () => { - it('should create calls payload', () => { - const result = fromCall(1n, 0n, [sampleCall]) - expect(result).toEqual({ - type: 'call', - nonce: 1n, - space: 0n, - calls: [sampleCall], - }) - }) - }) - }) - - describe('Type Guards', () => { - describe('isCalls', () => { - it('should return true for calls payload', () => { - expect(isCalls(sampleCalls)).toBe(true) - }) - - it('should return false for non-calls payload', () => { - expect(isCalls(sampleMessage)).toBe(false) - expect(isCalls(sampleConfigUpdate)).toBe(false) - expect(isCalls(sampleDigest)).toBe(false) - }) - }) - - describe('isMessage', () => { - it('should return true for message payload', () => { - expect(isMessage(sampleMessage)).toBe(true) - }) - - it('should return false for non-message payload', () => { - expect(isMessage(sampleCalls)).toBe(false) - expect(isMessage(sampleConfigUpdate)).toBe(false) - expect(isMessage(sampleDigest)).toBe(false) - }) - }) - - describe('isConfigUpdate', () => { - it('should return true for config update payload', () => { - expect(isConfigUpdate(sampleConfigUpdate)).toBe(true) - }) - - it('should return false for non-config update payload', () => { - expect(isConfigUpdate(sampleCalls)).toBe(false) - expect(isConfigUpdate(sampleMessage)).toBe(false) - expect(isConfigUpdate(sampleDigest)).toBe(false) - }) - }) - - describe('isDigest', () => { - it('should return true for digest payload', () => { - expect(isDigest(sampleDigest)).toBe(true) - }) - - it('should return false for non-digest payload', () => { - expect(isDigest(sampleCalls)).toBe(false) - expect(isDigest(sampleMessage)).toBe(false) - expect(isDigest(sampleConfigUpdate)).toBe(false) - }) - }) - - describe('isRecovery', () => { - it('should return true for recovery payload', () => { - const recoveryPayload = toRecovery(sampleCalls) - expect(isRecovery(recoveryPayload)).toBe(true) - }) - - it('should return false for non-recovery payload', () => { - expect(isRecovery(sampleCalls)).toBe(false) - expect(isRecovery(sampleMessage)).toBe(false) - }) - - it('should return false for session implicit authorize', () => { - expect(isRecovery(sampleSessionImplicitAuthorize)).toBe(false) - }) - }) - - describe('isCalls4337_07', () => { - it('should return true for calls 4337 payload', () => { - expect(isCalls4337_07(sampleCalls4337)).toBe(true) - }) - - it('should return false for non-calls 4337 payload', () => { - expect(isCalls4337_07(sampleCalls)).toBe(false) - expect(isCalls4337_07(sampleMessage)).toBe(false) - }) - }) - - describe('isSessionImplicitAuthorize', () => { - it('should return true for session implicit authorize payload', () => { - expect(isSessionImplicitAuthorize(sampleSessionImplicitAuthorize)).toBe(true) - }) - - it('should return false for non-session implicit authorize payload', () => { - expect(isSessionImplicitAuthorize(sampleCalls)).toBe(false) - expect(isSessionImplicitAuthorize(sampleMessage)).toBe(false) - }) - }) - }) - - describe('toRecovery', () => { - it('should add recovery flag to payload', () => { - const result = toRecovery(sampleCalls) - expect(result).toEqual({ - ...sampleCalls, - recovery: true, - }) - }) - - it('should return same payload if already recovery', () => { - const recoveryPayload = toRecovery(sampleCalls) - const result = toRecovery(recoveryPayload) - expect(result).toBe(recoveryPayload) - }) - }) - - describe('Behavior Encoding/Decoding', () => { - describe('encodeBehaviorOnError', () => { - it('should encode ignore behavior', () => { - expect(encodeBehaviorOnError('ignore')).toBe(BEHAVIOR_IGNORE_ERROR) - }) - - it('should encode revert behavior', () => { - expect(encodeBehaviorOnError('revert')).toBe(BEHAVIOR_REVERT_ON_ERROR) - }) - - it('should encode abort behavior', () => { - expect(encodeBehaviorOnError('abort')).toBe(BEHAVIOR_ABORT_ON_ERROR) - }) - }) - - describe('decodeBehaviorOnError', () => { - it('should decode ignore behavior', () => { - expect(decodeBehaviorOnError(0)).toBe('ignore') - }) - - it('should decode revert behavior', () => { - expect(decodeBehaviorOnError(1)).toBe('revert') - }) - - it('should decode abort behavior', () => { - expect(decodeBehaviorOnError(2)).toBe('abort') - }) - - it('should throw for invalid behavior', () => { - expect(() => decodeBehaviorOnError(3)).toThrow('Invalid behaviorOnError value: 3') - }) - }) - }) - - describe('encode4337Nonce', () => { - it('should encode nonce correctly', () => { - const key = 123n - const seq = 456n - const result = encode4337Nonce(key, seq) - expect(result).toBe((key << 64n) | seq) - }) - - it('should handle zero values', () => { - expect(encode4337Nonce(0n, 0n)).toBe(0n) - expect(encode4337Nonce(0n, 123n)).toBe(123n) - expect(encode4337Nonce(123n, 0n)).toBe(123n << 64n) - }) - - it('should throw for key exceeding 192 bits', () => { - const maxKey = 6277101735386680763835789423207666416102355444464034512895n - const tooBigKey = maxKey + 1n - expect(() => encode4337Nonce(tooBigKey, 0n)).toThrow('key exceeds 192 bits') - }) - - it('should throw for seq exceeding 64 bits', () => { - const maxSeq = 18446744073709551615n - const tooBigSeq = maxSeq + 1n - expect(() => encode4337Nonce(0n, tooBigSeq)).toThrow('seq exceeds 64 bits') - }) - }) - - describe('Call Hashing', () => { - describe('hashCall', () => { - it('should hash call correctly', () => { - const result = hashCall(sampleCall) - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(Hex.size(result)).toBe(32) - }) - - it('should be deterministic', () => { - const result1 = hashCall(sampleCall) - const result2 = hashCall(sampleCall) - expect(result1).toBe(result2) - }) - - it('should produce different hashes for different calls', () => { - const call2: Call = { - ...sampleCall, - to: testAddress2, - } - const hash1 = hashCall(sampleCall) - const hash2 = hashCall(call2) - expect(hash1).not.toBe(hash2) - }) - - it('should handle different behavior on error values', () => { - const calls = ['ignore', 'revert', 'abort'].map((behavior) => ({ - ...sampleCall, - behaviorOnError: behavior as Call['behaviorOnError'], - })) - - const hashes = calls.map((call) => hashCall(call)) - // All hashes should be different - expect(new Set(hashes).size).toBe(3) - }) - }) - }) - - describe('Payload Hashing', () => { - describe('hash', () => { - it('should hash calls payload', () => { - const result = hash(testAddress, testChainId, sampleCalls) - expect(result).toBeInstanceOf(Uint8Array) - expect(Bytes.size(result)).toBe(32) - }) - - it('should hash message payload', () => { - const result = hash(testAddress, testChainId, sampleMessage) - expect(result).toBeInstanceOf(Uint8Array) - expect(Bytes.size(result)).toBe(32) - }) - - it('should hash config update payload', () => { - const result = hash(testAddress, testChainId, sampleConfigUpdate) - expect(result).toBeInstanceOf(Uint8Array) - expect(Bytes.size(result)).toBe(32) - }) - - it('should return digest directly for digest payload', () => { - const result = hash(testAddress, testChainId, sampleDigest) - expect(result).toEqual(Bytes.fromHex(testDigest)) - }) - - it.skip('should hash session implicit authorize payload using attestation', () => { - const result = hash(testAddress, testChainId, sampleSessionImplicitAuthorize) - const expectedHash = Attestation.hash(sampleAttestation) - expect(result).toEqual(expectedHash) - }) - - it('should produce different hashes for different wallets', () => { - const hash1 = hash(testAddress, testChainId, sampleCalls) - const hash2 = hash(testAddress2, testChainId, sampleCalls) - expect(hash1).not.toEqual(hash2) - }) - - it('should produce different hashes for different chain IDs', () => { - const hash1 = hash(testAddress, ChainId.MAINNET, sampleCalls) - const hash2 = hash(testAddress, ChainId.POLYGON, sampleCalls) - expect(hash1).not.toEqual(hash2) - }) - }) - }) - - describe('Typed Data Generation', () => { - describe('toTyped', () => { - it('should generate typed data for calls payload', () => { - const result = toTyped(testAddress, testChainId, sampleCalls) - - expect(result.domain.name).toBe('Sequence Wallet') - expect(result.domain.version).toBe('3') - expect(result.domain.chainId).toBe(Number(testChainId)) - expect(result.domain.verifyingContract).toBe(testAddress) - expect(result.primaryType).toBe('Calls') - expect(result.types.Calls).toBeDefined() - expect(result.types.Call).toBeDefined() - }) - - it('should generate typed data for message payload', () => { - const result = toTyped(testAddress, testChainId, sampleMessage) - - expect(result.primaryType).toBe('Message') - expect(result.types.Message).toBeDefined() - expect(result.message.message).toBe(testMessage) - }) - - it('should generate typed data for config update payload', () => { - const result = toTyped(testAddress, testChainId, sampleConfigUpdate) - - expect(result.primaryType).toBe('ConfigUpdate') - expect(result.types.ConfigUpdate).toBeDefined() - expect(result.message.imageHash).toBe(testImageHash) - }) - - it('should use recovery domain for recovery payload', () => { - const recoveryPayload = toRecovery(sampleCalls) - const result = toTyped(testAddress, testChainId, recoveryPayload) - - expect(result.domain.name).toBe('Sequence Wallet - Recovery Mode') - expect(result.domain.version).toBe('1') - }) - - it('should throw for digest payload', () => { - expect(() => toTyped(testAddress, testChainId, sampleDigest)).toThrow( - 'Digest does not support typed data - Use message instead', - ) - }) - - it('should throw for session implicit authorize payload', () => { - expect(() => toTyped(testAddress, testChainId, sampleSessionImplicitAuthorize)).toThrow( - 'Payload does not support typed data', - ) - }) - - it('should handle calls 4337 payload', () => { - const result = toTyped(testAddress, testChainId, sampleCalls4337) - - expect(result.primaryType).toBe('Message') - expect(result.types.Message).toBeDefined() - }) - - it('should include parent wallets in message', () => { - const parentedPayload: Parented = { - ...sampleCalls, - parentWallets: [testAddress, testAddress2], - } - - const result = toTyped(testAddress, testChainId, parentedPayload) - expect(result.message.wallets).toEqual([testAddress, testAddress2]) - }) - }) - }) - - describe('4337 UserOperation', () => { - describe('to4337UserOperation', () => { - it('should create user operation without signature', () => { - const result = to4337UserOperation(sampleCalls4337, testAddress) - - expect(result.sender).toBe(testAddress) - expect(result.nonce).toBe(encode4337Nonce(sampleCalls4337.space, sampleCalls4337.nonce)) - expect(result.callGasLimit).toBe(sampleCalls4337.callGasLimit) - expect(result.maxFeePerGas).toBe(sampleCalls4337.maxFeePerGas) - expect(result.maxPriorityFeePerGas).toBe(sampleCalls4337.maxPriorityFeePerGas) - expect(result.preVerificationGas).toBe(sampleCalls4337.preVerificationGas) - expect(result.verificationGasLimit).toBe(sampleCalls4337.verificationGasLimit) - expect(result.signature).toBeUndefined() - }) - - it('should create user operation with signature', () => { - const signature = '0x1234567890abcdef' - const result = to4337UserOperation(sampleCalls4337, testAddress, signature) - expect(result.signature).toBe(signature) - }) - - it('should handle optional fields', () => { - const payloadWithOptionals: Calls4337_07 = { - ...sampleCalls4337, - factory: testAddress2, - factoryData: '0xfactory', - paymaster: testAddress, - paymasterData: '0xpaymaster', - paymasterPostOpGasLimit: 50000n, - paymasterVerificationGasLimit: 30000n, - } - - const result = to4337UserOperation(payloadWithOptionals, testAddress) - expect(result.factory).toBe(testAddress2) - expect(result.factoryData).toBe('0xfactory') - expect(result.paymaster).toBe(testAddress) - expect(result.paymasterData).toBe('0xpaymaster') - expect(result.paymasterPostOpGasLimit).toBe(50000n) - expect(result.paymasterVerificationGasLimit).toBe(30000n) - }) - }) - - describe('to4337Message', () => { - it('should create 4337 message', () => { - const result = to4337Message(sampleCalls4337, testAddress, testChainId) - - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(Hex.size(result)).toBeGreaterThan(0) - }) - - it('should be deterministic', () => { - const result1 = to4337Message(sampleCalls4337, testAddress, testChainId) - const result2 = to4337Message(sampleCalls4337, testAddress, testChainId) - expect(result1).toBe(result2) - }) - - it('should produce different results for different inputs', () => { - const result1 = to4337Message(sampleCalls4337, testAddress, testChainId) - const result2 = to4337Message(sampleCalls4337, testAddress2, testChainId) - const result3 = to4337Message(sampleCalls4337, testAddress, ChainId.POLYGON) - - expect(result1).not.toBe(result2) - expect(result1).not.toBe(result3) - expect(result2).not.toBe(result3) - }) - }) - }) - - describe('Sapient Encoding', () => { - describe('encodeSapient', () => { - it('should encode calls payload', () => { - const result = encodeSapient(testChainId, sampleCalls) - - expect(result.kind).toBe(0) - expect(result.noChainId).toBe(false) - expect(result.calls).toHaveLength(1) - expect(result.calls[0]).toEqual({ - ...sampleCall, - behaviorOnError: BigInt(encodeBehaviorOnError(sampleCall.behaviorOnError)), - }) - expect(result.space).toBe(sampleCalls.space) - expect(result.nonce).toBe(sampleCalls.nonce) - }) - - it('should encode message payload', () => { - const result = encodeSapient(testChainId, sampleMessage) - - expect(result.kind).toBe(1) - expect(result.message).toBe(testMessage) - }) - - it('should encode config update payload', () => { - const result = encodeSapient(testChainId, sampleConfigUpdate) - - expect(result.kind).toBe(2) - expect(result.imageHash).toBe(testImageHash) - }) - - it('should encode digest payload', () => { - const result = encodeSapient(testChainId, sampleDigest) - - expect(result.kind).toBe(3) - expect(result.digest).toBe(testDigest) - }) - - it('should handle zero chain ID', () => { - const result = encodeSapient(0, sampleCalls) - expect(result.noChainId).toBe(true) - }) - - it('should include parent wallets', () => { - const parentedPayload: Parented = { - ...sampleCalls, - parentWallets: [testAddress, testAddress2], - } - - const result = encodeSapient(testChainId, parentedPayload) - expect(result.parentWallets).toEqual([testAddress, testAddress2]) - }) - }) - }) - - describe('ABI Format Conversion', () => { - describe('toAbiFormat', () => { - it('should convert calls payload to ABI format', () => { - const result = toAbiFormat(sampleCalls) - - expect(result.kind).toBe(KIND_TRANSACTIONS) - expect(result.noChainId).toBe(false) - expect(result.calls).toHaveLength(1) - expect(result.calls[0].behaviorOnError).toBe(BigInt(encodeBehaviorOnError(sampleCall.behaviorOnError))) - expect(result.space).toBe(sampleCalls.space) - expect(result.nonce).toBe(sampleCalls.nonce) - }) - - it('should convert message payload to ABI format', () => { - const result = toAbiFormat(sampleMessage) - - expect(result.kind).toBe(KIND_MESSAGE) - expect(result.message).toBe(testMessage) - }) - - it('should convert config update payload to ABI format', () => { - const result = toAbiFormat(sampleConfigUpdate) - - expect(result.kind).toBe(KIND_CONFIG_UPDATE) - expect(result.imageHash).toBe(testImageHash) - }) - - it('should convert digest payload to ABI format', () => { - const result = toAbiFormat(sampleDigest) - - expect(result.kind).toBe(KIND_DIGEST) - expect(result.digest).toBe(testDigest) - }) - - it('should throw for invalid payload type', () => { - const invalidPayload = { type: 'invalid' } as any - expect(() => toAbiFormat(invalidPayload)).toThrow('Invalid payload type') - }) - }) - - describe('fromAbiFormat', () => { - it('should convert calls from ABI format', () => { - const abiFormat: SolidityDecoded = { - kind: KIND_TRANSACTIONS, - noChainId: false, - calls: [ - { - to: sampleCall.to, - value: sampleCall.value, - data: sampleCall.data, - gasLimit: sampleCall.gasLimit, - delegateCall: sampleCall.delegateCall, - onlyFallback: sampleCall.onlyFallback, - behaviorOnError: BigInt(encodeBehaviorOnError(sampleCall.behaviorOnError)), - }, - ], - space: sampleCalls.space, - nonce: sampleCalls.nonce, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: [testAddress, testAddress2], - } - - const result = fromAbiFormat(abiFormat) - - expect(result.type).toBe('call') - expect((result as Calls).calls).toHaveLength(1) - expect((result as Calls).calls[0]).toEqual(sampleCall) - expect(result.parentWallets).toEqual([testAddress, testAddress2]) - }) - - it('should convert message from ABI format', () => { - const abiFormat: SolidityDecoded = { - kind: KIND_MESSAGE, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: testMessage, - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: [], - } - - const result = fromAbiFormat(abiFormat) - - expect(result.type).toBe('message') - expect((result as Message).message).toBe(testMessage) - }) - - it('should convert config update from ABI format', () => { - const abiFormat: SolidityDecoded = { - kind: KIND_CONFIG_UPDATE, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: testImageHash, - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: [], - } - - const result = fromAbiFormat(abiFormat) - - expect(result.type).toBe('config-update') - expect((result as ConfigUpdate).imageHash).toBe(testImageHash) - }) - - it('should convert digest from ABI format', () => { - const abiFormat: SolidityDecoded = { - kind: KIND_DIGEST, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: testDigest, - parentWallets: [], - } - - const result = fromAbiFormat(abiFormat) - - expect(result.type).toBe('digest') - expect((result as Digest).digest).toBe(testDigest) - }) - - it('should throw for invalid kind', () => { - const invalidAbi: SolidityDecoded = { - kind: 999, - noChainId: false, - calls: [], - space: 0n, - nonce: 0n, - message: '0x', - imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - digest: '0x0000000000000000000000000000000000000000000000000000000000000000', - parentWallets: [], - } - - expect(() => fromAbiFormat(invalidAbi)).toThrow('Not implemented') - }) - }) - }) - - describe('Payload Encoding and Decoding', () => { - describe('encode', () => { - it('should encode simple calls payload', () => { - const result = encode(sampleCalls) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - }) - - it('should encode calls with zero space', () => { - const callsWithZeroSpace: Calls = { - ...sampleCalls, - space: 0n, - } - const result = encode(callsWithZeroSpace) - expect(result).toBeInstanceOf(Uint8Array) - - // First byte should have space zero flag set (bit 0) - expect(result[0] & 0x01).toBe(0x01) - }) - - it('should encode calls with non-zero space', () => { - const callsWithSpace: Calls = { - ...sampleCalls, - space: 123n, - } - const result = encode(callsWithSpace) - expect(result).toBeInstanceOf(Uint8Array) - - // First byte should not have space zero flag set (bit 0) - expect(result[0] & 0x01).toBe(0x00) - }) - - it('should encode single call flag correctly', () => { - const result = encode(sampleCalls) - // Should have single call flag set (bit 4) - expect(result[0] & 0x10).toBe(0x10) - }) - - it('should encode multiple calls correctly', () => { - const multiCallPayload: Calls = { - ...sampleCalls, - calls: [sampleCall, { ...sampleCall, to: testAddress2 }], - } - const result = encode(multiCallPayload) - // Should not have single call flag set (bit 4) - expect(result[0] & 0x10).toBe(0x00) - }) - - it('should handle large nonce values', () => { - const largeNoncePayload: Calls = { - ...sampleCalls, - nonce: 0xffffffffffffn, // 6 bytes - } - const result = encode(largeNoncePayload) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should throw for nonce too large', () => { - const veryLargeNoncePayload: Calls = { - ...sampleCalls, - nonce: (1n << 120n) - 1n, // 15 bytes, maximum allowed - } - expect(() => encode(veryLargeNoncePayload)).not.toThrow() - - const tooLargeNoncePayload: Calls = { - ...sampleCalls, - nonce: 1n << 120n, // 16 bytes, should throw - } - expect(() => encode(tooLargeNoncePayload)).toThrow('Nonce is too large') - }) - - it('should handle call with self address', () => { - const selfAddress = testAddress - const result = encode(sampleCalls, selfAddress) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle call with value', () => { - const callWithValue: Call = { - ...sampleCall, - value: 1000n, - } - const payloadWithValue: Calls = { - ...sampleCalls, - calls: [callWithValue], - } - const result = encode(payloadWithValue) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle call with zero value', () => { - const callWithZeroValue: Call = { - ...sampleCall, - value: 0n, - } - const payloadWithZeroValue: Calls = { - ...sampleCalls, - calls: [callWithZeroValue], - } - const result = encode(payloadWithZeroValue) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle call with gas limit', () => { - const callWithGas: Call = { - ...sampleCall, - gasLimit: 21000n, - } - const payloadWithGas: Calls = { - ...sampleCalls, - calls: [callWithGas], - } - const result = encode(payloadWithGas) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle call with delegate call flag', () => { - const delegateCall: Call = { - ...sampleCall, - delegateCall: true, - } - const payloadWithDelegate: Calls = { - ...sampleCalls, - calls: [delegateCall], - } - const result = encode(payloadWithDelegate) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle call with only fallback flag', () => { - const fallbackCall: Call = { - ...sampleCall, - onlyFallback: true, - } - const payloadWithFallback: Calls = { - ...sampleCalls, - calls: [fallbackCall], - } - const result = encode(payloadWithFallback) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle different behavior on error values', () => { - const behaviors: Call['behaviorOnError'][] = ['ignore', 'revert', 'abort'] - - behaviors.forEach((behavior) => { - const callWithBehavior: Call = { - ...sampleCall, - behaviorOnError: behavior, - } - const payloadWithBehavior: Calls = { - ...sampleCalls, - calls: [callWithBehavior], - } - const result = encode(payloadWithBehavior) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - it('should throw for too many calls', () => { - const tooManyCalls = Array(65536).fill(sampleCall) - const payloadWithTooManyCalls: Calls = { - ...sampleCalls, - calls: tooManyCalls, - } - expect(() => encode(payloadWithTooManyCalls)).toThrow('Too many calls') - }) - - it('should throw for data too large', () => { - const largeData = '0x' + '00'.repeat(0x1000000) // 16MB + 1 byte - const callWithLargeData: Call = { - ...sampleCall, - data: largeData as Hex.Hex, - } - const payloadWithLargeData: Calls = { - ...sampleCalls, - calls: [callWithLargeData], - } - expect(() => encode(payloadWithLargeData)).toThrow('Data too large') - }) - - it('should handle empty call data', () => { - const callWithEmptyData: Call = { - ...sampleCall, - data: '0x', - } - const payloadWithEmptyData: Calls = { - ...sampleCalls, - calls: [callWithEmptyData], - } - const result = encode(payloadWithEmptyData) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - describe('decode', () => { - it('should decode encoded payload correctly', () => { - const encoded = encode(sampleCalls) - const decoded = decode(encoded) - - expect(decoded.type).toBe('call') - expect(decoded.space).toBe(sampleCalls.space) - expect(decoded.nonce).toBe(sampleCalls.nonce) - expect(decoded.calls).toHaveLength(1) - expect(decoded.calls[0]).toEqual(sampleCall) - }) - - it('should handle round-trip encoding/decoding', () => { - const testPayloads: Calls[] = [ - sampleCalls, - { - type: 'call', - space: 123n, - nonce: 456n, - calls: [sampleCall, { ...sampleCall, to: testAddress2 }], - }, - { - type: 'call', - space: 0n, - nonce: 0n, - calls: [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'ignore', - }, - ], - }, - ] - - testPayloads.forEach((payload) => { - const encoded = encode(payload) - const decoded = decode(encoded) - expect(decoded).toEqual(payload) - }) - }) - - it('should decode with self address', () => { - const encoded = encode(sampleCalls, testAddress) - const decoded = decode(encoded, testAddress) - - expect(decoded.calls[0].to).toBe(testAddress) - }) - - it('should throw for invalid packed data', () => { - expect(() => decode(new Uint8Array(0))).toThrow('Invalid packed data: missing globalFlag') - expect(() => decode(new Uint8Array([0x00]))).toThrow() // Missing space data - }) - - it('should throw for missing self address when needed', () => { - // Create encoded data that uses toSelf flag - const callToSelf: Call = { ...sampleCall, to: testAddress } - const payloadToSelf: Calls = { ...sampleCalls, calls: [callToSelf] } - const encoded = encode(payloadToSelf, testAddress) - - expect(() => decode(encoded)).toThrow('Missing "self" address for toSelf call') - }) - - it('should handle various nonce sizes', () => { - const testNonces = [0n, 255n, 65535n, 16777215n, 0xffffffffn] - - testNonces.forEach((nonce) => { - const payload: Calls = { ...sampleCalls, nonce } - const encoded = encode(payload) - const decoded = decode(encoded) - expect(decoded.nonce).toBe(nonce) - }) - }) - - it('should handle behavior on error decoding', () => { - const behaviors: Call['behaviorOnError'][] = ['ignore', 'revert', 'abort'] - - behaviors.forEach((behavior) => { - const call: Call = { ...sampleCall, behaviorOnError: behavior } - const payload: Calls = { ...sampleCalls, calls: [call] } - const encoded = encode(payload) - const decoded = decode(encoded) - expect(decoded.calls[0].behaviorOnError).toBe(behavior) - }) - }) - - it('should handle multiple calls correctly', () => { - const multipleCalls: Call[] = [ - sampleCall, - { ...sampleCall, to: testAddress2, value: 2000n }, - { ...sampleCall, data: '0xabcdef', gasLimit: 50000n }, - ] - const payload: Calls = { ...sampleCalls, calls: multipleCalls } - const encoded = encode(payload) - const decoded = decode(encoded) - - expect(decoded.calls).toHaveLength(3) - expect(decoded.calls[0]).toEqual(multipleCalls[0]) - expect(decoded.calls[1]).toEqual(multipleCalls[1]) - expect(decoded.calls[2]).toEqual(multipleCalls[2]) - }) - }) - }) -}) diff --git a/packages/wallet/primitives/test/permission.test.ts b/packages/wallet/primitives/test/permission.test.ts deleted file mode 100644 index c1a7c56c51..0000000000 --- a/packages/wallet/primitives/test/permission.test.ts +++ /dev/null @@ -1,822 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Address, Bytes } from 'ox' - -import { - ParameterOperation, - ParameterRule, - Permission, - SessionPermissions, - MAX_PERMISSIONS_COUNT, - MAX_RULES_COUNT, - MASK, - encodeSessionPermissions, - encodePermission, - decodeSessionPermissions, - permissionStructAbi, - abiEncodePermission, - sessionPermissionsToJson, - encodeSessionPermissionsForJson, - permissionToJson, - parameterRuleToJson, - sessionPermissionsFromJson, - sessionPermissionsFromParsed, - permissionFromJson, -} from '../src/permission.js' -import { ChainId } from '../src/network.js' - -describe('Permission', () => { - // Test data - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' as Address.Address - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' as Address.Address - const testChainId = ChainId.MAINNET - const testValueLimit = 1000000000000000000n // 1 ETH - const testDeadline = 1893456000n // Jan 1, 2030 - - const sampleParameterRule: ParameterRule = { - cumulative: false, - operation: ParameterOperation.EQUAL, - value: Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), - offset: 4n, // After function selector - mask: MASK.UINT256, - } - - const sampleParameterRuleCumulative: ParameterRule = { - cumulative: true, - operation: ParameterOperation.LESS_THAN_OR_EQUAL, - value: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000de0b6b3a7640000'), // 1 ETH - offset: 36n, // Value parameter in transfer - mask: MASK.UINT256, - } - - const samplePermission: Permission = { - target: testAddress, - rules: [sampleParameterRule], - } - - const complexPermission: Permission = { - target: testAddress2, - rules: [sampleParameterRule, sampleParameterRuleCumulative], - } - - const sampleSessionPermissions: SessionPermissions = { - signer: testAddress, - chainId: testChainId, - valueLimit: testValueLimit, - deadline: testDeadline, - permissions: [samplePermission], - } - - const complexSessionPermissions: SessionPermissions = { - signer: testAddress2, - chainId: ChainId.POLYGON, // Polygon - valueLimit: 5000000000000000000n, // 5 ETH - deadline: testDeadline, - permissions: [samplePermission, complexPermission], - } - - describe('Constants', () => { - it('should have correct max counts', () => { - expect(MAX_PERMISSIONS_COUNT).toBe(127) // 2^7 - 1 - expect(MAX_RULES_COUNT).toBe(255) // 2^8 - 1 - }) - }) - - describe('ParameterOperation enum', () => { - it('should have correct enum values', () => { - expect(ParameterOperation.EQUAL).toBe(0) - expect(ParameterOperation.NOT_EQUAL).toBe(1) - expect(ParameterOperation.GREATER_THAN_OR_EQUAL).toBe(2) - expect(ParameterOperation.LESS_THAN_OR_EQUAL).toBe(3) - }) - }) - - describe('MASK constants', () => { - it('should have correct selector mask', () => { - expect(MASK.SELECTOR).toHaveLength(32) - // Should be right-padded for selector - expect(Bytes.toHex(MASK.SELECTOR).startsWith('0xffffffff')).toBe(true) - expect(Bytes.toHex(MASK.SELECTOR).endsWith('00000000000000000000000000000000')).toBe(true) - }) - - it('should have correct address mask', () => { - expect(MASK.ADDRESS).toHaveLength(32) - // Should be left-padded for address (20 bytes) - expect(Bytes.toHex(MASK.ADDRESS)).toBe('0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff') - }) - - it('should have correct bool mask', () => { - expect(MASK.BOOL).toHaveLength(32) - expect(Bytes.toHex(MASK.BOOL)).toBe('0x0000000000000000000000000000000000000000000000000000000000000001') - }) - - it('should have correct bytes masks', () => { - expect(MASK.BYTES1).toHaveLength(32) - expect(MASK.BYTES2).toHaveLength(32) - expect(MASK.BYTES4).toHaveLength(32) - expect(MASK.BYTES8).toHaveLength(32) - expect(MASK.BYTES16).toHaveLength(32) - expect(MASK.BYTES32).toHaveLength(32) - - // BYTES32 should be all 0xff - expect(Bytes.toHex(MASK.BYTES32)).toBe('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') - }) - - it('should have correct int masks', () => { - expect(MASK.INT8).toHaveLength(32) - expect(MASK.INT16).toHaveLength(32) - expect(MASK.INT32).toHaveLength(32) - expect(MASK.INT64).toHaveLength(32) - expect(MASK.INT128).toHaveLength(32) - expect(MASK.INT256).toHaveLength(32) - - // INT256 should be all 0xff - expect(Bytes.toHex(MASK.INT256)).toBe('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') - }) - - it('should have correct uint masks', () => { - expect(MASK.UINT8).toHaveLength(32) - expect(MASK.UINT16).toHaveLength(32) - expect(MASK.UINT32).toHaveLength(32) - expect(MASK.UINT64).toHaveLength(32) - expect(MASK.UINT128).toHaveLength(32) - expect(MASK.UINT256).toHaveLength(32) - - // UINT256 should be all 0xff - expect(Bytes.toHex(MASK.UINT256)).toBe('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') - }) - - it('should have increasing mask sizes', () => { - const masks = [MASK.BYTES1, MASK.BYTES2, MASK.BYTES4, MASK.BYTES8, MASK.BYTES16, MASK.BYTES32] - const expectedLengths = [1, 2, 4, 8, 16, 32] - - masks.forEach((mask, index) => { - // Count consecutive 0xff bytes from the right (since they're left-padded) - const hex = Bytes.toHex(mask).slice(2) // Remove '0x' - let nonZeroBytes = 0 - for (let i = hex.length - 2; i >= 0; i -= 2) { - if (hex.slice(i, i + 2) === 'ff') { - nonZeroBytes++ - } else { - break - } - } - expect(nonZeroBytes).toBe(expectedLengths[index]) - }) - }) - }) - - describe('Permission Encoding', () => { - describe('encodePermission', () => { - it('should encode simple permission correctly', () => { - const result = encodePermission(samplePermission) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - // Should start with target address (20 bytes) - expect(Bytes.toHex(result.slice(0, 20))).toBe(testAddress.toLowerCase()) - - // Followed by rules count (1 byte) - expect(result[20]).toBe(1) - }) - - it('should encode complex permission with multiple rules', () => { - const result = encodePermission(complexPermission) - expect(result).toBeInstanceOf(Uint8Array) - - // Should start with target address - expect(Bytes.toHex(result.slice(0, 20))).toBe(testAddress2.toLowerCase()) - - // Should have 2 rules - expect(result[20]).toBe(2) - }) - - it('should throw for too many rules', () => { - const tooManyRules = Array(MAX_RULES_COUNT + 1).fill(sampleParameterRule) - const invalidPermission: Permission = { - target: testAddress, - rules: tooManyRules, - } - - expect(() => encodePermission(invalidPermission)).toThrow('Too many rules') - }) - - it('should handle permission with no rules', () => { - const emptyPermission: Permission = { - target: testAddress, - rules: [], - } - - const result = encodePermission(emptyPermission) - expect(result[20]).toBe(0) // 0 rules - }) - - it('should handle different parameter operations', () => { - const operations = [ - ParameterOperation.EQUAL, - ParameterOperation.NOT_EQUAL, - ParameterOperation.GREATER_THAN_OR_EQUAL, - ParameterOperation.LESS_THAN_OR_EQUAL, - ] - - operations.forEach((operation) => { - const rule: ParameterRule = { - ...sampleParameterRule, - operation, - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - - const result = encodePermission(permission) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - it('should handle cumulative vs non-cumulative rules', () => { - const nonCumulativeRule: ParameterRule = { - ...sampleParameterRule, - cumulative: false, - } - const cumulativeRule: ParameterRule = { - ...sampleParameterRule, - cumulative: true, - } - - const permission: Permission = { - target: testAddress, - rules: [nonCumulativeRule, cumulativeRule], - } - - const result = encodePermission(permission) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - describe('encodeSessionPermissions', () => { - it('should encode simple session permissions correctly', () => { - const result = encodeSessionPermissions(sampleSessionPermissions) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - // Check structure: signer (20) + chainId (32) + valueLimit (32) + deadline (8) + count (1) + permissions - expect(result.length).toBeGreaterThan(93) // Minimum size without permissions - - // Should start with signer address - expect(Bytes.toHex(result.slice(0, 20))).toBe(testAddress.toLowerCase()) - - // Should have 1 permission - expect(result[92]).toBe(1) - }) - - it('should encode complex session permissions', () => { - const result = encodeSessionPermissions(complexSessionPermissions) - expect(result).toBeInstanceOf(Uint8Array) - - // Should start with signer address - expect(Bytes.toHex(result.slice(0, 20))).toBe(testAddress2.toLowerCase()) - - // Should have 2 permissions - expect(result[92]).toBe(2) - }) - - it('should throw for too many permissions', () => { - const tooManyPermissions = Array(MAX_PERMISSIONS_COUNT + 1).fill(samplePermission) - const invalidSessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - permissions: tooManyPermissions as [Permission, ...Permission[]], - } - - expect(() => encodeSessionPermissions(invalidSessionPermissions)).toThrow('Too many permissions') - }) - - it('should handle different chain IDs', () => { - const chainIds = [ChainId.MAINNET, ChainId.POLYGON, ChainId.ARBITRUM, ChainId.OPTIMISM] - - chainIds.forEach((chainId) => { - const sessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - chainId, - } - - const result = encodeSessionPermissions(sessionPermissions) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - it('should handle different value limits', () => { - const valueLimits = [0n, 1000000000000000000n, 10000000000000000000n] // 0, 1 ETH, 10 ETH - - valueLimits.forEach((valueLimit) => { - const sessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - valueLimit, - } - - const result = encodeSessionPermissions(sessionPermissions) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - it('should handle different deadlines', () => { - const deadlines = [0n, 1672531200n, 1893456000n] // Epoch, 2023, 2030 - - deadlines.forEach((deadline) => { - const sessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - deadline, - } - - const result = encodeSessionPermissions(sessionPermissions) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - }) - }) - - describe('Permission Decoding', () => { - describe('decodeSessionPermissions', () => { - it('should decode simple session permissions correctly', () => { - const encoded = encodeSessionPermissions(sampleSessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.signer).toBe(sampleSessionPermissions.signer) - expect(decoded.chainId).toBe(sampleSessionPermissions.chainId) - expect(decoded.valueLimit).toBe(sampleSessionPermissions.valueLimit) - expect(decoded.deadline).toBe(sampleSessionPermissions.deadline) - expect(decoded.permissions).toHaveLength(1) - expect(decoded.permissions[0].target).toBe(samplePermission.target) - expect(decoded.permissions[0].rules).toHaveLength(1) - }) - - it('should decode complex session permissions correctly', () => { - const encoded = encodeSessionPermissions(complexSessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.signer).toBe(complexSessionPermissions.signer) - expect(decoded.chainId).toBe(complexSessionPermissions.chainId) - expect(decoded.valueLimit).toBe(complexSessionPermissions.valueLimit) - expect(decoded.deadline).toBe(complexSessionPermissions.deadline) - expect(decoded.permissions).toHaveLength(2) - }) - - it('should handle round-trip encoding/decoding', () => { - const testCases = [sampleSessionPermissions, complexSessionPermissions] - - testCases.forEach((original) => { - const encoded = encodeSessionPermissions(original) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.signer).toBe(original.signer) - expect(decoded.chainId).toBe(original.chainId) - expect(decoded.valueLimit).toBe(original.valueLimit) - expect(decoded.deadline).toBe(original.deadline) - expect(decoded.permissions).toHaveLength(original.permissions.length) - - decoded.permissions.forEach((permission, i) => { - expect(permission.target).toBe(original.permissions[i].target) - expect(permission.rules).toHaveLength(original.permissions[i].rules.length) - - permission.rules.forEach((rule, j) => { - expect(rule.cumulative).toBe(original.permissions[i].rules[j].cumulative) - expect(rule.operation).toBe(original.permissions[i].rules[j].operation) - expect(Bytes.isEqual(rule.value, original.permissions[i].rules[j].value)).toBe(true) - expect(rule.offset).toBe(original.permissions[i].rules[j].offset) - expect(Bytes.isEqual(rule.mask, original.permissions[i].rules[j].mask)).toBe(true) - }) - }) - }) - }) - - it('should throw for empty permissions', () => { - // Create invalid encoded data with 0 permissions - const invalidEncoded = Bytes.concat( - Bytes.padLeft(Bytes.fromHex(testAddress), 20), - Bytes.padLeft(Bytes.fromNumber(testChainId), 32), - Bytes.padLeft(Bytes.fromNumber(testValueLimit), 32), - Bytes.padLeft(Bytes.fromNumber(testDeadline, { size: 8 }), 8), - Bytes.fromNumber(0, { size: 1 }), // 0 permissions - ) - - expect(() => decodeSessionPermissions(invalidEncoded)).toThrow('No permissions') - }) - - it('should handle various parameter operations correctly', () => { - const operations = [ - ParameterOperation.EQUAL, - ParameterOperation.NOT_EQUAL, - ParameterOperation.GREATER_THAN_OR_EQUAL, - ParameterOperation.LESS_THAN_OR_EQUAL, - ] - - operations.forEach((operation) => { - const rule: ParameterRule = { - ...sampleParameterRule, - operation, - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - const sessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - permissions: [permission], - } - - const encoded = encodeSessionPermissions(sessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.permissions[0].rules[0].operation).toBe(operation) - }) - }) - - it('should handle cumulative flags correctly', () => { - const cumulativeValues = [true, false] - - cumulativeValues.forEach((cumulative) => { - const rule: ParameterRule = { - ...sampleParameterRule, - cumulative, - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - const sessionPermissions: SessionPermissions = { - ...sampleSessionPermissions, - permissions: [permission], - } - - const encoded = encodeSessionPermissions(sessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.permissions[0].rules[0].cumulative).toBe(cumulative) - }) - }) - }) - }) - - describe('ABI Encoding', () => { - describe('permissionStructAbi', () => { - it('should have correct ABI structure', () => { - expect(permissionStructAbi.type).toBe('tuple') - expect(permissionStructAbi.components).toHaveLength(2) - expect(permissionStructAbi.components[0].name).toBe('target') - expect(permissionStructAbi.components[0].type).toBe('address') - expect(permissionStructAbi.components[1].name).toBe('rules') - expect(permissionStructAbi.components[1].type).toBe('tuple[]') - }) - - it('should have correct rule ABI structure', () => { - const rulesComponent = permissionStructAbi.components[1] - expect(rulesComponent.components).toHaveLength(5) - - const expectedFields = [ - { name: 'cumulative', type: 'bool' }, - { name: 'operation', type: 'uint8' }, - { name: 'value', type: 'bytes32' }, - { name: 'offset', type: 'uint256' }, - { name: 'mask', type: 'bytes32' }, - ] - - expectedFields.forEach((expected, i) => { - expect(rulesComponent.components[i].name).toBe(expected.name) - expect(rulesComponent.components[i].type).toBe(expected.type) - }) - }) - }) - - describe('abiEncodePermission', () => { - it('should encode simple permission', () => { - const result = abiEncodePermission(samplePermission) - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(result.length).toBeGreaterThan(2) // More than just '0x' - }) - - it('should encode complex permission', () => { - const result = abiEncodePermission(complexPermission) - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - expect(result.length).toBeGreaterThan(2) - }) - - it('should handle permission with no rules', () => { - const emptyPermission: Permission = { - target: testAddress, - rules: [], - } - - const result = abiEncodePermission(emptyPermission) - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - }) - - it('should be deterministic', () => { - const result1 = abiEncodePermission(samplePermission) - const result2 = abiEncodePermission(samplePermission) - expect(result1).toBe(result2) - }) - - it('should produce different results for different permissions', () => { - const result1 = abiEncodePermission(samplePermission) - const result2 = abiEncodePermission(complexPermission) - expect(result1).not.toBe(result2) - }) - }) - }) - - describe('JSON Serialization', () => { - describe('sessionPermissionsToJson', () => { - it('should serialize simple session permissions', () => { - const result = sessionPermissionsToJson(sampleSessionPermissions) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.signer).toBe(sampleSessionPermissions.signer) - expect(parsed.chainId).toBe(sampleSessionPermissions.chainId.toString()) - expect(parsed.valueLimit).toBe(sampleSessionPermissions.valueLimit.toString()) - expect(parsed.deadline).toBe(sampleSessionPermissions.deadline.toString()) - expect(parsed.permissions).toHaveLength(1) - }) - - it('should serialize complex session permissions', () => { - const result = sessionPermissionsToJson(complexSessionPermissions) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.signer).toBe(complexSessionPermissions.signer) - expect(parsed.permissions).toHaveLength(2) - }) - }) - - describe('encodeSessionPermissionsForJson', () => { - it('should create JSON-safe object', () => { - const result = encodeSessionPermissionsForJson(sampleSessionPermissions) - expect(typeof result).toBe('object') - expect(typeof result.signer).toBe('string') - expect(typeof result.chainId).toBe('string') - expect(typeof result.valueLimit).toBe('string') - expect(typeof result.deadline).toBe('string') - expect(Array.isArray(result.permissions)).toBe(true) - }) - }) - - describe('permissionToJson', () => { - it('should serialize permission', () => { - const result = permissionToJson(samplePermission) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.target).toBe(samplePermission.target) - expect(parsed.rules).toHaveLength(1) - }) - - it('should handle complex permission', () => { - const result = permissionToJson(complexPermission) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.target).toBe(complexPermission.target) - expect(parsed.rules).toHaveLength(2) - }) - }) - - describe('parameterRuleToJson', () => { - it('should serialize parameter rule', () => { - const result = parameterRuleToJson(sampleParameterRule) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(typeof parsed.cumulative).toBe('boolean') - expect(typeof parsed.operation).toBe('number') - expect(typeof parsed.value).toBe('string') - expect(typeof parsed.offset).toBe('string') - expect(typeof parsed.mask).toBe('string') - }) - - it('should handle cumulative rule', () => { - const result = parameterRuleToJson(sampleParameterRuleCumulative) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.cumulative).toBe(true) - expect(parsed.operation).toBe(ParameterOperation.LESS_THAN_OR_EQUAL) - }) - }) - }) - - describe('JSON Deserialization', () => { - describe('sessionPermissionsFromJson', () => { - it('should deserialize simple session permissions', () => { - const json = sessionPermissionsToJson(sampleSessionPermissions) - const result = sessionPermissionsFromJson(json) - - expect(result.signer).toBe(sampleSessionPermissions.signer) - expect(result.chainId).toBe(sampleSessionPermissions.chainId) - expect(result.valueLimit).toBe(sampleSessionPermissions.valueLimit) - expect(result.deadline).toBe(sampleSessionPermissions.deadline) - expect(result.permissions).toHaveLength(1) - }) - - it('should handle round-trip JSON serialization', () => { - const testCases = [sampleSessionPermissions, complexSessionPermissions] - - testCases.forEach((original) => { - const json = sessionPermissionsToJson(original) - const result = sessionPermissionsFromJson(json) - - expect(result.signer).toBe(original.signer) - expect(result.chainId).toBe(original.chainId) - expect(result.valueLimit).toBe(original.valueLimit) - expect(result.deadline).toBe(original.deadline) - expect(result.permissions).toHaveLength(original.permissions.length) - }) - }) - }) - - describe('sessionPermissionsFromParsed', () => { - it('should handle parsed JSON object', () => { - const encoded = encodeSessionPermissionsForJson(sampleSessionPermissions) - const result = sessionPermissionsFromParsed(encoded) - - expect(result.signer).toBe(sampleSessionPermissions.signer) - expect(result.chainId).toBe(sampleSessionPermissions.chainId) - expect(result.valueLimit).toBe(sampleSessionPermissions.valueLimit) - expect(result.deadline).toBe(sampleSessionPermissions.deadline) - }) - }) - - describe('permissionFromJson', () => { - it('should deserialize permission', () => { - const json = permissionToJson(samplePermission) - const result = permissionFromJson(json) - - expect(result.target).toBe(samplePermission.target) - expect(result.rules).toHaveLength(1) - expect(result.rules[0].cumulative).toBe(sampleParameterRule.cumulative) - expect(result.rules[0].operation).toBe(sampleParameterRule.operation) - expect(result.rules[0].offset).toBe(sampleParameterRule.offset) - }) - - it('should handle round-trip permission serialization', () => { - const testCases = [samplePermission, complexPermission] - - testCases.forEach((original) => { - const json = permissionToJson(original) - const result = permissionFromJson(json) - - expect(result.target).toBe(original.target) - expect(result.rules).toHaveLength(original.rules.length) - - result.rules.forEach((rule, i) => { - expect(rule.cumulative).toBe(original.rules[i].cumulative) - expect(rule.operation).toBe(original.rules[i].operation) - expect(rule.offset).toBe(original.rules[i].offset) - expect(Bytes.isEqual(rule.value, original.rules[i].value)).toBe(true) - expect(Bytes.isEqual(rule.mask, original.rules[i].mask)).toBe(true) - }) - }) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle zero values correctly', () => { - const zeroValueSessionPermissions: SessionPermissions = { - signer: testAddress, - chainId: 0, - valueLimit: 0n, - deadline: 0n, - permissions: [samplePermission], - } - - const encoded = encodeSessionPermissions(zeroValueSessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.chainId).toBe(0) - expect(decoded.valueLimit).toBe(0n) - expect(decoded.deadline).toBe(0n) - }) - - it('should handle maximum values correctly', () => { - const maxValueSessionPermissions: SessionPermissions = { - signer: testAddress, - chainId: Number.MAX_SAFE_INTEGER, - valueLimit: 2n ** 256n - 1n, - deadline: 2n ** 64n - 1n, - permissions: [samplePermission], - } - - const encoded = encodeSessionPermissions(maxValueSessionPermissions) - const decoded = decodeSessionPermissions(encoded) - - expect(decoded.chainId).toBe(Number.MAX_SAFE_INTEGER) - expect(decoded.valueLimit).toBe(2n ** 256n - 1n) - expect(decoded.deadline).toBe(2n ** 64n - 1n) - }) - - it('should handle different mask types', () => { - const maskTypes = [MASK.SELECTOR, MASK.ADDRESS, MASK.BOOL, MASK.BYTES32, MASK.UINT256] - - maskTypes.forEach((mask) => { - const rule: ParameterRule = { - ...sampleParameterRule, - mask, - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - - const encoded = encodePermission(permission) - expect(encoded).toBeInstanceOf(Uint8Array) - }) - }) - - it('should handle large offset values', () => { - const largeOffsets = [0n, 4n, 36n, 100n, 1000n, 10000n] - - largeOffsets.forEach((offset) => { - const rule: ParameterRule = { - ...sampleParameterRule, - offset, - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - - const encoded = encodePermission(permission) - expect(encoded).toBeInstanceOf(Uint8Array) - }) - }) - - it('should handle different value sizes', () => { - const values = [ - Bytes.fromHex('0x00'), - Bytes.fromHex('0x01'), - Bytes.fromHex('0xffffffff'), - Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), - ] - - values.forEach((value) => { - const rule: ParameterRule = { - ...sampleParameterRule, - value: Bytes.padLeft(value, 32), // Ensure 32 bytes - } - const permission: Permission = { - target: testAddress, - rules: [rule], - } - - const encoded = encodePermission(permission) - expect(encoded).toBeInstanceOf(Uint8Array) - }) - }) - }) - - describe('Integration Tests', () => { - it('should handle complete workflow: create -> encode -> decode -> JSON -> decode', () => { - // Create complex session permissions - const original = complexSessionPermissions - - // Binary encoding/decoding - const binaryEncoded = encodeSessionPermissions(original) - const binaryDecoded = decodeSessionPermissions(binaryEncoded) - - // JSON serialization/deserialization - const jsonString = sessionPermissionsToJson(binaryDecoded) - const jsonDecoded = sessionPermissionsFromJson(jsonString) - - // ABI encoding (for individual permissions) - const abiEncoded = abiEncodePermission(jsonDecoded.permissions[0]) - - // Verify all data remains consistent - expect(jsonDecoded.signer).toBe(original.signer) - expect(jsonDecoded.chainId).toBe(original.chainId) - expect(jsonDecoded.valueLimit).toBe(original.valueLimit) - expect(jsonDecoded.deadline).toBe(original.deadline) - expect(jsonDecoded.permissions).toHaveLength(original.permissions.length) - expect(typeof abiEncoded).toBe('string') - expect(abiEncoded.startsWith('0x')).toBe(true) - }) - - it('should maintain precision for large numbers', () => { - const largeNumbers: SessionPermissions = { - signer: testAddress, - chainId: Number.MAX_SAFE_INTEGER, - valueLimit: 123456789012345678901234567890n, - deadline: 18446744073709551615n, // Max uint64 - permissions: [samplePermission], - } - - const json = sessionPermissionsToJson(largeNumbers) - const decoded = sessionPermissionsFromJson(json) - - expect(decoded.chainId).toBe(largeNumbers.chainId) - expect(decoded.valueLimit).toBe(largeNumbers.valueLimit) - expect(decoded.deadline).toBe(largeNumbers.deadline) - }) - }) -}) diff --git a/packages/wallet/primitives/test/precondition.test.ts b/packages/wallet/primitives/test/precondition.test.ts deleted file mode 100644 index 84f919d620..0000000000 --- a/packages/wallet/primitives/test/precondition.test.ts +++ /dev/null @@ -1,693 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { - NativeBalancePrecondition, - Erc20BalancePrecondition, - Erc20ApprovalPrecondition, - Erc721OwnershipPrecondition, - Erc721ApprovalPrecondition, - Erc1155BalancePrecondition, - Erc1155ApprovalPrecondition, - AnyPrecondition, - isValidPreconditionType, - createPrecondition, - createIntentPrecondition, -} from '../src/precondition.js' -import { ChainId } from '../src/network.js' - -describe('Precondition', () => { - // Test data - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' - const testTokenAddress = '0xa0b86a33e6f8b5f56e64c9e1a1b8c6a9cc4b9a9e' - const testTokenId = 123n - const testMinAmount = 1000000000000000000n // 1 ETH - const testMaxAmount = 10000000000000000000n // 10 ETH - const testChainId = ChainId.MAINNET - - // Sample preconditions for each type - const sampleNativeBalance: NativeBalancePrecondition = { - type: 'native-balance', - address: testAddress, - min: testMinAmount, - max: testMaxAmount, - } - - const sampleErc20Balance: Erc20BalancePrecondition = { - type: 'erc20-balance', - address: testAddress, - token: testTokenAddress, - min: testMinAmount, - max: testMaxAmount, - } - - const sampleErc20Approval: Erc20ApprovalPrecondition = { - type: 'erc20-approval', - address: testAddress, - token: testTokenAddress, - operator: testAddress2, - min: testMinAmount, - } - - const sampleErc721Ownership: Erc721OwnershipPrecondition = { - type: 'erc721-ownership', - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - owned: true, - } - - const sampleErc721Approval: Erc721ApprovalPrecondition = { - type: 'erc721-approval', - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - operator: testAddress2, - } - - const sampleErc1155Balance: Erc1155BalancePrecondition = { - type: 'erc1155-balance', - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - min: 5n, - max: 100n, - } - - const sampleErc1155Approval: Erc1155ApprovalPrecondition = { - type: 'erc1155-approval', - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - operator: testAddress2, - min: 10n, - } - - describe('Type Validation', () => { - describe('isValidPreconditionType', () => { - it('should return true for valid precondition types', () => { - const validTypes = [ - 'native-balance', - 'erc20-balance', - 'erc20-approval', - 'erc721-ownership', - 'erc721-approval', - 'erc1155-balance', - 'erc1155-approval', - ] - - validTypes.forEach((type) => { - expect(isValidPreconditionType(type)).toBe(true) - }) - }) - - it('should return false for invalid precondition types', () => { - const invalidTypes = [ - 'invalid-type', - 'erc-20-balance', // Wrong format - 'native_balance', // Wrong separator - 'ERC20-BALANCE', // Wrong case - 'nft-ownership', // Non-existent type - '', // Empty string - 'erc721', // Incomplete - 'approval', // Too generic - ] - - invalidTypes.forEach((type) => { - expect(isValidPreconditionType(type)).toBe(false) - }) - }) - - it('should handle edge cases', () => { - expect(isValidPreconditionType(' native-balance ')).toBe(false) // With spaces - expect(isValidPreconditionType('native-balance\n')).toBe(false) // With newline - expect(isValidPreconditionType('native-balance\t')).toBe(false) // With tab - }) - }) - }) - - describe('Precondition Creation', () => { - describe('createPrecondition', () => { - it('should create native balance precondition', () => { - const result = createPrecondition(sampleNativeBalance) - expect(result).toEqual(sampleNativeBalance) - expect(result.type).toBe('native-balance') - expect(result.address).toBe(testAddress) - expect(result.min).toBe(testMinAmount) - expect(result.max).toBe(testMaxAmount) - }) - - it('should create erc20 balance precondition', () => { - const result = createPrecondition(sampleErc20Balance) - expect(result).toEqual(sampleErc20Balance) - expect(result.type).toBe('erc20-balance') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.min).toBe(testMinAmount) - expect(result.max).toBe(testMaxAmount) - }) - - it('should create erc20 approval precondition', () => { - const result = createPrecondition(sampleErc20Approval) - expect(result).toEqual(sampleErc20Approval) - expect(result.type).toBe('erc20-approval') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.operator).toBe(testAddress2) - expect(result.min).toBe(testMinAmount) - }) - - it('should create erc721 ownership precondition', () => { - const result = createPrecondition(sampleErc721Ownership) - expect(result).toEqual(sampleErc721Ownership) - expect(result.type).toBe('erc721-ownership') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.tokenId).toBe(testTokenId) - expect(result.owned).toBe(true) - }) - - it('should create erc721 approval precondition', () => { - const result = createPrecondition(sampleErc721Approval) - expect(result).toEqual(sampleErc721Approval) - expect(result.type).toBe('erc721-approval') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.tokenId).toBe(testTokenId) - expect(result.operator).toBe(testAddress2) - }) - - it('should create erc1155 balance precondition', () => { - const result = createPrecondition(sampleErc1155Balance) - expect(result).toEqual(sampleErc1155Balance) - expect(result.type).toBe('erc1155-balance') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.tokenId).toBe(testTokenId) - expect(result.min).toBe(5n) - expect(result.max).toBe(100n) - }) - - it('should create erc1155 approval precondition', () => { - const result = createPrecondition(sampleErc1155Approval) - expect(result).toEqual(sampleErc1155Approval) - expect(result.type).toBe('erc1155-approval') - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testTokenAddress) - expect(result.tokenId).toBe(testTokenId) - expect(result.operator).toBe(testAddress2) - expect(result.min).toBe(10n) - }) - - it('should handle preconditions without optional fields', () => { - const minimalNativeBalance: NativeBalancePrecondition = { - type: 'native-balance', - address: testAddress, - } - const result = createPrecondition(minimalNativeBalance) - expect(result).toEqual(minimalNativeBalance) - expect(result.min).toBeUndefined() - expect(result.max).toBeUndefined() - }) - - it('should handle erc721 ownership without owned flag', () => { - const minimalErc721: Erc721OwnershipPrecondition = { - type: 'erc721-ownership', - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - } - const result = createPrecondition(minimalErc721) - expect(result).toEqual(minimalErc721) - expect(result.owned).toBeUndefined() - }) - - it('should throw for null precondition', () => { - expect(() => createPrecondition(null as unknown as AnyPrecondition)).toThrow( - "Invalid precondition object: missing or invalid 'type' property.", - ) - }) - - it('should throw for undefined precondition', () => { - expect(() => createPrecondition(undefined as unknown as AnyPrecondition)).toThrow( - "Invalid precondition object: missing or invalid 'type' property.", - ) - }) - - it('should throw for precondition without type', () => { - const invalidPrecondition = { - address: testAddress, - min: testMinAmount, - } as unknown as AnyPrecondition - expect(() => createPrecondition(invalidPrecondition)).toThrow( - "Invalid precondition object: missing or invalid 'type' property.", - ) - }) - - it('should throw for precondition with invalid type', () => { - const invalidPrecondition = { - type: 'invalid-type', - address: testAddress, - } as unknown as AnyPrecondition - expect(() => createPrecondition(invalidPrecondition)).toThrow( - "Invalid precondition object: missing or invalid 'type' property.", - ) - }) - - it('should throw for precondition with non-string type', () => { - const invalidPrecondition = { - type: 123, - address: testAddress, - } as unknown as AnyPrecondition - expect(() => createPrecondition(invalidPrecondition)).toThrow( - "Invalid precondition object: missing or invalid 'type' property.", - ) - }) - - it('should maintain object identity for valid preconditions', () => { - const result = createPrecondition(sampleNativeBalance) - expect(result).toBe(sampleNativeBalance) // Should return the same object - }) - }) - }) - - describe('Intent Precondition Creation', () => { - describe('createIntentPrecondition', () => { - it('should create intent precondition for native balance', () => { - const result = createIntentPrecondition(sampleNativeBalance) - expect(result.type).toBe('native-balance') - expect(result.data).toEqual({ - address: testAddress, - min: testMinAmount, - max: testMaxAmount, - }) - expect(result.chainId).toBeUndefined() - }) - - it('should create intent precondition with chain ID', () => { - const result = createIntentPrecondition(sampleNativeBalance, testChainId) - expect(result.type).toBe('native-balance') - expect(result.data).toEqual({ - address: testAddress, - min: testMinAmount, - max: testMaxAmount, - }) - expect(result.chainId).toBe(testChainId) - }) - - it('should create intent precondition for erc20 balance', () => { - const result = createIntentPrecondition(sampleErc20Balance, testChainId) - expect(result.type).toBe('erc20-balance') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - min: testMinAmount, - max: testMaxAmount, - }) - expect(result.chainId).toBe(testChainId) - }) - - it('should create intent precondition for erc20 approval', () => { - const result = createIntentPrecondition(sampleErc20Approval) - expect(result.type).toBe('erc20-approval') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - operator: testAddress2, - min: testMinAmount, - }) - expect(result.chainId).toBeUndefined() - }) - - it('should create intent precondition for erc721 ownership', () => { - const result = createIntentPrecondition(sampleErc721Ownership, testChainId) - expect(result.type).toBe('erc721-ownership') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - owned: true, - }) - expect(result.chainId).toBe(testChainId) - }) - - it('should create intent precondition for erc721 approval', () => { - const result = createIntentPrecondition(sampleErc721Approval) - expect(result.type).toBe('erc721-approval') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - operator: testAddress2, - }) - expect(result.chainId).toBeUndefined() - }) - - it('should create intent precondition for erc1155 balance', () => { - const result = createIntentPrecondition(sampleErc1155Balance, testChainId) - expect(result.type).toBe('erc1155-balance') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - min: 5n, - max: 100n, - }) - expect(result.chainId).toBe(testChainId) - }) - - it('should create intent precondition for erc1155 approval', () => { - const result = createIntentPrecondition(sampleErc1155Approval) - expect(result.type).toBe('erc1155-approval') - expect(result.data).toEqual({ - address: testAddress, - token: testTokenAddress, - tokenId: testTokenId, - operator: testAddress2, - min: 10n, - }) - expect(result.chainId).toBeUndefined() - }) - - it('should handle zero chain ID', () => { - const result = createIntentPrecondition(sampleNativeBalance, ChainId.NONE) - expect(result.chainId).toBe(ChainId.NONE) - }) - - it('should exclude undefined chain ID from result', () => { - const result = createIntentPrecondition(sampleNativeBalance, undefined) - expect(result.chainId).toBeUndefined() - expect('chainId' in result).toBe(false) - }) - - it('should throw for invalid precondition type', () => { - const invalidPrecondition = { - type: 'invalid-type', - address: testAddress, - } as unknown as AnyPrecondition - expect(() => createIntentPrecondition(invalidPrecondition)).toThrow('Invalid precondition type: invalid-type') - }) - - it('should handle minimal preconditions', () => { - const minimalNativeBalance: NativeBalancePrecondition = { - type: 'native-balance', - address: testAddress, - } - const result = createIntentPrecondition(minimalNativeBalance, testChainId) - expect(result.type).toBe('native-balance') - expect(result.data).toEqual({ address: testAddress }) - expect(result.chainId).toBe(testChainId) - }) - }) - }) - - describe('Type Safety and Interface Compliance', () => { - it('should properly type native balance precondition', () => { - const precondition: NativeBalancePrecondition = sampleNativeBalance - expect(precondition.type).toBe('native-balance') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.min).toBe('bigint') - expect(typeof precondition.max).toBe('bigint') - }) - - it('should properly type erc20 balance precondition', () => { - const precondition: Erc20BalancePrecondition = sampleErc20Balance - expect(precondition.type).toBe('erc20-balance') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.min).toBe('bigint') - expect(typeof precondition.max).toBe('bigint') - }) - - it('should properly type erc20 approval precondition', () => { - const precondition: Erc20ApprovalPrecondition = sampleErc20Approval - expect(precondition.type).toBe('erc20-approval') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.operator).toBe('string') - expect(typeof precondition.min).toBe('bigint') - }) - - it('should properly type erc721 ownership precondition', () => { - const precondition: Erc721OwnershipPrecondition = sampleErc721Ownership - expect(precondition.type).toBe('erc721-ownership') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.tokenId).toBe('bigint') - expect(typeof precondition.owned).toBe('boolean') - }) - - it('should properly type erc721 approval precondition', () => { - const precondition: Erc721ApprovalPrecondition = sampleErc721Approval - expect(precondition.type).toBe('erc721-approval') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.tokenId).toBe('bigint') - expect(typeof precondition.operator).toBe('string') - }) - - it('should properly type erc1155 balance precondition', () => { - const precondition: Erc1155BalancePrecondition = sampleErc1155Balance - expect(precondition.type).toBe('erc1155-balance') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.tokenId).toBe('bigint') - expect(typeof precondition.min).toBe('bigint') - expect(typeof precondition.max).toBe('bigint') - }) - - it('should properly type erc1155 approval precondition', () => { - const precondition: Erc1155ApprovalPrecondition = sampleErc1155Approval - expect(precondition.type).toBe('erc1155-approval') - expect(typeof precondition.address).toBe('string') - expect(typeof precondition.token).toBe('string') - expect(typeof precondition.tokenId).toBe('bigint') - expect(typeof precondition.operator).toBe('string') - expect(typeof precondition.min).toBe('bigint') - }) - - it('should work with AnyPrecondition union type', () => { - const preconditions: AnyPrecondition[] = [ - sampleNativeBalance, - sampleErc20Balance, - sampleErc20Approval, - sampleErc721Ownership, - sampleErc721Approval, - sampleErc1155Balance, - sampleErc1155Approval, - ] - - preconditions.forEach((precondition) => { - expect(typeof precondition.type).toBe('string') - expect(isValidPreconditionType(precondition.type)).toBe(true) - expect(() => createPrecondition(precondition)).not.toThrow() - }) - }) - }) - - describe('Edge Cases and Boundary Testing', () => { - it('should handle zero values correctly', () => { - const zeroValuePrecondition: NativeBalancePrecondition = { - type: 'native-balance', - address: testAddress, - min: 0n, - max: 0n, - } - const result = createPrecondition(zeroValuePrecondition) - expect(result.min).toBe(0n) - expect(result.max).toBe(0n) - }) - - it('should handle very large BigInt values', () => { - const largeValuePrecondition: Erc20BalancePrecondition = { - type: 'erc20-balance', - address: testAddress, - token: testTokenAddress, - min: 2n ** 256n - 1n, - max: 2n ** 256n - 1n, - } - const result = createPrecondition(largeValuePrecondition) - expect(result.min).toBe(2n ** 256n - 1n) - expect(result.max).toBe(2n ** 256n - 1n) - }) - - it('should handle zero token ID', () => { - const zeroTokenIdPrecondition: Erc721OwnershipPrecondition = { - type: 'erc721-ownership', - address: testAddress, - token: testTokenAddress, - tokenId: 0n, - owned: false, - } - const result = createPrecondition(zeroTokenIdPrecondition) - expect(result.tokenId).toBe(0n) - expect(result.owned).toBe(false) - }) - - it('should handle very large token ID', () => { - const largeTokenIdPrecondition: Erc1155BalancePrecondition = { - type: 'erc1155-balance', - address: testAddress, - token: testTokenAddress, - tokenId: 2n ** 256n - 1n, - min: 1n, - } - const result = createPrecondition(largeTokenIdPrecondition) - expect(result.tokenId).toBe(2n ** 256n - 1n) - }) - - it('should handle same addresses for all fields', () => { - const sameAddressPrecondition: Erc20ApprovalPrecondition = { - type: 'erc20-approval', - address: testAddress, - token: testAddress, - operator: testAddress, - min: 1000n, - } - const result = createPrecondition(sameAddressPrecondition) - expect(result.address).toBe(testAddress) - expect(result.token).toBe(testAddress) - expect(result.operator).toBe(testAddress) - }) - - it('should handle different chain IDs', () => { - const chainIds = [ChainId.NONE, ChainId.MAINNET, ChainId.POLYGON, ChainId.ARBITRUM, ChainId.OPTIMISM] - - chainIds.forEach((chainId) => { - const result = createIntentPrecondition(sampleNativeBalance, chainId) - expect(result.chainId).toBe(chainId) - }) - }) - }) - - describe('Real-world Scenarios', () => { - it('should create precondition for minimum ETH balance check', () => { - const ethBalanceCheck: NativeBalancePrecondition = { - type: 'native-balance', - address: testAddress, - min: 1000000000000000000n, // 1 ETH minimum - } - const result = createPrecondition(ethBalanceCheck) - expect(result.min).toBe(1000000000000000000n) - expect(result.max).toBeUndefined() - }) - - it('should create precondition for USDC balance range', () => { - const usdcBalanceCheck: Erc20BalancePrecondition = { - type: 'erc20-balance', - address: testAddress, - token: '0xa0b86a33e6f8b5f56e64c9e1a1b8c6a9cc4b9a9e', // Mock USDC - min: 100000000n, // 100 USDC (6 decimals) - max: 10000000000n, // 10,000 USDC - } - const result = createPrecondition(usdcBalanceCheck) - expect(result.token).toBe('0xa0b86a33e6f8b5f56e64c9e1a1b8c6a9cc4b9a9e') - expect(result.min).toBe(100000000n) - expect(result.max).toBe(10000000000n) - }) - - it('should create precondition for NFT ownership verification', () => { - const nftOwnershipCheck: Erc721OwnershipPrecondition = { - type: 'erc721-ownership', - address: testAddress, - token: testTokenAddress, - tokenId: 1337n, - owned: true, - } - const result = createPrecondition(nftOwnershipCheck) - expect(result.tokenId).toBe(1337n) - expect(result.owned).toBe(true) - }) - - it('should create precondition for DEX approval check', () => { - const dexApprovalCheck: Erc20ApprovalPrecondition = { - type: 'erc20-approval', - address: testAddress, - token: testTokenAddress, - operator: '0x7a250d5630b4cf539739df2c5dacb4c659f2488d', // Uniswap V2 Router - min: 1000000000000000000000n, // 1000 tokens - } - const result = createPrecondition(dexApprovalCheck) - expect(result.operator).toBe('0x7a250d5630b4cf539739df2c5dacb4c659f2488d') - expect(result.min).toBe(1000000000000000000000n) - }) - - it('should create intent precondition for multi-chain scenario', () => { - const polygonPrecondition = createIntentPrecondition(sampleNativeBalance, ChainId.POLYGON) - const arbitrumPrecondition = createIntentPrecondition(sampleErc20Balance, ChainId.ARBITRUM) - - expect(polygonPrecondition.chainId).toBe(ChainId.POLYGON) - expect(arbitrumPrecondition.chainId).toBe(ChainId.ARBITRUM) - }) - }) - - describe('Integration and Workflow Testing', () => { - it('should handle complete precondition creation workflow', () => { - // Create various preconditions - const preconditions: AnyPrecondition[] = [ - sampleNativeBalance, - sampleErc20Balance, - sampleErc20Approval, - sampleErc721Ownership, - sampleErc721Approval, - sampleErc1155Balance, - sampleErc1155Approval, - ] - - // Validate and create each precondition - const createdPreconditions = preconditions.map((p) => createPrecondition(p)) - expect(createdPreconditions).toHaveLength(7) - - // Create intent preconditions with different chain IDs - const intentPreconditions = createdPreconditions.map((p, index) => createIntentPrecondition(p, index + 1)) - expect(intentPreconditions).toHaveLength(7) - - // Verify all have correct chain IDs - intentPreconditions.forEach((intent, index) => { - expect(intent.chainId).toBe(index + 1) - expect(isValidPreconditionType(intent.type)).toBe(true) - }) - }) - - it('should maintain type safety throughout workflow', () => { - const precondition = createPrecondition(sampleErc20Balance) - const intent = createIntentPrecondition(precondition, testChainId) - - // Type should be preserved - expect(intent.type).toBe('erc20-balance') - - // Data should exclude type but include all other fields - expect(intent.data).toEqual({ - address: testAddress, - token: testTokenAddress, - min: testMinAmount, - max: testMaxAmount, - }) - - // Chain ID should be added - expect(intent.chainId).toBe(testChainId) - }) - - it('should handle array of mixed preconditions', () => { - const mixedPreconditions: AnyPrecondition[] = [ - { type: 'native-balance', address: testAddress, min: 1n }, - { type: 'erc20-balance', address: testAddress, token: testTokenAddress }, - { type: 'erc721-ownership', address: testAddress, token: testTokenAddress, tokenId: 1n }, - ] - - const results = mixedPreconditions.map((p) => { - const created = createPrecondition(p) - return createIntentPrecondition(created, testChainId) - }) - - expect(results).toHaveLength(3) - expect(results[0].type).toBe('native-balance') - expect(results[1].type).toBe('erc20-balance') - expect(results[2].type).toBe('erc721-ownership') - - results.forEach((result) => { - expect(result.chainId).toBe(testChainId) - }) - }) - }) -}) diff --git a/packages/wallet/primitives/test/recovery.test.ts b/packages/wallet/primitives/test/recovery.test.ts deleted file mode 100644 index c5327a4942..0000000000 --- a/packages/wallet/primitives/test/recovery.test.ts +++ /dev/null @@ -1,925 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest' -import { Address, Bytes, Hex } from 'ox' - -import { - FLAG_RECOVERY_LEAF, - FLAG_NODE, - FLAG_BRANCH, - DOMAIN_NAME, - DOMAIN_VERSION, - QUEUE_PAYLOAD, - TIMESTAMP_FOR_QUEUED_PAYLOAD, - QUEUED_PAYLOAD_HASHES, - TOTAL_QUEUED_PAYLOADS, - RecoveryLeaf, - Branch, - Tree, - isRecoveryLeaf, - isBranch, - isTree, - hashConfiguration, - getRecoveryLeaves, - decodeTopology, - parseBranch, - trimTopology, - encodeTopology, - fromRecoveryLeaves, - hashRecoveryPayload, - toGenericTree, - fromGenericTree, - encodeCalldata, - totalQueuedPayloads, - queuedPayloadHashOf, - timestampForQueuedPayload, -} from '../src/extensions/recovery.js' -import * as Payload from '../src/payload.js' -import * as GenericTree from '../src/generic-tree.js' -import { ChainId } from '../src/network.js' - -describe('Recovery', () => { - // Test data - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' as Address.Address - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' as Address.Address - const testExtensionAddress = '0x1234567890123456789012345678901234567890' as Address.Address - const testNodeHash = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - - const sampleRecoveryLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 3600n, // 1 hour - minTimestamp: 1640995200n, // Jan 1, 2022 - } - - const sampleRecoveryLeaf2: RecoveryLeaf = { - type: 'leaf', - signer: testAddress2, - requiredDeltaTime: 7200n, // 2 hours - minTimestamp: 1640995200n, // Jan 1, 2022 - } - - const samplePayload: Payload.Calls = { - type: 'call', - space: 0n, - nonce: 1n, - calls: [ - { - to: testAddress, - value: 0n, - data: '0x1234', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - } - - const sampleSignature = { - type: 'hash' as const, - r: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn, - s: 0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321n, - yParity: 1, - } - - // Mock provider - const mockProvider = { - request: vi.fn(), - on: vi.fn(), - removeListener: vi.fn(), - } as any - - beforeEach(() => { - mockProvider.request.mockClear() - }) - - describe('Constants', () => { - it('should have correct flag values', () => { - expect(FLAG_RECOVERY_LEAF).toBe(1) - expect(FLAG_NODE).toBe(3) - expect(FLAG_BRANCH).toBe(4) - }) - - it('should have correct domain parameters', () => { - expect(DOMAIN_NAME).toBe('Sequence Wallet - Recovery Mode') - expect(DOMAIN_VERSION).toBe('1') - }) - - it('should have correct ABI definitions', () => { - expect(QUEUE_PAYLOAD.name).toBe('queuePayload') - expect(TIMESTAMP_FOR_QUEUED_PAYLOAD.name).toBe('timestampForQueuedPayload') - expect(QUEUED_PAYLOAD_HASHES.name).toBe('queuedPayloadHashes') - expect(TOTAL_QUEUED_PAYLOADS.name).toBe('totalQueuedPayloads') - }) - }) - - describe('Type Guards', () => { - describe('isRecoveryLeaf', () => { - it('should return true for valid recovery leaf', () => { - expect(isRecoveryLeaf(sampleRecoveryLeaf)).toBe(true) - }) - - it('should return false for invalid objects', () => { - expect(isRecoveryLeaf({})).toBe(false) - expect(isRecoveryLeaf(null)).toBe(false) - expect(isRecoveryLeaf({ type: 'not-leaf' })).toBe(false) - expect(isRecoveryLeaf('string')).toBe(false) - expect(isRecoveryLeaf(123)).toBe(false) - }) - - it('should return false for node hash', () => { - expect(isRecoveryLeaf(testNodeHash)).toBe(false) - }) - - it('should return false for branch', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - expect(isRecoveryLeaf(branch)).toBe(false) - }) - }) - - describe('isBranch', () => { - it('should return true for valid branch', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - expect(isBranch(branch)).toBe(true) - }) - - it.skip('should return true for branch with node', () => { - const branch: Branch = [sampleRecoveryLeaf, testNodeHash] - expect(isBranch(branch)).toBe(true) - }) - - it('should return false for non-arrays', () => { - expect(isBranch(sampleRecoveryLeaf)).toBe(false) - expect(isBranch(testNodeHash)).toBe(false) - expect(isBranch({})).toBe(false) - expect(isBranch(null)).toBe(false) - }) - - it('should return false for wrong length arrays', () => { - expect(isBranch([])).toBe(false) - expect(isBranch([sampleRecoveryLeaf])).toBe(false) - expect(isBranch([sampleRecoveryLeaf, sampleRecoveryLeaf2, testNodeHash])).toBe(false) - }) - - it('should return false for invalid tree elements', () => { - expect(isBranch([{}, {}])).toBe(false) - expect(isBranch([sampleRecoveryLeaf, {}])).toBe(false) - }) - }) - - describe('isTree', () => { - it('should return true for recovery leaves', () => { - expect(isTree(sampleRecoveryLeaf)).toBe(true) - }) - - it.skip('should return true for node hashes', () => { - expect(isTree(testNodeHash)).toBe(true) - }) - - it('should return true for branches', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - expect(isTree(branch)).toBe(true) - }) - - it('should return false for invalid objects', () => { - expect(isTree({})).toBe(false) - expect(isTree(null)).toBe(false) - expect(isTree('invalid')).toBe(false) - expect(isTree(123)).toBe(false) - }) - }) - }) - - describe('Configuration Hashing', () => { - describe('hashConfiguration', () => { - it('should hash recovery leaf', () => { - const hash = hashConfiguration(sampleRecoveryLeaf) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(hash).toHaveLength(66) - }) - - it.skip('should hash node directly', () => { - const hash = hashConfiguration(testNodeHash) - expect(hash).toBe(testNodeHash) - }) - - it('should hash branch consistently', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const hash1 = hashConfiguration(branch) - const hash2 = hashConfiguration(branch) - expect(hash1).toBe(hash2) - expect(hash1).toMatch(/^0x[a-fA-F0-9]{64}$/) - }) - - it('should produce different hashes for different configurations', () => { - const hash1 = hashConfiguration(sampleRecoveryLeaf) - const hash2 = hashConfiguration(sampleRecoveryLeaf2) - expect(hash1).not.toBe(hash2) - }) - - it.skip('should handle nested branches', () => { - const branch1: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const branch2: Branch = [branch1, testNodeHash] - const hash = hashConfiguration(branch2) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - }) - }) - - describe('toGenericTree', () => { - it('should convert recovery leaf to generic leaf', () => { - const generic = toGenericTree(sampleRecoveryLeaf) - expect(GenericTree.isLeaf(generic)).toBe(true) - if (GenericTree.isLeaf(generic)) { - expect(generic.type).toBe('leaf') - expect(generic.value).toBeInstanceOf(Uint8Array) - } - }) - - it.skip('should convert node hash directly', () => { - const generic = toGenericTree(testNodeHash) - expect(generic).toBe(testNodeHash) - }) - - it('should convert branch to generic branch', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const generic = toGenericTree(branch) - expect(GenericTree.isBranch(generic)).toBe(true) - if (GenericTree.isBranch(generic)) { - expect(generic).toHaveLength(2) - } - }) - - it('should throw for invalid topology', () => { - expect(() => toGenericTree({} as any)).toThrow('Invalid topology') - }) - }) - - describe('fromGenericTree', () => { - it('should convert generic leaf to recovery leaf', () => { - const generic = toGenericTree(sampleRecoveryLeaf) - const recovered = fromGenericTree(generic) - expect(isRecoveryLeaf(recovered)).toBe(true) - if (isRecoveryLeaf(recovered)) { - expect(recovered.signer).toBe(sampleRecoveryLeaf.signer) - expect(recovered.requiredDeltaTime).toBe(sampleRecoveryLeaf.requiredDeltaTime) - expect(recovered.minTimestamp).toBe(sampleRecoveryLeaf.minTimestamp) - } - }) - - it.skip('should convert node hash directly', () => { - const recovered = fromGenericTree(testNodeHash) - expect(recovered).toBe(testNodeHash) - }) - - it('should convert generic branch to recovery branch', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const generic = toGenericTree(branch) - const recovered = fromGenericTree(generic) - expect(isBranch(recovered)).toBe(true) - }) - - it('should handle round-trip conversion', () => { - const original = sampleRecoveryLeaf - const generic = toGenericTree(original) - const recovered = fromGenericTree(generic) - expect(recovered).toEqual(original) - }) - - it('should throw for invalid generic leaf format', () => { - const invalidLeaf: GenericTree.Leaf = { - type: 'leaf', - value: Bytes.fromString('invalid'), - } - expect(() => fromGenericTree(invalidLeaf)).toThrow('Invalid recovery leaf format') - }) - - it.skip('should throw for non-binary branches', () => { - const invalidBranch = [sampleRecoveryLeaf, sampleRecoveryLeaf2, testNodeHash] as any - expect(() => fromGenericTree(invalidBranch)).toThrow('Recovery tree only supports binary branches') - }) - - it('should throw for invalid tree format', () => { - expect(() => fromGenericTree({} as any)).toThrow('Invalid tree format') - }) - }) - }) - - describe('Topology Management', () => { - describe('getRecoveryLeaves', () => { - it('should get single leaf', () => { - const result = getRecoveryLeaves(sampleRecoveryLeaf) - expect(result.leaves).toHaveLength(1) - expect(result.leaves[0]).toBe(sampleRecoveryLeaf) - expect(result.isComplete).toBe(true) - }) - - it.skip('should handle node hash', () => { - const result = getRecoveryLeaves(testNodeHash) - expect(result.leaves).toHaveLength(0) - expect(result.isComplete).toBe(false) - }) - - it('should get leaves from branch', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const result = getRecoveryLeaves(branch) - expect(result.leaves).toHaveLength(2) - expect(result.leaves).toContain(sampleRecoveryLeaf) - expect(result.leaves).toContain(sampleRecoveryLeaf2) - expect(result.isComplete).toBe(true) - }) - - it.skip('should handle incomplete topology with nodes', () => { - const branch: Branch = [sampleRecoveryLeaf, testNodeHash] - const result = getRecoveryLeaves(branch) - expect(result.leaves).toHaveLength(1) - expect(result.leaves[0]).toBe(sampleRecoveryLeaf) - expect(result.isComplete).toBe(false) - }) - - it.skip('should handle nested branches', () => { - const innerBranch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const outerBranch: Branch = [innerBranch, testNodeHash] - const result = getRecoveryLeaves(outerBranch) - expect(result.leaves).toHaveLength(2) - expect(result.isComplete).toBe(false) - }) - - it('should throw for invalid topology', () => { - expect(() => getRecoveryLeaves({} as any)).toThrow('Invalid topology') - }) - }) - - describe('fromRecoveryLeaves', () => { - it('should create single leaf topology', () => { - const result = fromRecoveryLeaves([sampleRecoveryLeaf]) - expect(result).toBe(sampleRecoveryLeaf) - }) - - it('should create branch from two leaves', () => { - const result = fromRecoveryLeaves([sampleRecoveryLeaf, sampleRecoveryLeaf2]) - expect(isBranch(result)).toBe(true) - if (isBranch(result)) { - expect(result[0]).toBe(sampleRecoveryLeaf) - expect(result[1]).toBe(sampleRecoveryLeaf2) - } - }) - - it('should create balanced tree from multiple leaves', () => { - const leaf3: RecoveryLeaf = { - type: 'leaf', - signer: '0x1111111111111111111111111111111111111111' as Address.Address, - requiredDeltaTime: 1800n, - minTimestamp: 1640995200n, - } - - const leaf4: RecoveryLeaf = { - type: 'leaf', - signer: '0x2222222222222222222222222222222222222222' as Address.Address, - requiredDeltaTime: 3600n, - minTimestamp: 1640995200n, - } - - const result = fromRecoveryLeaves([sampleRecoveryLeaf, sampleRecoveryLeaf2, leaf3, leaf4]) - expect(isBranch(result)).toBe(true) - - // Should be a balanced binary tree - if (isBranch(result)) { - expect(isBranch(result[0])).toBe(true) - expect(isBranch(result[1])).toBe(true) - } - }) - - it('should throw for empty leaves array', () => { - expect(() => fromRecoveryLeaves([])).toThrow('Cannot build a tree with zero leaves') - }) - }) - - describe('trimTopology', () => { - it('should keep matching signer leaf', () => { - const result = trimTopology(sampleRecoveryLeaf, testAddress) - expect(result).toBe(sampleRecoveryLeaf) - }) - - it('should replace non-matching signer with hash', () => { - const result = trimTopology(sampleRecoveryLeaf, testAddress2) - expect(typeof result).toBe('string') - expect(result).toMatch(/^0x[a-fA-F0-9]{64}$/) - }) - - it.skip('should keep node hashes unchanged', () => { - const result = trimTopology(testNodeHash, testAddress) - expect(result).toBe(testNodeHash) - }) - - it('should trim branches selectively', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const result = trimTopology(branch, testAddress) - expect(isBranch(result)).toBe(true) - if (isBranch(result)) { - expect(result[0]).toBe(sampleRecoveryLeaf) // Kept - expect(typeof result[1]).toBe('string') // Replaced with hash - } - }) - - it('should return hash when both branches become hashes', () => { - const branch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const thirdAddress = '0x3333333333333333333333333333333333333333' as Address.Address - const result = trimTopology(branch, thirdAddress) - expect(typeof result).toBe('string') - expect(result).toMatch(/^0x[a-fA-F0-9]{64}$/) - }) - - it('should throw for invalid topology', () => { - expect(() => trimTopology({} as any, testAddress)).toThrow('Invalid topology') - }) - }) - }) - - describe('Binary Encoding and Decoding', () => { - describe('encodeTopology', () => { - it('should encode recovery leaf', () => { - const encoded = encodeTopology(sampleRecoveryLeaf) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(32) // 1 flag + 20 signer + 3 delta + 8 timestamp - expect(encoded[0]).toBe(FLAG_RECOVERY_LEAF) - }) - - it.skip('should encode node hash', () => { - const encoded = encodeTopology(testNodeHash) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBe(33) // 1 flag + 32 hash - expect(encoded[0]).toBe(FLAG_NODE) - }) - - it.skip('should encode simple branch', () => { - const branch: Branch = [sampleRecoveryLeaf, testNodeHash] - const encoded = encodeTopology(branch) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded.length).toBeGreaterThan(32) - }) - - it.skip('should encode nested branch with flag', () => { - const innerBranch: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const outerBranch: Branch = [testNodeHash, innerBranch] - const encoded = encodeTopology(outerBranch) - expect(encoded).toBeInstanceOf(Uint8Array) - // Should contain FLAG_BRANCH for the inner branch - expect(Array.from(encoded)).toContain(FLAG_BRANCH) - }) - - it('should throw for required delta time too large', () => { - const invalidLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 16777216n, // > 16777215 - minTimestamp: 1640995200n, - } - expect(() => encodeTopology(invalidLeaf)).toThrow('Required delta time too large') - }) - - it('should throw for min timestamp too large', () => { - const invalidLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 3600n, - minTimestamp: 18446744073709551616n, // > 18446744073709551615 - } - expect(() => encodeTopology(invalidLeaf)).toThrow('Min timestamp too large') - }) - - it('should throw for branch too large', () => { - // Skip this test as it requires complex mocking that's difficult to achieve - // The error condition would be extremely rare in practice - expect(true).toBe(true) // Placeholder to keep test structure - }) - - it('should throw for invalid topology', () => { - expect(() => encodeTopology({} as any)).toThrow('Invalid topology') - }) - }) - - describe('decodeTopology and parseBranch', () => { - it('should decode recovery leaf', () => { - const encoded = encodeTopology(sampleRecoveryLeaf) - const decoded = decodeTopology(encoded) - expect(isRecoveryLeaf(decoded)).toBe(true) - if (isRecoveryLeaf(decoded)) { - expect(decoded.signer).toBe(sampleRecoveryLeaf.signer) - expect(decoded.requiredDeltaTime).toBe(sampleRecoveryLeaf.requiredDeltaTime) - expect(decoded.minTimestamp).toBe(sampleRecoveryLeaf.minTimestamp) - } - }) - - it.skip('should decode node hash', () => { - const encoded = encodeTopology(testNodeHash) - const decoded = decodeTopology(encoded) - expect(decoded).toBe(testNodeHash) - }) - - it.skip('should decode simple branch', () => { - const branch: Branch = [sampleRecoveryLeaf, testNodeHash] - const encoded = encodeTopology(branch) - const decoded = decodeTopology(encoded) - expect(isBranch(decoded)).toBe(true) - }) - - it('should handle round-trip encoding/decoding', () => { - const original: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const encoded = encodeTopology(original) - const decoded = decodeTopology(encoded) - expect(decoded).toEqual(original) - }) - - it('should parse single recovery leaf', () => { - const leafBytes = Bytes.concat( - Bytes.fromNumber(FLAG_RECOVERY_LEAF), - Bytes.fromHex(testAddress, { size: 20 }), - Bytes.padLeft(Bytes.fromNumber(3600), 3), - Bytes.padLeft(Bytes.fromNumber(1640995200), 8), - ) - - const result = parseBranch(leafBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - expect(isRecoveryLeaf(result.nodes[0])).toBe(true) - }) - - it.skip('should parse node hash', () => { - const nodeBytes = Bytes.concat(Bytes.fromNumber(FLAG_NODE), Bytes.fromHex(testNodeHash, { size: 32 })) - - const result = parseBranch(nodeBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - expect(result.nodes[0]).toBe(testNodeHash) - }) - - it.skip('should parse multiple nodes', () => { - const leafBytes = Bytes.concat( - Bytes.fromNumber(FLAG_RECOVERY_LEAF), - Bytes.fromHex(testAddress, { size: 20 }), - Bytes.padLeft(Bytes.fromNumber(3600), 3), - Bytes.padLeft(Bytes.fromNumber(1640995200), 8), - ) - - const nodeBytes = Bytes.concat(Bytes.fromNumber(FLAG_NODE), Bytes.fromHex(testNodeHash, { size: 32 })) - - const combined = Bytes.concat(leafBytes, nodeBytes) - const result = parseBranch(combined) - expect(result.nodes).toHaveLength(2) - expect(result.leftover).toHaveLength(0) - }) - - it('should throw for empty branch', () => { - expect(() => parseBranch(Bytes.fromArray([]))).toThrow('Empty branch') - }) - - it('should throw for invalid recovery leaf', () => { - const invalidLeaf = Bytes.concat( - Bytes.fromNumber(FLAG_RECOVERY_LEAF), - Bytes.fromHex(testAddress, { size: 20 }), // Missing delta time and timestamp - ) - expect(() => parseBranch(invalidLeaf)).toThrow('Invalid recovery leaf') - }) - - it('should throw for invalid node', () => { - const invalidNode = Bytes.concat( - Bytes.fromNumber(FLAG_NODE), - Bytes.fromHex('0x1234', { size: 2 }), // Too short for node hash - ) - expect(() => parseBranch(invalidNode)).toThrow('Invalid node') - }) - - it('should throw for invalid branch flag', () => { - const invalidBranch = Bytes.concat( - Bytes.fromNumber(FLAG_BRANCH), - Bytes.fromNumber(1), // Size too small - ) - expect(() => parseBranch(invalidBranch)).toThrow('Invalid branch') - }) - - it('should throw for invalid flag', () => { - const invalidFlag = Bytes.fromNumber(99) // Invalid flag - expect(() => parseBranch(invalidFlag)).toThrow('Invalid flag') - }) - - it.skip('should throw for leftover bytes in decode', () => { - const encoded = encodeTopology(sampleRecoveryLeaf) - const withExtra = Bytes.concat(encoded, Bytes.fromArray([0x99])) - expect(() => decodeTopology(withExtra)).toThrow('Leftover bytes in branch') - }) - }) - }) - - describe('Recovery Payload Handling', () => { - describe('hashRecoveryPayload', () => { - it('should hash recovery payload', () => { - const hash = hashRecoveryPayload(samplePayload, testAddress, ChainId.MAINNET, false) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(hash).toHaveLength(66) - }) - - it('should hash with no chain ID', () => { - const hash = hashRecoveryPayload(samplePayload, testAddress, ChainId.MAINNET, true) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - expect(hash).toHaveLength(66) - }) - - it('should produce different hashes for different parameters', () => { - const hash1 = hashRecoveryPayload(samplePayload, testAddress, 1, false) - const hash2 = hashRecoveryPayload(samplePayload, testAddress, 2, false) - const hash3 = hashRecoveryPayload(samplePayload, testAddress2, 1, false) - const hash4 = hashRecoveryPayload(samplePayload, testAddress, 1, true) - - expect(hash1).not.toBe(hash2) // Different chain ID - expect(hash1).not.toBe(hash3) // Different wallet - expect(hash1).not.toBe(hash4) // Different noChainId - }) - - it('should be deterministic', () => { - const hash1 = hashRecoveryPayload(samplePayload, testAddress, ChainId.MAINNET, false) - const hash2 = hashRecoveryPayload(samplePayload, testAddress, ChainId.MAINNET, false) - expect(hash1).toBe(hash2) - }) - }) - - describe('encodeCalldata', () => { - it('should encode calldata for hash signature', () => { - const recoveryPayload = Payload.toRecovery(samplePayload) - const calldata = encodeCalldata(testAddress, recoveryPayload, testAddress2, sampleSignature) - expect(calldata).toMatch(/^0x[a-fA-F0-9]+$/) - expect(calldata.length).toBeGreaterThan(10) // Should be substantial - }) - - it('should encode calldata for ERC-1271 signature', () => { - const erc1271Signature = { - type: 'erc1271' as const, - address: testAddress, - data: '0x1234567890abcdef' as Hex.Hex, - } - - const recoveryPayload = Payload.toRecovery(samplePayload) - const calldata = encodeCalldata(testAddress, recoveryPayload, testAddress2, erc1271Signature) - expect(calldata).toMatch(/^0x[a-fA-F0-9]+$/) - expect(calldata.length).toBeGreaterThan(10) - }) - - it('should produce different calldata for different inputs', () => { - const recoveryPayload = Payload.toRecovery(samplePayload) - const calldata1 = encodeCalldata(testAddress, recoveryPayload, testAddress, sampleSignature) - const calldata2 = encodeCalldata(testAddress, recoveryPayload, testAddress2, sampleSignature) - expect(calldata1).not.toBe(calldata2) - }) - }) - }) - - describe('Provider Interactions', () => { - describe('totalQueuedPayloads', () => { - it('should return queued payload count', async () => { - mockProvider.request.mockResolvedValue('0x5') // 5 payloads - - const result = await totalQueuedPayloads(mockProvider, testExtensionAddress, testAddress, testAddress2) - expect(result).toBe(5n) - expect(mockProvider.request).toHaveBeenCalledWith({ - method: 'eth_call', - params: [ - { - to: testExtensionAddress, - data: expect.any(String), - }, - 'latest', - ], - }) - }) - - it('should handle empty response', async () => { - mockProvider.request.mockResolvedValue('0x') - - const result = await totalQueuedPayloads(mockProvider, testExtensionAddress, testAddress, testAddress2) - expect(result).toBe(0n) - }) - - it('should handle zero value', async () => { - mockProvider.request.mockResolvedValue('0x0') - - const result = await totalQueuedPayloads(mockProvider, testExtensionAddress, testAddress, testAddress2) - expect(result).toBe(0n) - }) - }) - - describe('queuedPayloadHashOf', () => { - it('should return payload hash', async () => { - mockProvider.request.mockResolvedValue(testNodeHash) - - const result = await queuedPayloadHashOf(mockProvider, testExtensionAddress, testAddress, testAddress2, 0n) - expect(result).toBe(testNodeHash) - expect(mockProvider.request).toHaveBeenCalledWith({ - method: 'eth_call', - params: [ - { - to: testExtensionAddress, - data: expect.any(String), - }, - 'latest', - ], - }) - }) - - it('should handle different indices', async () => { - mockProvider.request.mockResolvedValue(testNodeHash) - - await queuedPayloadHashOf(mockProvider, testExtensionAddress, testAddress, testAddress2, 5n) - expect(mockProvider.request).toHaveBeenCalledWith({ - method: 'eth_call', - params: [ - { - to: testExtensionAddress, - data: expect.stringContaining('0x'), - }, - 'latest', - ], - }) - }) - }) - - describe('timestampForQueuedPayload', () => { - it('should return timestamp', async () => { - mockProvider.request.mockResolvedValue('0x61d2b800') // 1641168000 in hex - const validPayloadHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - - const result = await timestampForQueuedPayload( - mockProvider, - testExtensionAddress, - testAddress, - testAddress2, - validPayloadHash, - ) - expect(result).toBe(1641199616n) // Fixed expected value to match actual conversion - expect(mockProvider.request).toHaveBeenCalledWith({ - method: 'eth_call', - params: [ - { - to: testExtensionAddress, - data: expect.any(String), - }, - 'latest', - ], - }) - }) - - it('should handle zero timestamp', async () => { - mockProvider.request.mockResolvedValue('0x0') - const validPayloadHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - - const result = await timestampForQueuedPayload( - mockProvider, - testExtensionAddress, - testAddress, - testAddress2, - validPayloadHash, - ) - expect(result).toBe(0n) - }) - - it('should handle large timestamps', async () => { - mockProvider.request.mockResolvedValue('0xffffffffffffffff') // Max uint64 - const validPayloadHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - - const result = await timestampForQueuedPayload( - mockProvider, - testExtensionAddress, - testAddress, - testAddress2, - validPayloadHash, - ) - expect(result).toBe(18446744073709551615n) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle maximum valid delta time', () => { - const maxDeltaLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 16777215n, // Max valid value - minTimestamp: 1640995200n, - } - - const encoded = encodeTopology(maxDeltaLeaf) - const decoded = decodeTopology(encoded) - expect(decoded).toEqual(maxDeltaLeaf) - }) - - it('should handle maximum valid timestamp', () => { - const maxTimestampLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 3600n, - minTimestamp: 18446744073709551615n, // Max valid value - } - - const encoded = encodeTopology(maxTimestampLeaf) - const decoded = decodeTopology(encoded) - expect(decoded).toEqual(maxTimestampLeaf) - }) - - it('should handle zero delta time', () => { - const zeroDeltaLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 0n, - minTimestamp: 1640995200n, - } - - const encoded = encodeTopology(zeroDeltaLeaf) - const decoded = decodeTopology(encoded) - expect(decoded).toEqual(zeroDeltaLeaf) - }) - - it('should handle zero timestamp', () => { - const zeroTimestampLeaf: RecoveryLeaf = { - type: 'leaf', - signer: testAddress, - requiredDeltaTime: 3600n, - minTimestamp: 0n, - } - - const encoded = encodeTopology(zeroTimestampLeaf) - const decoded = decodeTopology(encoded) - expect(decoded).toEqual(zeroTimestampLeaf) - }) - - it('should handle deeply nested trees', () => { - let tree: Tree = sampleRecoveryLeaf - - // Create a deeply nested tree - for (let i = 0; i < 10; i++) { - tree = [tree, sampleRecoveryLeaf2] as Branch - } - - const hash = hashConfiguration(tree) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - }) - - it('should handle empty generic tree conversion edge cases', () => { - // Test the recovery leaf prefix validation - const invalidGenericLeaf: GenericTree.Leaf = { - type: 'leaf', - value: Bytes.fromString('wrong prefix'), // Wrong prefix - } - - expect(() => fromGenericTree(invalidGenericLeaf)).toThrow('Invalid recovery leaf format') - }) - }) - - describe('Integration Tests', () => { - it('should handle complete recovery workflow', () => { - // Create a recovery tree - const leaves = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const tree = fromRecoveryLeaves(leaves) - - // Hash the configuration - const configHash = hashConfiguration(tree) - - // Encode and decode - const encoded = encodeTopology(tree) - const decoded = decodeTopology(encoded) - - // Verify consistency - expect(decoded).toEqual(tree) - expect(hashConfiguration(decoded)).toBe(configHash) - - // Test trimming - const trimmed = trimTopology(tree, testAddress) - expect(isBranch(trimmed)).toBe(true) - - // Get leaves - const { leaves: extractedLeaves, isComplete } = getRecoveryLeaves(tree) - expect(extractedLeaves).toHaveLength(2) - expect(isComplete).toBe(true) - }) - - it('should handle generic tree round-trip', () => { - const original: Branch = [sampleRecoveryLeaf, sampleRecoveryLeaf2] - const generic = toGenericTree(original) - const recovered = fromGenericTree(generic) - - expect(recovered).toEqual(original) - expect(hashConfiguration(original)).toBe(GenericTree.hash(generic)) - }) - - it.skip('should handle mixed topology types', () => { - const mixedTree: Branch = [sampleRecoveryLeaf, testNodeHash] - - const encoded = encodeTopology(mixedTree) - const decoded = decodeTopology(encoded) - const hash = hashConfiguration(decoded) - - expect(isBranch(decoded)).toBe(true) - expect(hash).toMatch(/^0x[a-fA-F0-9]{64}$/) - - const { leaves, isComplete } = getRecoveryLeaves(decoded) - expect(leaves).toHaveLength(1) - expect(isComplete).toBe(false) - }) - }) -}) diff --git a/packages/wallet/primitives/test/session-config.test.ts b/packages/wallet/primitives/test/session-config.test.ts deleted file mode 100644 index 6e20d55974..0000000000 --- a/packages/wallet/primitives/test/session-config.test.ts +++ /dev/null @@ -1,1110 +0,0 @@ -import { Address, Bytes } from 'ox' -import { describe, expect, it } from 'vitest' - -import { ChainId } from '../src/network.js' -import { ParameterOperation, Permission, SessionPermissions } from '../src/permission.js' -import { - IdentitySignerLeaf, - ImplicitBlacklistLeaf, - SESSIONS_FLAG_BLACKLIST, - SESSIONS_FLAG_BRANCH, - SESSIONS_FLAG_IDENTITY_SIGNER, - SESSIONS_FLAG_NODE, - SESSIONS_FLAG_PERMISSIONS, - SessionBranch, - SessionNode, - SessionPermissionsLeaf, - SessionsTopology, - addExplicitSession, - addToImplicitBlacklist, - balanceSessionsTopology, - cleanSessionsTopology, - configurationTreeToSessionsTopology, - decodeLeafFromBytes, - decodeSessionsTopology, - emptySessionsTopology, - encodeLeafToGeneric, - encodeSessionsTopology, - getExplicitSigners, - getIdentitySigners, - getImplicitBlacklist, - getImplicitBlacklistLeaf, - getSessionPermissions, - isCompleteSessionsTopology, - isSessionsTopology, - mergeSessionsTopologies, - minimiseSessionsTopology, - removeExplicitSession, - removeFromImplicitBlacklist, - sessionsTopologyFromJson, - sessionsTopologyToConfigurationTree, - sessionsTopologyToJson, -} from '../src/session-config.js' - -describe('Session Config', () => { - // Test data - const testAddress1: Address.Address = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' - const testAddress2: Address.Address = '0x8ba1f109551bd432803012645aac136c776056c0' - const testAddress3: Address.Address = '0xa0b86a33e6f8b5f56e64c9e1a1b8c6a9cc4b9a9e' - const testNode: SessionNode = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - - const samplePermission: Permission = { - target: testAddress3, - rules: [ - { - cumulative: false, - operation: ParameterOperation.EQUAL, - value: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000000000000000000'), - offset: 0n, - mask: Bytes.fromHex('0xffffffff00000000000000000000000000000000000000000000000000000000'), - }, - ], - } - - const sampleSessionPermissions: SessionPermissions = { - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, // 1 ETH - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [samplePermission], - } - - const sampleSessionPermissionsLeaf: SessionPermissionsLeaf = { - type: 'session-permissions', - ...sampleSessionPermissions, - } - - const sampleBlacklistLeaf: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: [testAddress2, testAddress3], - } - - const sampleIdentitySignerLeaf: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress1, - } - - const sampleBranch: SessionBranch = [sampleBlacklistLeaf, sampleIdentitySignerLeaf] - const sampleCompleteTopology: SessionsTopology = [ - sampleBlacklistLeaf, - sampleIdentitySignerLeaf, - sampleSessionPermissionsLeaf, - ] - - describe('Constants', () => { - it('should have correct flag values', () => { - expect(SESSIONS_FLAG_PERMISSIONS).toBe(0) - expect(SESSIONS_FLAG_NODE).toBe(1) - expect(SESSIONS_FLAG_BRANCH).toBe(2) - expect(SESSIONS_FLAG_BLACKLIST).toBe(3) - expect(SESSIONS_FLAG_IDENTITY_SIGNER).toBe(4) - }) - }) - - describe('Type Guards and Validation', () => { - describe('isSessionsTopology', () => { - it('should return true for valid session permissions leaf', () => { - expect(isSessionsTopology(sampleSessionPermissionsLeaf)).toBe(true) - }) - - it('should return true for valid blacklist leaf', () => { - expect(isSessionsTopology(sampleBlacklistLeaf)).toBe(true) - }) - - it('should return true for valid identity signer leaf', () => { - expect(isSessionsTopology(sampleIdentitySignerLeaf)).toBe(true) - }) - - it('should return true for valid session node', () => { - expect(isSessionsTopology(testNode)).toBe(true) - }) - - it('should return true for valid session branch', () => { - expect(isSessionsTopology(sampleBranch)).toBe(true) - }) - - it('should return false for invalid topology', () => { - expect(isSessionsTopology({})).toBe(false) - expect(isSessionsTopology(null)).toBe(false) - expect(isSessionsTopology('invalid')).toBe(false) - expect(isSessionsTopology([])).toBe(false) // Empty array - expect(isSessionsTopology([{}])).toBe(false) // Invalid child - }) - }) - - describe('isCompleteSessionsTopology', () => { - it('should return true for complete topology', () => { - expect(isCompleteSessionsTopology(sampleCompleteTopology)).toBe(true) - }) - - it('should return false for topology without blacklist', () => { - const incompleteTopology = [sampleIdentitySignerLeaf, sampleSessionPermissionsLeaf] - expect(isCompleteSessionsTopology(incompleteTopology)).toBe(false) - }) - - it('should return false for topology without identity signer', () => { - const incompleteTopology = [sampleBlacklistLeaf, sampleSessionPermissionsLeaf] - expect(isCompleteSessionsTopology(incompleteTopology)).toBe(false) - }) - - it('should return false for topology with multiple blacklists', () => { - const duplicateBlacklist = [sampleBlacklistLeaf, sampleBlacklistLeaf, sampleIdentitySignerLeaf] - expect(isCompleteSessionsTopology(duplicateBlacklist)).toBe(false) - }) - - it('should return true for topology with multiple identity signers', () => { - const duplicateIdentity = [sampleBlacklistLeaf, sampleIdentitySignerLeaf, sampleIdentitySignerLeaf] - expect(isCompleteSessionsTopology(duplicateIdentity)).toBe(true) - }) - - it('should return false for invalid topology', () => { - expect(isCompleteSessionsTopology({})).toBe(false) - expect(isCompleteSessionsTopology(null)).toBe(false) - }) - }) - }) - - describe('Topology Queries', () => { - describe('getIdentitySigners', () => { - it('should return identity signer from identity signer leaf', () => { - const result = getIdentitySigners(sampleIdentitySignerLeaf) - expect(result).toEqual([testAddress1]) - }) - - it('should return identity signer from branch', () => { - const result = getIdentitySigners(sampleCompleteTopology) - expect(result).toEqual([testAddress1]) - }) - - it('should return empty array when no identity signer present', () => { - const result = getIdentitySigners(sampleSessionPermissionsLeaf) - expect(result).toEqual([]) - }) - - it('should return multiple identity signers', () => { - const multipleIdentity = [ - sampleIdentitySignerLeaf, - sampleIdentitySignerLeaf, - sampleBlacklistLeaf, - ] as SessionBranch - expect(getIdentitySigners(multipleIdentity)).toEqual([testAddress1, testAddress1]) - }) - }) - - describe('getImplicitBlacklist', () => { - it('should return blacklist addresses', () => { - const result = getImplicitBlacklist(sampleBlacklistLeaf) - expect(result).toEqual([testAddress2, testAddress3]) - }) - - it('should return blacklist from branch', () => { - const result = getImplicitBlacklist(sampleCompleteTopology) - expect(result).toEqual([testAddress2, testAddress3]) - }) - - it('should return null when no blacklist present', () => { - const result = getImplicitBlacklist(sampleSessionPermissionsLeaf) - expect(result).toBe(null) - }) - }) - - describe('getImplicitBlacklistLeaf', () => { - it('should return blacklist leaf', () => { - const result = getImplicitBlacklistLeaf(sampleBlacklistLeaf) - expect(result).toBe(sampleBlacklistLeaf) - }) - - it('should return blacklist leaf from branch', () => { - const result = getImplicitBlacklistLeaf(sampleCompleteTopology) - expect(result).toBe(sampleBlacklistLeaf) - }) - - it('should return null when no blacklist present', () => { - const result = getImplicitBlacklistLeaf(sampleSessionPermissionsLeaf) - expect(result).toBe(null) - }) - - it('should throw for multiple blacklists', () => { - const multipleBlacklist = [sampleBlacklistLeaf, sampleBlacklistLeaf, sampleIdentitySignerLeaf] as SessionBranch - expect(() => getImplicitBlacklistLeaf(multipleBlacklist)).toThrow('Multiple blacklists') - }) - }) - - describe('getSessionPermissions', () => { - it('should return session permissions for matching address', () => { - const result = getSessionPermissions(sampleSessionPermissionsLeaf, testAddress1) - expect(result).toBe(sampleSessionPermissionsLeaf) - }) - - it('should return null for non-matching address', () => { - const result = getSessionPermissions(sampleSessionPermissionsLeaf, testAddress2) - expect(result).toBe(null) - }) - - it('should find session permissions in branch', () => { - const result = getSessionPermissions(sampleCompleteTopology, testAddress1) - expect(result).toBe(sampleSessionPermissionsLeaf) - }) - - it('should return null when session not found in branch', () => { - const result = getSessionPermissions(sampleBranch, testAddress1) - expect(result).toBe(null) - }) - }) - - describe('getExplicitSigners', () => { - it('should return empty array for topology without session permissions', () => { - const result = getExplicitSigners(sampleBranch) - expect(result).toEqual([]) - }) - - it('should return signer addresses from session permissions', () => { - const result = getExplicitSigners(sampleCompleteTopology) - expect(result).toEqual([testAddress1]) - }) - - it('should return multiple signers from complex topology', () => { - const anotherSession: SessionPermissionsLeaf = { - type: 'session-permissions', - signer: testAddress2, - chainId: ChainId.MAINNET, - valueLimit: 500000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 1800), - permissions: [samplePermission], - } - const complexTopology = [sampleCompleteTopology, anotherSession] as SessionBranch - const result = getExplicitSigners(complexTopology) - expect(result).toContain(testAddress1) - expect(result).toContain(testAddress2) - }) - }) - }) - - describe('Leaf Encoding and Decoding', () => { - describe('encodeLeafToGeneric', () => { - it('should encode session permissions leaf', () => { - const result = encodeLeafToGeneric(sampleSessionPermissionsLeaf) - expect(result.type).toBe('leaf') - expect(result.value).toBeInstanceOf(Uint8Array) - expect(result.value[0]).toBe(SESSIONS_FLAG_PERMISSIONS) - }) - - it('should encode blacklist leaf', () => { - const result = encodeLeafToGeneric(sampleBlacklistLeaf) - expect(result.type).toBe('leaf') - expect(result.value).toBeInstanceOf(Uint8Array) - expect(result.value[0]).toBe(SESSIONS_FLAG_BLACKLIST) - }) - - it('should encode identity signer leaf', () => { - const result = encodeLeafToGeneric(sampleIdentitySignerLeaf) - expect(result.type).toBe('leaf') - expect(result.value).toBeInstanceOf(Uint8Array) - expect(result.value[0]).toBe(SESSIONS_FLAG_IDENTITY_SIGNER) - }) - - it('should throw for invalid leaf', () => { - expect(() => encodeLeafToGeneric({} as any)).toThrow('Invalid leaf') - }) - }) - - describe('decodeLeafFromBytes', () => { - it('should decode blacklist leaf', () => { - const encoded = Bytes.concat( - Bytes.fromNumber(SESSIONS_FLAG_BLACKLIST), - Bytes.fromHex(testAddress2), - Bytes.fromHex(testAddress3), - ) - const result = decodeLeafFromBytes(encoded) - expect(result.type).toBe('implicit-blacklist') - expect((result as ImplicitBlacklistLeaf).blacklist).toEqual([testAddress2, testAddress3]) - }) - - it('should decode identity signer leaf', () => { - const encoded = Bytes.concat(Bytes.fromNumber(SESSIONS_FLAG_IDENTITY_SIGNER), Bytes.fromHex(testAddress1)) - const result = decodeLeafFromBytes(encoded) - expect(result.type).toBe('identity-signer') - expect((result as IdentitySignerLeaf).identitySigner).toBe(testAddress1) - }) - - it('should decode session permissions leaf', () => { - // Use the actual encoding from sampleSessionPermissionsLeaf - const encoded = encodeLeafToGeneric(sampleSessionPermissionsLeaf) - const result = decodeLeafFromBytes(encoded.value) - expect(result.type).toBe('session-permissions') - expect((result as SessionPermissionsLeaf).signer).toBe(testAddress1) - }) - - it('should throw for invalid flag', () => { - const invalidEncoded = Bytes.fromNumber(255) // Invalid flag - expect(() => decodeLeafFromBytes(invalidEncoded)).toThrow('Invalid leaf') - }) - }) - - describe('Round-trip encoding/decoding', () => { - it('should handle round-trip for blacklist leaf', () => { - const encoded = encodeLeafToGeneric(sampleBlacklistLeaf) - const decoded = decodeLeafFromBytes(encoded.value) - expect(decoded).toEqual(sampleBlacklistLeaf) - }) - - it('should handle round-trip for identity signer leaf', () => { - const encoded = encodeLeafToGeneric(sampleIdentitySignerLeaf) - const decoded = decodeLeafFromBytes(encoded.value) - expect(decoded).toEqual(sampleIdentitySignerLeaf) - }) - }) - }) - - describe('Configuration Tree Conversion', () => { - describe('sessionsTopologyToConfigurationTree', () => { - it('should convert session leaf to generic tree leaf', () => { - const result = sessionsTopologyToConfigurationTree(sampleSessionPermissionsLeaf) - expect(result).toHaveProperty('type', 'leaf') - expect(result).toHaveProperty('value') - }) - - it('should convert session node to generic tree node', () => { - const result = sessionsTopologyToConfigurationTree(testNode) - expect(result).toBe(testNode) - }) - - it('should convert session branch to generic tree branch', () => { - const result = sessionsTopologyToConfigurationTree(sampleBranch) - expect(Array.isArray(result)).toBe(true) - expect(result).toHaveLength(2) - }) - - it('should throw for invalid topology', () => { - expect(() => sessionsTopologyToConfigurationTree({} as any)).toThrow('Invalid topology') - }) - }) - - describe('configurationTreeToSessionsTopology', () => { - it('should convert generic tree branch to session branch', () => { - const genericBranch = sampleBranch.map(sessionsTopologyToConfigurationTree) as any - const result = configurationTreeToSessionsTopology(genericBranch) - expect(Array.isArray(result)).toBe(true) - expect(result).toHaveLength(2) - }) - - it('should throw for unknown node in configuration tree', () => { - expect(() => configurationTreeToSessionsTopology(testNode)).toThrow('Unknown in configuration tree') - }) - - it('should convert generic tree leaf to session leaf', () => { - const genericLeaf = sessionsTopologyToConfigurationTree(sampleBlacklistLeaf) - const result = configurationTreeToSessionsTopology(genericLeaf) - expect(result).toEqual(sampleBlacklistLeaf) - }) - }) - }) - - describe('Sessions Topology Encoding and Decoding', () => { - describe('encodeSessionsTopology', () => { - it('should encode session permissions leaf', () => { - const result = encodeSessionsTopology(sampleSessionPermissionsLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] >> 4).toBe(SESSIONS_FLAG_PERMISSIONS) - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(sampleSessionPermissionsLeaf) - }) - - it('should encode session node', () => { - const result = encodeSessionsTopology(testNode) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] >> 4).toBe(SESSIONS_FLAG_NODE) - expect(result.length).toBe(33) // 1 flag byte + 32 hash bytes - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(testNode) - }) - - it('should encode blacklist leaf', () => { - const result = encodeSessionsTopology(sampleBlacklistLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] >> 4).toBe(SESSIONS_FLAG_BLACKLIST) - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(sampleBlacklistLeaf) - }) - - it('should encode large blacklist leaf', () => { - const blacklistCount = 1000 - const largeBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: Array(blacklistCount).fill(testAddress1), - } - const result = encodeSessionsTopology(largeBlacklist) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]).toBe((SESSIONS_FLAG_BLACKLIST << 4) | 0x0f) // Encoded large size flag - expect(Bytes.toNumber(result.slice(1, 3))).toBe(blacklistCount) - expect(result.slice(3)).toEqual( - Bytes.concat(...largeBlacklist.blacklist.map((b) => Bytes.padLeft(Bytes.fromHex(b), 20))), - ) - expect(result.length).toBe(3 + blacklistCount * 20) - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(largeBlacklist) - }) - - it('should encode identity signer leaf', () => { - const result = encodeSessionsTopology(sampleIdentitySignerLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] >> 4).toBe(SESSIONS_FLAG_IDENTITY_SIGNER) - expect(result.length).toBe(21) // 1 flag byte + 20 address bytes - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(sampleIdentitySignerLeaf) - }) - - it('should encode session branch', () => { - const result = encodeSessionsTopology(sampleBranch) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] >> 4).toBe(SESSIONS_FLAG_BRANCH) - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(sampleBranch) - }) - - it('should handle large blacklist with extended encoding', () => { - const largeBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: Array(20).fill(testAddress1), // Large blacklist - } - const result = encodeSessionsTopology(largeBlacklist) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0] & 0x0f).toBe(0x0f) // Extended encoding flag - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(largeBlacklist) - }) - - it('should handle complete topology', () => { - const result = encodeSessionsTopology(sampleCompleteTopology) - expect(result).toBeInstanceOf(Uint8Array) - const decoded = decodeSessionsTopology(result) - expect(decoded).toEqual(sampleCompleteTopology) - }) - - it('should throw for blacklist too large', () => { - const tooLargeBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: Array(70000).fill(testAddress1), // Way too large - } - expect(() => encodeSessionsTopology(tooLargeBlacklist)).toThrow('Blacklist too large') - }) - - it('should throw for branch too large', () => { - // Create a branch that would be too large when encoded - make it much simpler - const hugeBranch = [sampleSessionPermissionsLeaf, sampleBlacklistLeaf] as SessionBranch - // This won't actually throw since the encoding isn't that large, so just check it encodes - const result = encodeSessionsTopology(hugeBranch) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should throw for invalid topology', () => { - expect(() => encodeSessionsTopology({} as any)).toThrow('Invalid topology') - }) - }) - }) - - describe('JSON Serialization', () => { - describe('sessionsTopologyToJson', () => { - it('should serialize simple leaf to JSON', () => { - const result = sessionsTopologyToJson(sampleBlacklistLeaf) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed.type).toBe('implicit-blacklist') - expect(parsed.blacklist).toEqual([testAddress2, testAddress3]) - }) - - it('should serialize session node to JSON', () => { - const result = sessionsTopologyToJson(testNode) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(parsed).toBe(testNode) - }) - - it('should serialize branch to JSON', () => { - const result = sessionsTopologyToJson(sampleBranch) - expect(typeof result).toBe('string') - - const parsed = JSON.parse(result) - expect(Array.isArray(parsed)).toBe(true) - expect(parsed).toHaveLength(2) - }) - - it('should throw for invalid topology', () => { - expect(() => sessionsTopologyToJson({} as any)).toThrow('Invalid topology') - }) - }) - - describe('sessionsTopologyFromJson', () => { - it('should deserialize blacklist leaf from JSON', () => { - const json = sessionsTopologyToJson(sampleBlacklistLeaf) - const result = sessionsTopologyFromJson(json) - expect(result).toEqual(sampleBlacklistLeaf) - }) - - it('should deserialize identity signer leaf from JSON', () => { - const json = sessionsTopologyToJson(sampleIdentitySignerLeaf) - const result = sessionsTopologyFromJson(json) - expect(result).toEqual(sampleIdentitySignerLeaf) - }) - - it('should deserialize session node from JSON', () => { - const json = sessionsTopologyToJson(testNode) - const result = sessionsTopologyFromJson(json) - expect(result).toBe(testNode) - }) - - it('should deserialize branch from JSON', () => { - const json = sessionsTopologyToJson(sampleBranch) - const result = sessionsTopologyFromJson(json) - expect(Array.isArray(result)).toBe(true) - expect(result).toHaveLength(2) - }) - - it('should handle round-trip serialization', () => { - const json = sessionsTopologyToJson(sampleCompleteTopology) - const result = sessionsTopologyFromJson(json) - expect(isCompleteSessionsTopology(result)).toBe(true) - }) - - it('should throw for invalid JSON', () => { - expect(() => sessionsTopologyFromJson('invalid json')).toThrow() - }) - - it('should throw for invalid topology in JSON', () => { - expect(() => sessionsTopologyFromJson('{"invalid": "topology"}')).toThrow('Invalid topology') - }) - }) - }) - - describe('Topology Operations', () => { - describe('removeExplicitSession', () => { - it('should remove matching session permissions', () => { - const result = removeExplicitSession(sampleSessionPermissionsLeaf, testAddress1) - expect(result).toBe(null) - }) - - it('should return unchanged for non-matching session', () => { - const result = removeExplicitSession(sampleSessionPermissionsLeaf, testAddress2) - expect(result).toBe(sampleSessionPermissionsLeaf) - }) - - it('should remove session from branch', () => { - const result = removeExplicitSession(sampleCompleteTopology, testAddress1) - expect(result).toEqual([sampleBlacklistLeaf, sampleIdentitySignerLeaf]) - }) - - it('should collapse single child branch', () => { - const branchWithOneSession = [sampleSessionPermissionsLeaf, sampleBlacklistLeaf] as SessionBranch - const result = removeExplicitSession(branchWithOneSession, testAddress1) - expect(result).toBe(sampleBlacklistLeaf) - }) - - it('should return null for empty branch', () => { - const result = removeExplicitSession( - [sampleSessionPermissionsLeaf, sampleBlacklistLeaf] as SessionBranch, - testAddress1, - ) - expect(result).toBe(sampleBlacklistLeaf) - }) - - it('should return other leaves unchanged', () => { - const result = removeExplicitSession(sampleBlacklistLeaf, testAddress1) - expect(result).toBe(sampleBlacklistLeaf) - }) - }) - - describe('addExplicitSession', () => { - it('should add new session to topology', () => { - const newSession: SessionPermissions = { - signer: testAddress2, - chainId: ChainId.MAINNET, - valueLimit: 500000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 1800), - permissions: [samplePermission], - } - - const result = addExplicitSession(sampleBranch, newSession) - expect(isSessionsTopology(result)).toBe(true) - - const foundSession = getSessionPermissions(result, testAddress2) - expect(foundSession).toBeTruthy() - expect(foundSession?.signer).toBe(testAddress2) - }) - - it('should throw when session already exists', () => { - expect(() => addExplicitSession(sampleCompleteTopology, sampleSessionPermissionsLeaf)).toThrow( - 'Session already exists', - ) - }) - }) - - describe('mergeSessionsTopologies', () => { - it('should merge two topologies into branch', () => { - const result = mergeSessionsTopologies(sampleBlacklistLeaf, sampleIdentitySignerLeaf) - expect(Array.isArray(result)).toBe(true) - expect(result).toHaveLength(2) - expect(result[0]).toBe(sampleBlacklistLeaf) - expect(result[1]).toBe(sampleIdentitySignerLeaf) - }) - }) - - describe('balanceSessionsTopology', () => { - it('should balance topology with blacklist and identity signer', () => { - const result = balanceSessionsTopology(sampleCompleteTopology) - expect(isSessionsTopology(result)).toBe(true) - - const blacklist = getImplicitBlacklist(result) - const identitySigners = getIdentitySigners(result) - expect(blacklist).toBeTruthy() - expect(identitySigners).toBeTruthy() - }) - }) - - describe('cleanSessionsTopology', () => { - it('should remove expired sessions', () => { - const expiredSession: SessionPermissionsLeaf = { - type: 'session-permissions', - signer: testAddress2, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) - 3600), // Expired 1 hour ago - permissions: [samplePermission], - } - - const topologyWithExpired = [sampleBlacklistLeaf, sampleIdentitySignerLeaf, expiredSession] as SessionBranch - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - - const result = cleanSessionsTopology(topologyWithExpired, currentTime) - expect(result).toBeTruthy() - - const foundSession = getSessionPermissions(result!, testAddress2) - expect(foundSession).toBe(null) - }) - - it('should keep valid sessions', () => { - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - const result = cleanSessionsTopology(sampleCompleteTopology, currentTime) - - expect(result).toBeTruthy() - const foundSession = getSessionPermissions(result!, testAddress1) - expect(foundSession).toBeTruthy() - }) - - it('should return null for empty topology after cleaning', () => { - const expiredSession: SessionPermissionsLeaf = { - type: 'session-permissions', - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) - 3600), // Expired - permissions: [samplePermission], - } - - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - const result = cleanSessionsTopology(expiredSession, currentTime) - expect(result).toBe(null) - }) - - it('should return session node unchanged', () => { - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - const result = cleanSessionsTopology(testNode, currentTime) - expect(result).toBe(testNode) - }) - - it('should keep identity signer and blacklist leaves', () => { - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - - const identityResult = cleanSessionsTopology(sampleIdentitySignerLeaf, currentTime) - expect(identityResult).toBe(sampleIdentitySignerLeaf) - - const blacklistResult = cleanSessionsTopology(sampleBlacklistLeaf, currentTime) - expect(blacklistResult).toBe(sampleBlacklistLeaf) - }) - - it('should collapse single child branches', () => { - const singleChildBranch = [sampleBlacklistLeaf, sampleIdentitySignerLeaf] as SessionBranch - const currentTime = BigInt(Math.floor(Date.now() / 1000)) - - const result = cleanSessionsTopology(singleChildBranch, currentTime) - expect(result).toBeTruthy() - }) - }) - - describe('minimiseSessionsTopology', () => { - it('should convert unused sessions to nodes', () => { - const result = minimiseSessionsTopology(sampleCompleteTopology, [], []) - expect(isSessionsTopology(result)).toBe(true) - - // The result should be minimized but still a valid topology - expect(result).toBeTruthy() - }) - - it('should preserve explicit signers', () => { - const result = minimiseSessionsTopology(sampleCompleteTopology, [testAddress1], []) - expect(isSessionsTopology(result)).toBe(true) - - // Should preserve the session permissions since address is in explicit signers - const foundSession = getSessionPermissions(result, testAddress1) - expect(foundSession).toBeTruthy() - }) - - it('should handle identity signer leaf', () => { - const result = minimiseSessionsTopology(sampleIdentitySignerLeaf, [], []) - expect(result).toBe(sampleIdentitySignerLeaf) // Never roll up identity signer - }) - - it('should handle session node', () => { - const result = minimiseSessionsTopology(testNode, [], []) - expect(result).toBe(testNode) // Already encoded and hashed - }) - - it('should throw for invalid topology', () => { - expect(() => minimiseSessionsTopology({} as any, [], [])).toThrow('Invalid topology') - }) - - it('should minimize topology with multiple identity signers but keep only the specified one', () => { - // Create multiple identity signer leaves - const identitySigner1: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress1, - } - const identitySigner2: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress2, - } - const identitySigner3: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress3, - } - - // Create topology with multiple identity signers - const topologyWithMultipleIdentitySigners: SessionBranch = [ - sampleBlacklistLeaf, - identitySigner1, - identitySigner2, - identitySigner3, - sampleSessionPermissionsLeaf, - ] - - // Minimize with only testAddress2 as the identity signer - const result = minimiseSessionsTopology( - topologyWithMultipleIdentitySigners, - [], // no explicit signers - [], // no implicit signers - testAddress2, // only keep this identity signer - ) - - expect(isSessionsTopology(result)).toBe(true) - - // Get all identity signers from the result - const identitySigners = getIdentitySigners(result) - - // Should only contain the specified identity signer - expect(identitySigners).toEqual([testAddress2]) - expect(identitySigners).not.toContain(testAddress1) - expect(identitySigners).not.toContain(testAddress3) - - // Verify the result is still a valid topology - expect(isSessionsTopology(result)).toBe(true) - }) - - it('should minimize deeply nested topology with multiple identity signers but keep only the specified one', () => { - // Create multiple identity signer leaves - const identitySigner1: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress1, - } - const identitySigner2: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress2, - } - const identitySigner3: IdentitySignerLeaf = { - type: 'identity-signer', - identitySigner: testAddress3, - } - - // Create additional session permissions for nesting - const sessionPermissions2: SessionPermissionsLeaf = { - type: 'session-permissions', - signer: testAddress2, - chainId: ChainId.MAINNET, - valueLimit: 500000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 1800), - permissions: [samplePermission], - } - - const sessionPermissions3: SessionPermissionsLeaf = { - type: 'session-permissions', - signer: testAddress3, - chainId: ChainId.MAINNET, - valueLimit: 750000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 2700), - permissions: [samplePermission], - } - - // Create a deeply nested topology structure - // Level 1: Main branch - // Level 2: Nested branches containing different combinations - const deeplyNestedTopology: SessionBranch = [ - // First nested branch: blacklist + identity signer 1 - [ - sampleBlacklistLeaf, - identitySigner1, - sampleSessionPermissionsLeaf, // testAddress1 session - ], - // Second nested branch: identity signer 2 + session permissions 2 - [ - identitySigner2, - sessionPermissions2, // testAddress2 session - ], - // Third nested branch: identity signer 3 + session permissions 3 - [ - identitySigner3, - sessionPermissions3, // testAddress3 session - ], - ] - - // Minimize with only testAddress2 as the identity signer - const result = minimiseSessionsTopology( - deeplyNestedTopology, - [], // no explicit signers - [], // no implicit signers - testAddress2, // only keep this identity signer - ) - - expect(isSessionsTopology(result)).toBe(true) - - // Get all identity signers from the result - const identitySigners = getIdentitySigners(result) - - // Should only contain the specified identity signer - expect(identitySigners).toEqual([testAddress2]) - expect(identitySigners).not.toContain(testAddress1) - expect(identitySigners).not.toContain(testAddress3) - - // Verify the result is still a valid topology - expect(isSessionsTopology(result)).toBe(true) - - // Verify that the nested structure is properly minimized - // The result should be a branch with hashed nodes and the preserved identity signer - if (Array.isArray(result)) { - // Should have some components (hashed nodes and the preserved identity signer) - expect(result.length).toBeGreaterThan(0) - } - }) - }) - - describe('addToImplicitBlacklist', () => { - it('should add address to blacklist', () => { - const newAddress = '0x1111111111111111111111111111111111111111' as Address.Address - const result = addToImplicitBlacklist(sampleCompleteTopology, newAddress) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist).toContain(newAddress) - expect(blacklist).toHaveLength(3) - }) - - it('should not add duplicate address', () => { - const result = addToImplicitBlacklist(sampleCompleteTopology, testAddress2) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist?.filter((addr) => addr === testAddress2)).toHaveLength(1) - }) - - it('should throw when no blacklist found', () => { - expect(() => addToImplicitBlacklist(sampleSessionPermissionsLeaf, testAddress1)).toThrow('No blacklist found') - }) - }) - - describe('removeFromImplicitBlacklist', () => { - it('should remove address from blacklist', () => { - // Create a topology with a fresh blacklist to avoid side effects - const freshBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: [testAddress2, testAddress3], - } - const testTopology = [freshBlacklist, sampleIdentitySignerLeaf, sampleSessionPermissionsLeaf] as SessionBranch - - const result = removeFromImplicitBlacklist(testTopology, testAddress2) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist).not.toContain(testAddress2) - expect(blacklist).toContain(testAddress3) - expect(blacklist).toHaveLength(1) - }) - - it('should handle non-existent address gracefully', () => { - const nonExistentAddress = '0x1111111111111111111111111111111111111111' as Address.Address - // Create a copy since removeFromImplicitBlacklist mutates the original - const topologyClone = structuredClone(sampleCompleteTopology) - const result = removeFromImplicitBlacklist(topologyClone, nonExistentAddress) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist).toContain(testAddress2) - expect(blacklist).toContain(testAddress3) - expect(blacklist).toHaveLength(2) - }) - - it('should throw when no blacklist found', () => { - expect(() => removeFromImplicitBlacklist(sampleSessionPermissionsLeaf, testAddress1)).toThrow( - 'No blacklist found', - ) - }) - }) - - describe('emptySessionsTopology', () => { - it('should create empty topology with identity signer', () => { - const result = emptySessionsTopology(testAddress1) - - expect(isCompleteSessionsTopology(result)).toBe(true) - - const identitySigners = getIdentitySigners(result) - expect(identitySigners).toEqual([testAddress1]) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist).toEqual([]) - - const explicitSigners = getExplicitSigners(result) - expect(explicitSigners).toEqual([]) - }) - - it('should create empty topology with multiple identity signers', () => { - const result = emptySessionsTopology([testAddress1, testAddress2]) - - expect(isCompleteSessionsTopology(result)).toBe(true) - - const identitySigners = getIdentitySigners(result) - expect(identitySigners).toEqual([testAddress1, testAddress2]) - - const blacklist = getImplicitBlacklist(result) - expect(blacklist).toEqual([]) - - const explicitSigners = getExplicitSigners(result) - expect(explicitSigners).toEqual([]) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle empty blacklist', () => { - const emptyBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: [], - } - - expect(isSessionsTopology(emptyBlacklist)).toBe(true) - - const encoded = encodeSessionsTopology(emptyBlacklist) - expect(encoded).toBeInstanceOf(Uint8Array) - expect(encoded[0] & 0x0f).toBe(0) // Length should be 0 - }) - - it('should handle complex nested topology', () => { - // Create fresh blacklist for this test to avoid contamination from other tests - const freshBlacklist: ImplicitBlacklistLeaf = { - type: 'implicit-blacklist', - blacklist: [testAddress2, testAddress3], - } - - const nestedTopology = [ - [freshBlacklist, sampleIdentitySignerLeaf] as SessionBranch, - sampleSessionPermissionsLeaf, - ] as SessionBranch - - expect(isSessionsTopology(nestedTopology)).toBe(true) - expect(isCompleteSessionsTopology(nestedTopology)).toBe(true) - - const identitySigners = getIdentitySigners(nestedTopology) - expect(identitySigners).toEqual([testAddress1]) - - const blacklist = getImplicitBlacklist(nestedTopology) - expect(blacklist).toContain(testAddress2) - expect(blacklist).toContain(testAddress3) - expect(blacklist).toHaveLength(2) - }) - - it('should handle single-element branch', () => { - const singleElementBranch = [sampleBlacklistLeaf] - expect(isSessionsTopology(singleElementBranch)).toBe(false) // Branch needs at least 2 elements - }) - - it('should handle large session permissions', () => { - const largePermissions: SessionPermissions = { - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 2n ** 256n - 1n, // Maximum uint256 - deadline: BigInt(Math.floor(Date.now() / 1000) + 365 * 24 * 3600), // 1 year from now - permissions: [samplePermission], - } - - const largeSessionLeaf: SessionPermissionsLeaf = { - type: 'session-permissions', - ...largePermissions, - } - - expect(isSessionsTopology(largeSessionLeaf)).toBe(true) - - const encoded = encodeSessionsTopology(largeSessionLeaf) - expect(encoded).toBeInstanceOf(Uint8Array) - }) - }) - - describe('Integration Tests', () => { - it('should handle complete workflow from creation to serialization', () => { - // Create empty topology - const empty = emptySessionsTopology(testAddress1) - - // Add a session - const session: SessionPermissions = { - signer: testAddress2, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [samplePermission], - } - const withSession = addExplicitSession(empty, session) - - // Add to blacklist - const withBlacklist = addToImplicitBlacklist(withSession, testAddress3) - - // Verify completeness - expect(isCompleteSessionsTopology(withBlacklist)).toBe(true) - - // Serialize to JSON - const json = sessionsTopologyToJson(withBlacklist) - expect(typeof json).toBe('string') - - // Deserialize from JSON - const deserialized = sessionsTopologyFromJson(json) - expect(isCompleteSessionsTopology(deserialized)).toBe(true) - - // Verify data integrity - expect(getIdentitySigners(deserialized)).toEqual([testAddress1]) - expect(getImplicitBlacklist(deserialized)).toContain(testAddress3) - expect(getSessionPermissions(deserialized, testAddress2)).toBeTruthy() - }) - - it('should handle cleanup and removal operations', () => { - // Start with complete topology - let topology: SessionsTopology = sampleCompleteTopology - - // Remove a session - topology = removeExplicitSession(topology, testAddress1)! - expect(getSessionPermissions(topology, testAddress1)).toBe(null) - - // Remove from blacklist - topology = removeFromImplicitBlacklist(topology, testAddress2) - expect(getImplicitBlacklist(topology)).not.toContain(testAddress2) - - // Clean expired sessions (none should be expired in this case) - const cleaned = cleanSessionsTopology(topology) - expect(cleaned).toBeTruthy() - - // Minimize topology - const minimized = minimiseSessionsTopology(cleaned!, [], []) - expect(isSessionsTopology(minimized)).toBe(true) - }) - }) -}) diff --git a/packages/wallet/primitives/test/session-signature.test.ts b/packages/wallet/primitives/test/session-signature.test.ts deleted file mode 100644 index a1fd0fe233..0000000000 --- a/packages/wallet/primitives/test/session-signature.test.ts +++ /dev/null @@ -1,916 +0,0 @@ -import { Address, Bytes, Hex } from 'ox' -import { describe, expect, it } from 'vitest' - -import { Attestation } from '../src/attestation.js' -import { ChainId } from '../src/network.js' -import * as Payload from '../src/payload.js' -import { ParameterOperation } from '../src/permission.js' -import { minimiseSessionsTopology, SessionsTopology } from '../src/session-config.js' -import { - decodeSessionSignature, - encodeSessionCallSignatureForJson, - encodeSessionSignature, - ExplicitSessionCallSignature, - hashPayloadWithCallIdx, - ImplicitSessionCallSignature, - isExplicitSessionCallSignature, - isImplicitSessionCallSignature, - SessionCallSignature, - sessionCallSignatureFromJson, - sessionCallSignatureFromParsed, - sessionCallSignatureToJson, -} from '../src/session-signature.js' -import { RSY } from '../src/signature.js' -import { Extensions } from '../src/index.js' - -describe('Session Signature', () => { - // Test data - const testAddress1: Address.Address = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' - const testAddress2: Address.Address = '0x8ba1f109551bd432803012645aac136c776056c0' - const testChainId = ChainId.MAINNET - const testSpace = 0n - const testNonce = 1n - - const sampleRSY: RSY = { - r: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn, - s: 0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321n, - yParity: 1, - } - - const sampleRSY2: RSY = { - r: 0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefn, - s: 0x1234561234561234561234561234561234561234561234561234561234561234n, - yParity: 0, - } - - const sampleAttestation: Attestation = { - approvedSigner: testAddress1, - identityType: Bytes.fromHex('0x00000001'), - issuerHash: Bytes.fromHex('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'), - audienceHash: Bytes.fromHex('0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef'), - applicationData: Bytes.fromString('test application data'), - authData: { - redirectUrl: 'https://example.com/callback', - issuedAt: 123456789n, - }, - } - - const sampleImplicitSignature: ImplicitSessionCallSignature = { - attestation: sampleAttestation, - identitySignature: sampleRSY, - sessionSignature: sampleRSY2, - } - - const sampleExplicitSignature: ExplicitSessionCallSignature = { - permissionIndex: 5n, - sessionSignature: sampleRSY, - } - - const sampleCall: Payload.Call = { - to: testAddress1, - value: 1000000000000000000n, // 1 ETH - data: '0x1234567890abcdef', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - const samplePayload: Payload.Calls = { - type: 'call', - space: testSpace, - nonce: testNonce, - calls: [sampleCall], - } - - // Create a complete sessions topology for testing - const completeTopology: SessionsTopology = [ - { - type: 'implicit-blacklist', - blacklist: [testAddress2], - }, - { - type: 'identity-signer', - identitySigner: testAddress1, - }, - { - type: 'session-permissions', - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [ - { - target: testAddress2, - rules: [ - { - cumulative: false, - operation: ParameterOperation.EQUAL, - value: Bytes.fromHex('0x'), - offset: 0n, - mask: Bytes.fromHex('0xffffffff00000000000000000000000000000000000000000000000000000000'), - }, - ], - }, - ], - }, - ] - - describe('Type Guards', () => { - describe('isImplicitSessionCallSignature', () => { - it('should return true for implicit session call signature', () => { - expect(isImplicitSessionCallSignature(sampleImplicitSignature)).toBe(true) - }) - - it('should return false for explicit session call signature', () => { - expect(isImplicitSessionCallSignature(sampleExplicitSignature)).toBe(false) - }) - - it('should return false for invalid objects', () => { - expect(isImplicitSessionCallSignature({} as any)).toBe(false) - expect(isImplicitSessionCallSignature({ attestation: sampleAttestation } as any)).toBe(false) // Missing other fields - expect(isImplicitSessionCallSignature({ identitySignature: sampleRSY } as any)).toBe(false) // Missing other fields - }) - }) - - describe('isExplicitSessionCallSignature', () => { - it('should return true for explicit session call signature', () => { - expect(isExplicitSessionCallSignature(sampleExplicitSignature)).toBe(true) - }) - - it('should return false for implicit session call signature', () => { - expect(isExplicitSessionCallSignature(sampleImplicitSignature)).toBe(false) - }) - - it('should return false for invalid objects', () => { - expect(isExplicitSessionCallSignature({} as any)).toBe(false) - expect(isExplicitSessionCallSignature({ permissionIndex: 5n } as any)).toBe(false) // Missing sessionSignature - expect(isExplicitSessionCallSignature({ sessionSignature: sampleRSY } as any)).toBe(false) // Missing permissionIndex - }) - }) - }) - - describe('JSON Serialization', () => { - describe('sessionCallSignatureToJson', () => { - it('should serialize implicit session call signature to JSON', () => { - // Skip actual JSON.stringify to avoid BigInt issues, just test the structure - const encoded = encodeSessionCallSignatureForJson(sampleImplicitSignature) - expect(encoded.attestation).toBeDefined() - expect(encoded.identitySignature).toBeDefined() - expect(encoded.sessionSignature).toBeDefined() - }) - - it('should serialize explicit session call signature to JSON', () => { - // Skip actual JSON.stringify to avoid BigInt issues, just test the structure - const encoded = encodeSessionCallSignatureForJson(sampleExplicitSignature) - expect(encoded.permissionIndex).toBe(5n) - expect(encoded.sessionSignature).toBeDefined() - }) - - it('should handle actual JSON serialization with custom replacer', () => { - // Test the actual JSON.stringify path (line 42) - try { - const jsonStr = sessionCallSignatureToJson(sampleExplicitSignature) - expect(typeof jsonStr).toBe('string') - expect(jsonStr.length).toBeGreaterThan(0) - - // Should be able to parse it back - const parsed = JSON.parse(jsonStr) - expect(parsed.permissionIndex).toBeDefined() - expect(parsed.sessionSignature).toBeDefined() - } catch (error) { - // If JSON.stringify fails due to BigInt, that's expected in some environments - // The important thing is that the function exists and attempts the operation - expect(error).toBeDefined() - } - }) - }) - - describe('encodeSessionCallSignatureForJson', () => { - it('should encode implicit session call signature for JSON', () => { - const result = encodeSessionCallSignatureForJson(sampleImplicitSignature) - - expect(result.attestation).toBeDefined() - expect(result.identitySignature).toBeDefined() - expect(result.sessionSignature).toBeDefined() - expect(typeof result.identitySignature).toBe('string') - expect(result.identitySignature).toContain(':') // RSV format - }) - - it('should encode explicit session call signature for JSON', () => { - const result = encodeSessionCallSignatureForJson(sampleExplicitSignature) - - expect(result.permissionIndex).toBe(5n) - expect(result.sessionSignature).toBeDefined() - expect(typeof result.sessionSignature).toBe('string') - expect(result.sessionSignature).toContain(':') // RSV format - }) - - it('should throw for invalid call signature', () => { - expect(() => encodeSessionCallSignatureForJson({} as any)).toThrow('Invalid call signature') - }) - }) - - describe('sessionCallSignatureFromJson', () => { - it('should throw for invalid JSON', () => { - expect(() => sessionCallSignatureFromJson('invalid json')).toThrow() - }) - }) - - describe('sessionCallSignatureFromParsed', () => { - it('should throw for invalid call signature object', () => { - expect(() => sessionCallSignatureFromParsed({})).toThrow('Invalid call signature') - }) - }) - - describe('Round-trip serialization', () => { - it('should handle round-trip for explicit signature (encoding only)', () => { - // Just test encoding without full JSON round-trip due to BigInt serialization issues - const encoded = encodeSessionCallSignatureForJson(sampleExplicitSignature) - expect(encoded.permissionIndex).toBe(sampleExplicitSignature.permissionIndex) - expect(typeof encoded.sessionSignature).toBe('string') - }) - - it('should handle round-trip for implicit signature (encoding only)', () => { - // Just test encoding without full JSON round-trip due to BigInt serialization issues - const encoded = encodeSessionCallSignatureForJson(sampleImplicitSignature) - expect(encoded.attestation).toBeDefined() - expect(typeof encoded.identitySignature).toBe('string') - expect(typeof encoded.sessionSignature).toBe('string') - }) - }) - }) - - describe('RSY Signature Format', () => { - it('should handle RSY to RSV string conversion', () => { - // Test the encoding directly without JSON serialization - const encoded = encodeSessionCallSignatureForJson(sampleExplicitSignature) - // The format is r:s:v where r and s are decimal strings, not hex - expect(encoded.sessionSignature).toMatch(/^\d+:\d+:\d+$/) - }) - - it('should handle various yParity values', () => { - const signatures = [ - { ...sampleRSY, yParity: 0 }, - { ...sampleRSY, yParity: 1 }, - ] - - signatures.forEach((sig) => { - const callSig: ExplicitSessionCallSignature = { - permissionIndex: 1n, - sessionSignature: sig, - } - - const encoded = encodeSessionCallSignatureForJson(callSig) - expect(encoded.sessionSignature).toContain(':') - }) - }) - - it('should throw for invalid RSV format during parsing', () => { - const invalidFormats = [ - '0x123:0x456', // Missing v - '0x123:0x456:28:extra', // Too many parts - ] - - invalidFormats.forEach((format) => { - const invalidData = { permissionIndex: 1, sessionSignature: format } - expect(() => sessionCallSignatureFromParsed(invalidData)).toThrow() - }) - }) - }) - - describe('Signature Encoding and Decoding', () => { - describe('encode / decodeSessionCallSignatures', () => { - it('should encode single explicit session call signature', () => { - const callSignatures = [sampleExplicitSignature] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures.length).toBe(1) - const callSignature = decoded.callSignatures[0]! - if (!isExplicitSessionCallSignature(callSignature)) { - throw new Error('Call signature is not explicit') - } - expect(callSignature.permissionIndex).toBe(callSignatures[0]!.permissionIndex) - // The topology gets minimized during encoding, so we expect the minimized version - const minimizedTopology = minimiseSessionsTopology(completeTopology, [], [], testAddress1) - expect(decoded.topology).toEqual(minimizedTopology) - }) - - // Skip implicit signature tests that cause encoding issues - it.skip('should encode single implicit session call signature', () => { - const callSignatures = [sampleImplicitSignature] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures).toEqual(callSignatures) - expect(decoded.topology).toEqual(completeTopology) - }) - - it.skip('should encode multiple mixed session call signatures', () => { - const callSignatures = [sampleImplicitSignature, sampleExplicitSignature] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures).toEqual(callSignatures) - expect(decoded.topology).toEqual(completeTopology) - }) - - it.skip('should encode multiple implicit signatures with same attestation', () => { - const callSignatures = [ - sampleImplicitSignature, - { - ...sampleImplicitSignature, - sessionSignature: sampleRSY2, // Different session signature - }, - ] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures).toEqual(callSignatures) - const minimizedTopology = minimiseSessionsTopology(completeTopology, [], [], testAddress1) - expect(decoded.topology).toEqual(minimizedTopology) - }) - - it('should throw for incomplete topology', () => { - const incompleteTopology: SessionsTopology = [ - { - type: 'implicit-blacklist', - blacklist: [testAddress2], - }, - { - type: 'session-permissions', - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [ - { - target: testAddress2, - rules: [ - { - cumulative: false, - operation: ParameterOperation.EQUAL, - value: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000000000000000000'), - offset: 0n, - mask: Bytes.fromHex('0xffffffff00000000000000000000000000000000000000000000000000000000'), - }, - ], - }, - ], - }, - // Missing identity signer, but has 2 elements for valid SessionBranch - ] - - expect(() => encodeSessionSignature([sampleExplicitSignature], incompleteTopology, testAddress1)).toThrow( - 'Incomplete topology', - ) - }) - - it('should throw for too large permission index', () => { - const largeIndexSignature: ExplicitSessionCallSignature = { - permissionIndex: 128n, // Too large (MAX_PERMISSIONS_COUNT is 127) - sessionSignature: sampleRSY, - } - - expect(() => encodeSessionSignature([largeIndexSignature], completeTopology, testAddress1)).toThrow( - 'Permission index is too large', - ) - }) - - it('should handle explicit signers parameter', () => { - const callSignatures = [sampleExplicitSignature] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures.length).toBe(1) - const callSignature = decoded.callSignatures[0]! - if (!isExplicitSessionCallSignature(callSignature)) { - throw new Error('Call signature is not explicit') - } - expect(callSignature.permissionIndex).toBe(callSignatures[0]!.permissionIndex) - // The topology gets minimized during encoding, so we expect the minimized version - const minimizedTopology = minimiseSessionsTopology(completeTopology, [], [], testAddress1) - expect(decoded.topology).toEqual(minimizedTopology) - }) - - it('should handle implicit signers parameter', () => { - const callSignatures = [sampleExplicitSignature] - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1, [], [testAddress2]) - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - - const decoded = decodeSessionSignature(result) - expect(decoded.callSignatures.length).toBe(1) - const callSignature = decoded.callSignatures[0]! - if (!isExplicitSessionCallSignature(callSignature)) { - throw new Error('Call signature is not explicit') - } - expect(callSignature.permissionIndex).toBe(callSignatures[0]!.permissionIndex) - // The topology gets minimized during encoding, so we expect the minimized version - const minimizedTopology = minimiseSessionsTopology(completeTopology, [], [testAddress2], testAddress1) - expect(decoded.topology).toEqual(minimizedTopology) - }) - - it('should throw for invalid call signature type', () => { - const invalidSignature = {} as any - expect(() => encodeSessionSignature([invalidSignature], completeTopology, testAddress1)).toThrow( - 'Invalid call signature', - ) - }) - - it('should throw for identity signer not found', () => { - const callSignatures = [sampleExplicitSignature] - expect(() => - encodeSessionSignature(callSignatures, completeTopology, testAddress2, [], [testAddress2]), - ).toThrow('Identity signer not found') - }) - }) - }) - - describe('Helper Functions', () => { - describe('hashPayloadWithCallIdx', () => { - it('should hash call with replay protection parameters', () => { - const result = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - - expect(result).toMatch(/^0x[0-9a-f]{64}$/) // 32-byte hex string - expect(Hex.size(result)).toBe(32) - }) - - it('should produce different hashes for different chain IDs', () => { - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, ChainId.MAINNET) - const hash2 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, ChainId.POLYGON) - - expect(hash1).not.toBe(hash2) - }) - - it('should produce different hashes for different spaces', () => { - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx( - testAddress1, - { ...samplePayload, space: samplePayload.space + 1n }, - 0, - testChainId, - ) - - expect(hash1).not.toBe(hash2) - }) - - it('should produce different hashes for different nonces', () => { - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx( - testAddress1, - { ...samplePayload, nonce: samplePayload.nonce + 1n }, - 0, - testChainId, - ) - - expect(hash1).not.toBe(hash2) - }) - - it('should produce different hashes for different calls', () => { - const call2: Payload.Call = { - ...sampleCall, - value: 2000000000000000000n, // Different value - } - const payload2 = { ...samplePayload, calls: [call2] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx(testAddress1, payload2, 0, testChainId) - - expect(hash1).not.toBe(hash2) - }) - - it('should produce different hashes for different wallets', () => { - const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId) - - expect(hash1).not.toBe(hash2) - }) - - it('should NOT produce different hashes for different wallets when using deprecated hash encoding for Dev1 and Dev2', () => { - // This is ONLY for backward compatibility with Dev1 and Dev2 - // This is exploitable and should not be used in practice - const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) - const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId, Extensions.Dev2.sessions) - - expect(hash1).toBe(hash2) - }) - - it('should produce different hashes for different wallets when using deprecated hash encoding for Dev1/2, Rc3 and latest', () => { - // This is ONLY for backward compatibility with Rc3 - // This is exploitable and should not be used in practice - const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) - const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId, Extensions.Rc3.sessions) - const hash3 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId) - - expect(hash1).not.toBe(hash2) - expect(hash1).not.toBe(hash3) - expect(hash2).not.toBe(hash3) - }) - - it('should produce different hashes for same call at different index', () => { - const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 1, testChainId) - - expect(hash1).not.toBe(hash2) - }) - - it('should NOT produce different hashes for same call at different index if skipCallIdx is true', () => { - // This is ONLY for backward compatibility with Dev1 and Dev2 - // This is exploitable and should not be used in practice - const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) - const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 1, testChainId, Extensions.Dev1.sessions) - - expect(hash1).toBe(hash2) - }) - - it('should be deterministic', () => { - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - - expect(hash1).toBe(hash2) - }) - - it('should handle large numbers', () => { - const largeChainId = Number.MAX_SAFE_INTEGER - const largeSpace = 2n ** 16n - const largeNonce = 2n ** 24n - - const result = hashPayloadWithCallIdx( - testAddress1, - { ...samplePayload, space: largeSpace, nonce: largeNonce }, - 0, - largeChainId, - ) - expect(result).toMatch(/^0x[0-9a-f]{64}$/) - }) - - it('should handle zero values', () => { - const result = hashPayloadWithCallIdx(testAddress1, { ...samplePayload, space: 0n, nonce: 0n }, 0, 0) - expect(result).toMatch(/^0x[0-9a-f]{64}$/) - }) - - it('should handle call with empty data', () => { - const callWithEmptyData: Payload.Call = { - ...sampleCall, - data: '0x', - } - const payload = { ...samplePayload, calls: [callWithEmptyData] } - - const result = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) - expect(result).toMatch(/^0x[0-9a-f]{64}$/) - }) - - it('should handle call with delegate call flag', () => { - const delegateCall: Payload.Call = { - ...sampleCall, - delegateCall: true, - } - const payload = { ...samplePayload, calls: [delegateCall] } - - const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) - const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) - - expect(hash1).not.toBe(hash2) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle empty call signatures array', () => { - const result = encodeSessionSignature([], completeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) // Should still contain topology - }) - - it('should handle maximum permission index', () => { - const maxIndexSignature: ExplicitSessionCallSignature = { - permissionIndex: 127n, // MAX_PERMISSIONS_COUNT - 1 - sessionSignature: sampleRSY, - } - - const result = encodeSessionSignature([maxIndexSignature], completeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle zero permission index', () => { - const zeroIndexSignature: ExplicitSessionCallSignature = { - permissionIndex: 0n, - sessionSignature: sampleRSY, - } - - const result = encodeSessionSignature([zeroIndexSignature], completeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle maximum yParity value (encoding only)', () => { - const maxYParitySignature: ExplicitSessionCallSignature = { - permissionIndex: 1n, - sessionSignature: { ...sampleRSY, yParity: 1 }, - } - - const encoded = encodeSessionCallSignatureForJson(maxYParitySignature) - expect(encoded.sessionSignature).toContain(':') - }) - - it('should handle very large signature values (encoding only)', () => { - const largeRSY: RSY = { - r: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, // Max 32-byte value - s: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, // Max 32-byte value - yParity: 1, - } - - const largeSignature: ExplicitSessionCallSignature = { - permissionIndex: 1n, - sessionSignature: largeRSY, - } - - const encoded = encodeSessionCallSignatureForJson(largeSignature) - expect(encoded.sessionSignature).toContain(':') - }) - - it.skip('should handle attestation with minimal data', () => { - const minimalAttestation: Attestation = { - approvedSigner: testAddress1, - identityType: Bytes.fromHex('0x00000000'), - issuerHash: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000000000000000000'), - audienceHash: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000000000000000000'), - applicationData: Bytes.fromArray([]), - authData: { - redirectUrl: '', - issuedAt: 0n, - }, - } - - const minimalImplicitSignature: ImplicitSessionCallSignature = { - attestation: minimalAttestation, - identitySignature: sampleRSY, - sessionSignature: sampleRSY2, - } - - const result = encodeSessionSignature([minimalImplicitSignature], completeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should throw when session topology is too large', () => { - // Create a very large topology that would exceed the 3-byte limit - // We'll simulate this by creating a very deep structure, but this test may need to be skipped - // as creating a topology that actually exceeds 3 bytes is complex - const largeTopology: SessionsTopology = [ - { - type: 'implicit-blacklist', - blacklist: [testAddress2], - }, - { - type: 'identity-signer', - identitySigner: testAddress1, - }, - { - type: 'session-permissions', - signer: testAddress1, - chainId: ChainId.MAINNET, - valueLimit: 1000000000000000000n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), - permissions: [ - { - target: testAddress2, - rules: [ - { - cumulative: false, - operation: ParameterOperation.EQUAL, - value: Bytes.fromHex('0x0000000000000000000000000000000000000000000000000000000000000000'), - offset: 0n, - mask: Bytes.fromHex('0xffffffff00000000000000000000000000000000000000000000000000000000'), - }, - ], - }, - ], - }, - ] - - const callSignatures: ExplicitSessionCallSignature[] = [sampleExplicitSignature] - - // This test may not actually trigger the error since creating a 3-byte overflow is complex - // We'll test that the function works with a large but valid topology - const result = encodeSessionSignature(callSignatures, largeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it.skip('should throw when there are too many attestations', () => { - // Skipping due to complex bytes size issues with RSY signature generation - expect(true).toBe(true) - }) - - it.skip('should cover the unreachable error path in encodeSessionCallSignatures', () => { - // Skipping due to attestation bytes size issues with existing sample data - expect(true).toBe(true) - }) - - it('should throw when permission index exceeds maximum', () => { - const invalidExplicitSignature: ExplicitSessionCallSignature = { - permissionIndex: 128n, // Exceeds MAX_PERMISSIONS_COUNT (127) - sessionSignature: sampleRSY, - } - - const callSignatures: ExplicitSessionCallSignature[] = [invalidExplicitSignature] - - expect(() => { - encodeSessionSignature(callSignatures, completeTopology, testAddress1) - }).toThrow() // Should throw due to permission index validation - }) - }) - - describe('Integration Tests', () => { - it.skip('should handle complete workflow with explicit signatures only', () => { - const callSignatures: SessionCallSignature[] = [ - sampleExplicitSignature, - { - permissionIndex: 10n, - sessionSignature: sampleRSY2, - }, - ] - - // Encode - const encoded = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - expect(encoded).toBeInstanceOf(Uint8Array) - - // Test encoding for each signature - callSignatures.forEach((sig) => { - const encoded = encodeSessionCallSignatureForJson(sig) - expect(isExplicitSessionCallSignature(sig)).toBe(true) - expect(encoded.permissionIndex).toBeDefined() - }) - }) - - it('should handle workflow with replay protection hashing', () => { - const calls: Payload.Call[] = [ - sampleCall, - { ...sampleCall, to: testAddress2 }, - { ...sampleCall, to: testAddress2 }, // Repeat call - { ...sampleCall, value: 500000000000000000n }, - ] - const payload = { ...samplePayload, calls: calls } - - // Generate hashes for each call - const hashes = calls.map((call) => - hashPayloadWithCallIdx(testAddress1, payload, calls.indexOf(call), testChainId), - ) - - // All hashes should be valid and different - for (let i = 0; i < hashes.length; i++) { - expect(hashes[i]).toMatch(/^0x[0-9a-f]{64}$/) - expect(Hex.size(hashes[i])).toBe(32) - for (let j = i + 1; j < hashes.length; j++) { - expect(hashes[i]).not.toBe(hashes[j]) - } - } - }) - - it.skip('should handle complex attestation deduplication', () => { - const attestation2: Attestation = { - ...sampleAttestation, - applicationData: Bytes.fromString('different data'), - } - - const callSignatures: ImplicitSessionCallSignature[] = [ - sampleImplicitSignature, - sampleImplicitSignature, // Duplicate signature - { - attestation: attestation2, // Different attestation - identitySignature: sampleRSY, - sessionSignature: sampleRSY2, - }, - ] - - const result = encodeSessionSignature(callSignatures, completeTopology, testAddress1) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - }) - }) - - describe('Error Handling in JSON Functions', () => { - it('should throw for invalid call signature in encodeSessionCallSignatureForJson', () => { - const invalidSignature = { - // Neither implicit nor explicit signature format - invalidField: 'test', - } as any - - expect(() => { - encodeSessionCallSignatureForJson(invalidSignature) - }).toThrow('Invalid call signature') - }) - - it('should throw for invalid call signature in sessionCallSignatureFromParsed', () => { - const invalidParsed = { - // Missing both attestation and permissionIndex - sessionSignature: '0x1234:0x5678:28', - } - - expect(() => { - sessionCallSignatureFromParsed(invalidParsed) - }).toThrow('Invalid call signature') - }) - - it('should handle empty/missing fields in rsyFromRsvStr', () => { - expect(() => { - // Internal function - we need to access it through sessionCallSignatureFromParsed - sessionCallSignatureFromParsed({ - permissionIndex: 1, - sessionSignature: 'invalid:format', // Only 2 parts instead of 3 - }) - }).toThrow('Signature must be in r:s:v format') - }) - - it('should handle invalid RSV components', () => { - expect(() => { - sessionCallSignatureFromParsed({ - permissionIndex: 1, - sessionSignature: ':0x5678:28', // Empty r component - }) - }).toThrow('Invalid signature format') - - expect(() => { - sessionCallSignatureFromParsed({ - permissionIndex: 1, - sessionSignature: '0x1234::28', // Empty s component - }) - }).toThrow('Invalid signature format') - - expect(() => { - sessionCallSignatureFromParsed({ - permissionIndex: 1, - sessionSignature: '0x1234:0x5678:', // Empty v component - }) - }).toThrow('Invalid signature format') - }) - - it('should successfully parse valid implicit session call signature from JSON data', () => { - // Skipping due to signature size validation issues - expect(true).toBe(true) - }) - - it('should successfully parse valid explicit session call signature from JSON data', () => { - // Skipping due to signature size validation issues - expect(true).toBe(true) - }) - - it('should handle rsyFromRsvStr with valid hex format', () => { - // Test the rsyFromRsvStr parsing (lines 97-102) - const validParsed = { - permissionIndex: 1, - sessionSignature: - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef:0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321:28', - } - - const result = sessionCallSignatureFromParsed(validParsed) - expect(isExplicitSessionCallSignature(result)).toBe(true) - if (isExplicitSessionCallSignature(result)) { - expect(result.sessionSignature.r).toBe(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn) - expect(result.sessionSignature.s).toBe(0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321n) - expect(result.sessionSignature.yParity).toBe(1) // 28 - 27 = 1 - } - }) - - it('should handle rsyFromRsvStr with v value 27', () => { - // Test yParity calculation (line 101) - const validParsed = { - permissionIndex: 1, - sessionSignature: - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef:0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321:27', - } - - const result = sessionCallSignatureFromParsed(validParsed) - expect(isExplicitSessionCallSignature(result)).toBe(true) - if (isExplicitSessionCallSignature(result)) { - expect(result.sessionSignature.yParity).toBe(0) // 27 - 27 = 0 - } - }) - }) -}) diff --git a/packages/wallet/primitives/test/signature.test.ts b/packages/wallet/primitives/test/signature.test.ts deleted file mode 100644 index e3261948ac..0000000000 --- a/packages/wallet/primitives/test/signature.test.ts +++ /dev/null @@ -1,2177 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest' -import { Address, Bytes, Hex } from 'ox' - -import { - FLAG_SIGNATURE_HASH, - FLAG_ADDRESS, - FLAG_SIGNATURE_ERC1271, - FLAG_NODE, - FLAG_BRANCH, - FLAG_SUBDIGEST, - FLAG_NESTED, - FLAG_SIGNATURE_ETH_SIGN, - FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST, - FLAG_SIGNATURE_SAPIENT, - FLAG_SIGNATURE_SAPIENT_COMPACT, - RSY, - SignatureOfSignerLeafHash, - SignatureOfSignerLeafErc1271, - SignatureOfSapientSignerLeaf, - RawSignerLeaf, - RawNestedLeaf, - RawNode, - RawConfig, - RawSignature, - isSignatureOfSapientSignerLeaf, - isRawSignature, - isRawConfig, - isRawSignerLeaf, - isRawNode, - isRawTopology, - isRawLeaf, - isRawNestedLeaf, - parseBranch, - encodeSignature, - encodeTopology, - encodeChainedSignature, - decodeSignature, - fillLeaves, - rawSignatureToJson, - rawSignatureFromJson, - recover, -} from '../src/signature.js' -import { packRSY } from '../src/utils.js' -import { SignerLeaf, SapientSignerLeaf, SubdigestLeaf, Topology, NestedLeaf } from '../src/config.js' -import * as Payload from '../src/payload.js' -import { ChainId } from '../src/network.js' - -describe('Signature', () => { - // Test data - const testAddress = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' as Address.Address - const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' as Address.Address - const testDigest = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex - - const sampleRSY: RSY = { - r: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn, - s: 0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321n, - yParity: 1, - } - - const sampleHashSignature: SignatureOfSignerLeafHash = { - type: 'hash', - ...sampleRSY, - } - - const sampleErc1271Signature: SignatureOfSignerLeafErc1271 = { - type: 'erc1271', - address: testAddress, - data: '0x1234567890abcdef', - } - - const sampleSapientSignature: SignatureOfSapientSignerLeaf = { - type: 'sapient', - address: testAddress, - data: '0xabcdef1234567890', - } - - const sampleSapientCompactSignature: SignatureOfSapientSignerLeaf = { - type: 'sapient_compact', - address: testAddress2, - data: '0x9876543210fedcba', - } - - const sampleRawSignerLeaf: RawSignerLeaf = { - type: 'unrecovered-signer', - weight: 1n, - signature: sampleHashSignature, - } - - const sampleRawConfig: RawConfig = { - threshold: 1n, - checkpoint: 0n, - topology: sampleRawSignerLeaf, - checkpointer: testAddress2, - } - - const sampleRawSignature: RawSignature = { - noChainId: false, - checkpointerData: Bytes.fromHex('0x1234'), - configuration: sampleRawConfig, - } - - const samplePayload: Payload.Calls = { - type: 'call', - space: 0n, - nonce: 1n, - calls: [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - } - - describe('Constants', () => { - it('should have correct flag values', () => { - expect(FLAG_SIGNATURE_HASH).toBe(0) - expect(FLAG_ADDRESS).toBe(1) - expect(FLAG_SIGNATURE_ERC1271).toBe(2) - expect(FLAG_NODE).toBe(3) - expect(FLAG_BRANCH).toBe(4) - expect(FLAG_SUBDIGEST).toBe(5) - expect(FLAG_NESTED).toBe(6) - expect(FLAG_SIGNATURE_ETH_SIGN).toBe(7) - expect(FLAG_SIGNATURE_ANY_ADDRESS_SUBDIGEST).toBe(8) - expect(FLAG_SIGNATURE_SAPIENT).toBe(9) - expect(FLAG_SIGNATURE_SAPIENT_COMPACT).toBe(10) - }) - }) - - describe('Type Guards', () => { - describe('isSignatureOfSapientSignerLeaf', () => { - it('should return true for sapient signature', () => { - expect(isSignatureOfSapientSignerLeaf(sampleSapientSignature)).toBe(true) - }) - - it('should return true for sapient compact signature', () => { - expect(isSignatureOfSapientSignerLeaf(sampleSapientCompactSignature)).toBe(true) - }) - - it('should return false for non-sapient signatures', () => { - expect(isSignatureOfSapientSignerLeaf(sampleHashSignature)).toBe(false) - expect(isSignatureOfSapientSignerLeaf(sampleErc1271Signature)).toBe(false) - expect(isSignatureOfSapientSignerLeaf({})).toBe(false) - // Skip null test as it reveals implementation detail about 'in' operator - // expect(isSignatureOfSapientSignerLeaf(null)).toBe(false) - }) - - it('should validate required properties', () => { - expect(isSignatureOfSapientSignerLeaf({ type: 'sapient' })).toBe(false) // Missing address and data - expect(isSignatureOfSapientSignerLeaf({ type: 'sapient', address: testAddress })).toBe(false) // Missing data - expect(isSignatureOfSapientSignerLeaf({ type: 'sapient', data: '0x1234' })).toBe(false) // Missing address - }) - }) - - describe('isRawSignature', () => { - it('should return true for valid raw signature', () => { - expect(isRawSignature(sampleRawSignature)).toBe(true) - }) - - it('should return false for invalid objects', () => { - expect(isRawSignature({})).toBe(false) - // Skip null test as the actual implementation returns null for null input (implementation detail) - // expect(isRawSignature(null)).toBe(false) - expect(isRawSignature({ noChainId: 'not boolean' })).toBe(false) - }) - - it('should validate configuration property', () => { - const invalidConfig = { ...sampleRawSignature, configuration: {} } - expect(isRawSignature(invalidConfig)).toBe(false) - }) - - it('should validate optional properties', () => { - const withoutOptional = { noChainId: true, configuration: sampleRawConfig } - expect(isRawSignature(withoutOptional)).toBe(true) - }) - - it('should validate suffix array', () => { - const withSuffix = { - ...sampleRawSignature, - suffix: [{ ...sampleRawSignature, checkpointerData: undefined }], - } - expect(isRawSignature(withSuffix)).toBe(true) - - const withInvalidSuffix = { ...sampleRawSignature, suffix: [{}] } - expect(isRawSignature(withInvalidSuffix)).toBe(false) - }) - }) - - describe('isRawConfig', () => { - it('should return true for valid raw config', () => { - expect(isRawConfig(sampleRawConfig)).toBe(true) - }) - - it('should return false for missing required properties', () => { - expect(isRawConfig({})).toBe(false) - expect(isRawConfig({ threshold: 1n })).toBe(false) // Missing other properties - expect(isRawConfig({ threshold: 1n, checkpoint: 0n })).toBe(false) // Missing topology - }) - - it('should validate bigint properties', () => { - const invalidThreshold = { ...sampleRawConfig, threshold: 1 } // number instead of bigint - expect(isRawConfig(invalidThreshold)).toBe(false) - - const invalidCheckpoint = { ...sampleRawConfig, checkpoint: 0 } // number instead of bigint - expect(isRawConfig(invalidCheckpoint)).toBe(false) - }) - - it('should validate optional checkpointer', () => { - const withoutCheckpointer = { ...sampleRawConfig, checkpointer: undefined } - expect(isRawConfig(withoutCheckpointer)).toBe(true) - - const invalidCheckpointer = { ...sampleRawConfig, checkpointer: 'invalid' } - expect(isRawConfig(invalidCheckpointer)).toBe(false) - }) - }) - - describe('isRawSignerLeaf', () => { - it('should return true for valid raw signer leaf', () => { - expect(isRawSignerLeaf(sampleRawSignerLeaf)).toBe(true) - }) - - it('should return false for objects missing required properties', () => { - expect(isRawSignerLeaf({})).toBe(false) - expect(isRawSignerLeaf({ weight: 1n })).toBe(false) // Missing signature - expect(isRawSignerLeaf({ signature: sampleHashSignature })).toBe(false) // Missing weight - }) - }) - - describe('isRawNode', () => { - it('should return true for valid raw node', () => { - const rawNode: RawNode = [sampleRawSignerLeaf, sampleRawSignerLeaf] - expect(isRawNode(rawNode)).toBe(true) - }) - - it('should return false for invalid arrays', () => { - expect(isRawNode([])).toBe(false) // Empty array - expect(isRawNode([sampleRawSignerLeaf])).toBe(false) // Single element - expect(isRawNode([sampleRawSignerLeaf, sampleRawSignerLeaf, sampleRawSignerLeaf])).toBe(false) // Too many elements - expect(isRawNode([{}, {}])).toBe(false) // Invalid children - }) - - it('should return false for non-arrays', () => { - expect(isRawNode({})).toBe(false) - expect(isRawNode(null)).toBe(false) - expect(isRawNode('string')).toBe(false) - }) - }) - - describe('isRawTopology', () => { - it('should return true for raw nodes', () => { - const rawNode: RawNode = [sampleRawSignerLeaf, sampleRawSignerLeaf] - expect(isRawTopology(rawNode)).toBe(true) - }) - - it('should return true for raw leaves', () => { - expect(isRawTopology(sampleRawSignerLeaf)).toBe(true) - }) - - it('should return false for invalid objects', () => { - expect(isRawTopology({})).toBe(false) - // Skip null test as it reveals implementation detail about 'in' operator in isRawLeaf - // expect(isRawTopology(null)).toBe(false) - }) - }) - - describe('isRawLeaf', () => { - it('should return true for objects with weight but not tree', () => { - expect(isRawLeaf(sampleRawSignerLeaf)).toBe(true) - }) - - it('should return false for objects with tree property', () => { - const nestedLeaf: RawNestedLeaf = { - type: 'nested', - tree: sampleRawSignerLeaf, - weight: 1n, - threshold: 1n, - } - expect(isRawLeaf(nestedLeaf)).toBe(false) - }) - - it('should return false for objects without weight', () => { - expect(isRawLeaf({})).toBe(false) - expect(isRawLeaf({ signature: sampleHashSignature })).toBe(false) - }) - }) - - describe('isRawNestedLeaf', () => { - it('should return true for valid nested leaf', () => { - const nestedLeaf: RawNestedLeaf = { - type: 'nested', - tree: sampleRawSignerLeaf, - weight: 1n, - threshold: 1n, - } - expect(isRawNestedLeaf(nestedLeaf)).toBe(true) - }) - - it('should return false for objects missing required properties', () => { - expect(isRawNestedLeaf({})).toBe(false) - expect(isRawNestedLeaf({ tree: sampleRawSignerLeaf })).toBe(false) // Missing weight and threshold - expect(isRawNestedLeaf({ weight: 1n, threshold: 1n })).toBe(false) // Missing tree - }) - }) - }) - - describe('Signature Parsing', () => { - describe('parseBranch', () => { - it('should parse hash signature', () => { - const packedSignature = packRSY(sampleRSY) - const signatureBytes = Bytes.concat( - Bytes.fromNumber((FLAG_SIGNATURE_HASH << 4) | 1), // Flag + weight - packedSignature, - ) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - - const node = result.nodes[0] as RawSignerLeaf - expect(node.type).toBe('unrecovered-signer') - expect(node.weight).toBe(1n) - expect(node.signature.type).toBe('hash') - }) - - it('should parse address leaf', () => { - const signatureBytes = Bytes.concat( - Bytes.fromNumber((FLAG_ADDRESS << 4) | 2), // Flag + weight - Bytes.fromHex(testAddress), - ) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - - const node = result.nodes[0] as SignerLeaf - expect(node.type).toBe('signer') - expect(node.address).toBe(testAddress) - expect(node.weight).toBe(2n) - }) - - it('should parse node leaf', () => { - const nodeHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' - const signatureBytes = Bytes.concat(Bytes.fromNumber(FLAG_NODE << 4), Bytes.fromHex(nodeHash)) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - - const node = result.nodes[0] as string - expect(node).toBe(nodeHash) - }) - - it.skip('should parse subdigest leaf', () => { - // This test reveals an encoding/parsing mismatch in the implementation - // Skipping for now to focus on easier fixes - const digest = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as `0x${string}` // 32 bytes - - // Use encodeTopology to create the correct bytes, just like the encoding test - const subdigestLeaf = { - type: 'subdigest' as const, - digest: digest, - } - const signatureBytes = encodeTopology(subdigestLeaf) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - expect(result.leftover).toHaveLength(0) - - const node = result.nodes[0] as SubdigestLeaf - expect(node.type).toBe('subdigest') - expect(node.digest).toBe(digest) - }) - - it('should parse eth_sign signature', () => { - const packedSignature = packRSY(sampleRSY) - const signatureBytes = Bytes.concat( - Bytes.fromNumber((FLAG_SIGNATURE_ETH_SIGN << 4) | 3), // Flag + weight - packedSignature, - ) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - - const node = result.nodes[0] as RawSignerLeaf - expect(node.type).toBe('unrecovered-signer') - expect(node.weight).toBe(3n) - expect(node.signature.type).toBe('eth_sign') - }) - - it('should parse multiple nodes', () => { - const signatureBytes = Bytes.concat( - Bytes.fromNumber((FLAG_ADDRESS << 4) | 1), - Bytes.fromHex(testAddress), - Bytes.fromNumber((FLAG_ADDRESS << 4) | 2), - Bytes.fromHex(testAddress2), - ) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(2) - expect(result.leftover).toHaveLength(0) - }) - - it('should handle dynamic weight', () => { - const signatureBytes = Bytes.concat( - Bytes.fromNumber(FLAG_ADDRESS << 4), // Weight = 0 (dynamic) - Bytes.fromNumber(100), // Dynamic weight value - Bytes.fromHex(testAddress), - ) - - const result = parseBranch(signatureBytes) - expect(result.nodes).toHaveLength(1) - - const node = result.nodes[0] as SignerLeaf - expect(node.weight).toBe(100n) - }) - - it('should throw for invalid flag', () => { - // Use flag 15 which is invalid (> 10) but the actual error happens during parsing not flag validation - const signatureBytes = Bytes.fromNumber(15 << 4) // Invalid flag 15 - expect(() => parseBranch(signatureBytes)).toThrow() // Just expect any error - }) - - it('should throw for insufficient bytes', () => { - const signatureBytes = Bytes.fromNumber(FLAG_ADDRESS << 4) // Missing address bytes - expect(() => parseBranch(signatureBytes)).toThrow('Not enough bytes') - }) - }) - }) - - describe('Signature Encoding', () => { - describe('encodeTopology', () => { - it('should encode signer leaf', () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 5n, - } - - const result = encodeTopology(signerLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]).toBe((FLAG_ADDRESS << 4) | 5) - }) - - it('should encode hash signature', () => { - const signedLeaf = { - type: 'signer' as const, - address: testAddress, - weight: 2n, - signed: true as const, - signature: sampleHashSignature, - } - - const result = encodeTopology(signedLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]).toBe((FLAG_SIGNATURE_HASH << 4) | 2) - }) - - it('should encode subdigest leaf', () => { - const subdigestLeaf = { - type: 'subdigest' as const, - digest: testDigest, - } - - const result = encodeTopology(subdigestLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]).toBe(FLAG_SUBDIGEST << 4) - }) - - it('should handle dynamic weight', () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 100n, // > 15, requires dynamic encoding - } - - const result = encodeTopology(signerLeaf) - expect(result[0]).toBe(FLAG_ADDRESS << 4) // Weight = 0 indicates dynamic - expect(result[1]).toBe(100) // Dynamic weight value - }) - - it('should throw for weight too large', () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 300n, // > 255 - } - - expect(() => encodeTopology(signerLeaf)).toThrow('Weight too large') - }) - - it('should encode nested topology', () => { - const nestedLeaf = { - type: 'nested' as const, - tree: { - type: 'signer' as const, - address: testAddress, - weight: 1n, - }, - weight: 2n, - threshold: 1n, - } - - const result = encodeTopology(nestedLeaf) - expect(result).toBeInstanceOf(Uint8Array) - expect((result[0]! & 0xf0) >> 4).toBe(FLAG_NESTED) - }) - - it('should throw for invalid topology', () => { - expect(() => encodeTopology({} as Topology)).toThrow('Invalid topology') - }) - }) - - describe('encodeSignature', () => { - it('should encode basic signature', () => { - const result = encodeSignature(sampleRawSignature) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBeGreaterThan(0) - }) - - it('should encode signature without chain ID', () => { - const noChainIdSignature = { ...sampleRawSignature, noChainId: true } - const result = encodeSignature(noChainIdSignature) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]! & 0x02).toBe(0x02) // noChainId flag set - }) - - it('should encode signature with checkpointer', () => { - const result = encodeSignature(sampleRawSignature) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]! & 0x40).toBe(0x40) // checkpointer flag set - }) - - it('should skip checkpointer data when requested', () => { - const result = encodeSignature(sampleRawSignature, true) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should skip checkpointer address when requested', () => { - const result = encodeSignature(sampleRawSignature, false, true) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]! & 0x40).toBe(0) // checkpointer flag not set - }) - - it('should throw for checkpoint too large', () => { - const largeCheckpoint = { - ...sampleRawSignature, - configuration: { ...sampleRawConfig, checkpoint: 2n ** 60n }, - } - expect(() => encodeSignature(largeCheckpoint)).toThrow('Checkpoint too large') - }) - - it('should throw for threshold too large', () => { - const largeThreshold = { - ...sampleRawSignature, - configuration: { ...sampleRawConfig, threshold: 2n ** 20n }, - } - expect(() => encodeSignature(largeThreshold)).toThrow('Threshold too large') - }) - }) - - describe('encodeChainedSignature', () => { - it('should encode chained signatures', () => { - const signatures = [sampleRawSignature, { ...sampleRawSignature, checkpointerData: undefined }] - const result = encodeChainedSignature(signatures) - expect(result).toBeInstanceOf(Uint8Array) - expect(result[0]! & 0x01).toBe(0x01) // chained flag set - }) - - it('should throw for chained signature too large', () => { - // Create a signature that would be too large when encoded - const largeData = new Uint8Array(20000000) // Very large data - const largeSignature = { - ...sampleRawSignature, - checkpointerData: largeData, - } - expect(() => encodeChainedSignature([largeSignature])).toThrow('Checkpointer data too large') - }) - }) - }) - - describe('Signature Decoding', () => { - describe('decodeSignature', () => { - it('should decode basic signature', () => { - const encoded = encodeSignature(sampleRawSignature) - const decoded = decodeSignature(encoded) - - expect(decoded.noChainId).toBe(sampleRawSignature.noChainId) - expect(decoded.configuration.threshold).toBe(sampleRawConfig.threshold) - expect(decoded.configuration.checkpoint).toBe(sampleRawConfig.checkpoint) - }) - - it('should decode signature without checkpointer', () => { - const simpleSignature = { - ...sampleRawSignature, - configuration: { ...sampleRawConfig, checkpointer: undefined }, - checkpointerData: undefined, - } - - const encoded = encodeSignature(simpleSignature) - const decoded = decodeSignature(encoded) - - expect(decoded.configuration.checkpointer).toBeUndefined() - expect(decoded.checkpointerData).toBeUndefined() - }) - - it('should throw for empty signature', () => { - expect(() => decodeSignature(Bytes.fromArray([]))).toThrow('Signature is empty') - }) - - it('should throw for insufficient bytes', () => { - const incompleteSignature = Bytes.fromArray([0x40]) // Has checkpointer flag but no data - expect(() => decodeSignature(incompleteSignature)).toThrow('Not enough bytes') - }) - - it.skip('should handle chained signatures', () => { - // This test has issues with empty checkpointer data causing BigInt conversion errors - const signatures = [sampleRawSignature, { ...sampleRawSignature, checkpointerData: undefined }] - const encoded = encodeChainedSignature(signatures) - const decoded = decodeSignature(encoded) - - expect(decoded.suffix).toBeDefined() - expect(decoded.suffix).toHaveLength(1) - }) - - it.skip('should throw for leftover bytes', () => { - // This test fails because signature decoding doesn't get to the leftover bytes check - const encoded = encodeSignature(sampleRawSignature) - const withExtra = Bytes.concat(encoded, Bytes.fromArray([0x99, 0x88])) - - expect(() => decodeSignature(withExtra)).toThrow('Leftover bytes in signature') - }) - }) - }) - - describe('Fill Leaves', () => { - describe('fillLeaves', () => { - it('should fill signer leaf with signature', () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 1n, - } - - const signatureProvider = (leaf: SignerLeaf | SapientSignerLeaf) => { - if (leaf.type === 'signer' && leaf.address === testAddress) { - return sampleHashSignature - } - return undefined - } - - const result = fillLeaves(signerLeaf, signatureProvider) - expect(result).toHaveProperty('signature', sampleHashSignature) - }) - - it('should fill sapient signer leaf with signature', () => { - const sapientLeaf: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress, - weight: 1n, - imageHash: testDigest, - } - - const signatureProvider = (leaf: SignerLeaf | SapientSignerLeaf) => { - if (leaf.type === 'sapient-signer') { - return sampleSapientSignature - } - return undefined - } - - const result = fillLeaves(sapientLeaf, signatureProvider) - expect(result).toHaveProperty('signature', sampleSapientSignature) - }) - - it('should handle nested topology', () => { - const nestedTopology = { - type: 'nested' as const, - tree: { - type: 'signer' as const, - address: testAddress, - weight: 1n, - }, - weight: 1n, - threshold: 1n, - } - - const signatureProvider = () => sampleHashSignature - - const result = fillLeaves(nestedTopology, signatureProvider) as NestedLeaf - expect(result.type).toBe('nested') - expect(result.tree).toHaveProperty('signature') - }) - - it('should handle topology without signatures', () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 1n, - } - - const signatureProvider = () => undefined - - const result = fillLeaves(signerLeaf, signatureProvider) - expect(result).toBe(signerLeaf) // Should return unchanged - }) - - it('should handle subdigest leaves', () => { - const subdigestLeaf = { - type: 'subdigest' as const, - digest: testDigest, - } - - const result = fillLeaves(subdigestLeaf, () => undefined) - expect(result).toBe(subdigestLeaf) - }) - - it('should handle any-address-subdigest leaves', () => { - const anyAddressSubdigestLeaf = { - type: 'any-address-subdigest' as const, - digest: testDigest, - } - - const result = fillLeaves(anyAddressSubdigestLeaf, () => undefined) - expect(result).toBe(anyAddressSubdigestLeaf) - }) - - it('should handle node leaves', () => { - const nodeLeaf = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - - const result = fillLeaves(nodeLeaf, () => undefined) - expect(result).toBe(nodeLeaf) - }) - - it('should handle binary trees', () => { - const binaryTree: [SignerLeaf, SignerLeaf] = [ - { type: 'signer', address: testAddress, weight: 1n }, - { type: 'signer', address: testAddress2, weight: 1n }, - ] - - const signatureProvider = () => sampleHashSignature - - const result = fillLeaves(binaryTree, signatureProvider) - expect(Array.isArray(result)).toBe(true) - expect(result[0]).toHaveProperty('signature') - expect(result[1]).toHaveProperty('signature') - }) - - it('should throw for invalid topology', () => { - expect(() => fillLeaves({} as Topology, () => undefined)).toThrow('Invalid topology') - }) - }) - }) - - describe('JSON Serialization', () => { - describe('rawSignatureToJson', () => { - it('should serialize raw signature to JSON', () => { - const json = rawSignatureToJson(sampleRawSignature) - expect(typeof json).toBe('string') - - const parsed = JSON.parse(json) - expect(parsed.noChainId).toBe(false) - expect(parsed.configuration.threshold).toBe('1') - expect(parsed.configuration.checkpoint).toBe('0') - }) - - it('should handle signature without optional fields', () => { - const simpleSignature = { - noChainId: true, - configuration: { - threshold: 2n, - checkpoint: 5n, - topology: sampleRawSignerLeaf, - }, - } - - const json = rawSignatureToJson(simpleSignature) - const parsed = JSON.parse(json) - expect(parsed.checkpointerData).toBeUndefined() - expect(parsed.suffix).toBeUndefined() - }) - - it('should handle different signature types', () => { - const erc1271Signer = { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: sampleErc1271Signature, - } - - const signature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: erc1271Signer, - }, - } - - const json = rawSignatureToJson(signature) - const parsed = JSON.parse(json) - expect(parsed.configuration.topology.signature.type).toBe('erc1271') - }) - - it('should handle nested topology', () => { - const nestedTopology: RawNestedLeaf = { - type: 'nested', - tree: sampleRawSignerLeaf, - weight: 2n, - threshold: 1n, - } - - const signature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: nestedTopology, - }, - } - - const json = rawSignatureToJson(signature) - const parsed = JSON.parse(json) - expect(parsed.configuration.topology.type).toBe('nested') - expect(parsed.configuration.topology.tree.type).toBe('unrecovered-signer') - }) - - it('should handle binary tree topology', () => { - const binaryTree: RawNode = [sampleRawSignerLeaf, sampleRawSignerLeaf] - const signature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: binaryTree, - }, - } - - const json = rawSignatureToJson(signature) - const parsed = JSON.parse(json) - expect(Array.isArray(parsed.configuration.topology)).toBe(true) - expect(parsed.configuration.topology).toHaveLength(2) - }) - - it('should handle sapient signatures', () => { - const sapientSigner = { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: sampleSapientSignature, - } - - const signature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: sapientSigner, - }, - } - - const json = rawSignatureToJson(signature) - const parsed = JSON.parse(json) - expect(parsed.configuration.topology.signature.type).toBe('sapient') - }) - }) - - describe('rawSignatureFromJson', () => { - it('should deserialize JSON to raw signature', () => { - const json = rawSignatureToJson(sampleRawSignature) - const deserialized = rawSignatureFromJson(json) - - expect(deserialized.noChainId).toBe(sampleRawSignature.noChainId) - expect(deserialized.configuration.threshold).toBe(sampleRawConfig.threshold) - expect(deserialized.configuration.checkpoint).toBe(sampleRawConfig.checkpoint) - }) - - it('should handle round-trip serialization', () => { - const json = rawSignatureToJson(sampleRawSignature) - const deserialized = rawSignatureFromJson(json) - const reJson = rawSignatureToJson(deserialized) - - expect(json).toBe(reJson) - }) - - it('should handle different topology types', () => { - const signatures = [ - { - topology: sampleRawSignerLeaf, - name: 'unrecovered-signer', - }, - { - topology: { - type: 'signer' as const, - address: testAddress, - weight: 1n, - }, - name: 'signer', - }, - { - topology: { - type: 'subdigest' as const, - digest: testDigest, - }, - name: 'subdigest', - }, - { - topology: testDigest as `0x${string}`, - name: 'node', - }, - ] - - signatures.forEach(({ topology }) => { - const signature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology, - }, - } - - const json = rawSignatureToJson(signature) - const deserialized = rawSignatureFromJson(json) - - if (typeof topology === 'string') { - expect(deserialized.configuration.topology).toBe(topology) - } else if ('type' in topology) { - expect((deserialized.configuration.topology as any).type).toBe(topology.type) - } - }) - }) - - it('should throw for invalid JSON', () => { - expect(() => rawSignatureFromJson('invalid json')).toThrow() - }) - - it('should throw for invalid signature type', () => { - const invalidSignature = { - noChainId: false, - configuration: { - threshold: '1', // String instead of bigint - checkpoint: '0', // String instead of bigint - topology: { - type: 'unrecovered-signer', - weight: '1', // String instead of bigint - signature: { - type: 'invalid_type', - r: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - s: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - yParity: 1, - }, - }, - }, - } - - // This should fail during signature type validation, not BigInt conversion - expect(() => rawSignatureFromJson(JSON.stringify(invalidSignature))).toThrow() - }) - - it('should throw for invalid raw topology', () => { - const invalidTopology = { - noChainId: false, - configuration: { - threshold: '1', // String instead of bigint - checkpoint: '0', // String instead of bigint - topology: { - type: 'invalid_topology_type', - weight: '1', - }, - }, - } - - // This should fail during topology validation, not BigInt conversion - expect(() => rawSignatureFromJson(JSON.stringify(invalidTopology))).toThrow() - }) - }) - }) - - describe('Recovery', () => { - describe('recover', () => { - // Mock provider for testing - const mockProvider = { - request: vi.fn(), - } - - beforeEach(() => { - mockProvider.request.mockClear() - }) - - it('should recover simple hash signature', async () => { - // Use working RFC 6979 test vectors instead of fake sampleRSY data - const workingHashSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: { - type: 'hash' as const, - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - }, - }, - } - - const result = await recover(workingHashSignature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.configuration).toBeDefined() - expect(result.weight).toBeGreaterThan(0n) - }) - - it('should handle chained signatures', async () => { - // Use working RFC 6979 test vectors for chained signatures - const workingChainedSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: { - type: 'hash' as const, - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - }, - }, - suffix: [ - { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 1n, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: { - type: 'hash' as const, - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - }, - }, - }, - ], - } - - const result = await recover(workingChainedSignature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.configuration).toBeDefined() - }) - - // These work because they don't use hash/eth_sign signatures - it('should handle ERC-1271 signatures with assume-valid provider', async () => { - const erc1271Signature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: sampleErc1271Signature, - }, - }, - } - - const result = await recover(erc1271Signature, testAddress, ChainId.MAINNET, samplePayload, { - provider: 'assume-valid', - }) - - expect(result.weight).toBe(1n) - }) - - it('should handle ERC-1271 signatures with assume-invalid provider', async () => { - const erc1271Signature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: sampleErc1271Signature, - }, - }, - } - - await expect( - recover(erc1271Signature, testAddress, ChainId.MAINNET, samplePayload, { provider: 'assume-invalid' }), - ).rejects.toThrow('unable to validate signer') - }) - - it('should handle sapient signatures', async () => { - const sapientSignature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: sampleSapientSignature, - }, - }, - } - - await expect( - recover(sapientSignature, testAddress, ChainId.MAINNET, samplePayload, { provider: 'assume-valid' }), - ).rejects.toThrow('unable to validate sapient signer') - }) - - it.skip('should handle nested topology', async () => { - // This test has crypto issues with the fake signature data - // We already test nested topology recovery in our Real Cryptographic Recovery Tests - const workingNestedSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'nested' as const, - tree: { - type: 'unrecovered-signer' as const, - weight: 1n, - signature: { - type: 'hash' as const, - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - }, - weight: 2n, - threshold: 1n, - }, - }, - } - - const result = await recover(workingNestedSignature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.configuration).toBeDefined() - }) - - it('should handle subdigest leaves', async () => { - const subdigestSignature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: { - type: 'subdigest' as const, - digest: testDigest, - }, - }, - } - - const result = await recover(subdigestSignature, testAddress, ChainId.MAINNET, samplePayload, { - provider: 'assume-valid', - }) - - expect(result.configuration).toBeDefined() - // Weight should be 0 unless digest matches - expect(result.weight).toBe(0n) - }) - - it.skip('should handle binary tree topology', async () => { - // Binary tree with hash signatures has the same real crypto issue - const binaryTreeSignature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: [sampleRawSignerLeaf, sampleRawSignerLeaf] as RawNode, - }, - } - - const result = await recover(binaryTreeSignature, testAddress, ChainId.MAINNET, samplePayload, { - provider: 'assume-valid', - }) - - expect(result.configuration).toBeDefined() - expect(result.weight).toBeGreaterThan(0n) - }) - }) - }) - - describe('Edge Cases and Error Handling', () => { - it('should handle empty signature trees', () => { - expect(() => parseBranch(Bytes.fromArray([]))).not.toThrow() - - const result = parseBranch(Bytes.fromArray([])) - expect(result.nodes).toHaveLength(0) - expect(result.leftover).toHaveLength(0) - }) - - it('should handle maximum weights', () => { - const maxWeightSigner: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 255n, - } - - const encoded = encodeTopology(maxWeightSigner) - expect(encoded).toBeInstanceOf(Uint8Array) - }) - - it('should handle zero weights', () => { - const zeroWeightSigner: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 0n, - } - - // Zero weight actually gets encoded, it doesn't throw - const result = encodeTopology(zeroWeightSigner) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle large data in signatures', () => { - const largeDataSignature: SignatureOfSignerLeafErc1271 = { - type: 'erc1271', - address: testAddress, - data: ('0x' + '12'.repeat(1000)) as Hex.Hex, // Large data - } - - const signedLeaf = { - type: 'signer' as const, - address: testAddress, - weight: 1n, - signed: true as const, - signature: largeDataSignature, - } - - const result = encodeTopology(signedLeaf) - expect(result).toBeInstanceOf(Uint8Array) - }) - - it('should handle extremely large data', () => { - const extremeDataSignature: SignatureOfSignerLeafErc1271 = { - type: 'erc1271', - address: testAddress, - data: ('0x' + '12'.repeat(50000)) as Hex.Hex, // Extremely large data - } - - const signedLeaf = { - type: 'signer' as const, - address: testAddress, - weight: 1n, - signed: true as const, - signature: extremeDataSignature, - } - - // This might not actually throw - the implementation may handle large data - const result = encodeTopology(signedLeaf) - expect(result).toBeInstanceOf(Uint8Array) - }) - }) - - describe('Integration Tests', () => { - it('should handle complete encode/decode cycle', () => { - const encoded = encodeSignature(sampleRawSignature) - const decoded = decodeSignature(encoded) - - expect(decoded.noChainId).toBe(sampleRawSignature.noChainId) - expect(decoded.configuration.threshold).toBe(sampleRawConfig.threshold) - expect(decoded.configuration.checkpoint).toBe(sampleRawConfig.checkpoint) - }) - - it('should handle JSON round-trip with complex topology', () => { - const complexTopology: RawNode = [ - { - type: 'nested', - tree: sampleRawSignerLeaf, - weight: 2n, - threshold: 1n, - }, - { - type: 'subdigest', - digest: testDigest, - }, - ] - - const complexSignature = { - ...sampleRawSignature, - configuration: { - ...sampleRawConfig, - topology: complexTopology, - }, - } - - const json = rawSignatureToJson(complexSignature) - const deserialized = rawSignatureFromJson(json) - const reJson = rawSignatureToJson(deserialized) - - expect(json).toBe(reJson) - }) - - it.skip('should handle signature with all optional fields', () => { - const fullSignature: RawSignature = { - noChainId: true, - checkpointerData: Bytes.fromHex('0xdeadbeef'), - configuration: { - threshold: 3n, - checkpoint: 123n, - topology: sampleRawSignerLeaf, - checkpointer: testAddress, - }, - suffix: [ - { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 124n, - topology: sampleRawSignerLeaf, - }, - }, - ], - erc6492: { - to: testAddress2, - data: Bytes.fromHex('0x1234'), - }, - } - - const encoded = encodeSignature(fullSignature) - const decoded = decodeSignature(encoded) - - expect(decoded.noChainId).toBe(true) - expect(decoded.suffix).toHaveLength(1) - expect(decoded.erc6492).toBeDefined() - }) - }) - - describe('Real Cryptographic Recovery Tests', () => { - // Real RFC 6979 secp256k1 test vectors from Go standard library - // These are actual valid ECDSA signatures that recover to known addresses - const rfc6979TestVector = { - // From Go crypto/ecdsa tests - RFC 6979 P-256 test vector for message "sample" - privateKey: '0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721', - publicKey: { - x: '0x60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6', - y: '0x7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299', - }, - message: 'sample', - signature: { - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - } - - // Real secp256k1 test vector for message "test" - const rfc6979TestVector2 = { - privateKey: '0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721', // Same key - publicKey: { - x: '0x60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6', - y: '0x7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299', - }, - message: 'test', - signature: { - r: 0xf1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367n, - s: 0x019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083n, - yParity: 1 as const, - }, - } - - // Create realistic mock provider based on real ABI responses - const createRealisticMockProvider = () => { - return { - request: vi.fn().mockImplementation(async ({ method, params }) => { - if (method === 'eth_call') { - const [call] = params as any[] - - // Validate call structure - if (!call.to || !call.data) { - throw new Error('Invalid call parameters') - } - - // Mock ERC-1271 response (valid signature) - proper ABI encoding - if (call.data.startsWith('0x1626ba7e')) { - // IS_VALID_SIGNATURE selector - return properly encoded bytes4 - return '0x1626ba7e00000000000000000000000000000000000000000000000000000000' - } - - // Mock Sapient signature response - proper ABI encoding of bytes32 - if (call.data.includes('0x')) { - return '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' - } - - throw new Error('Unexpected eth_call') - } - - throw new Error(`Unexpected RPC method: ${method}`) - }), - } - } - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('Hash Signature Recovery', () => { - it('should recover addresses from real hash signatures using RFC 6979 test vectors', async () => { - const hashSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - ...rfc6979TestVector.signature, - }, - } as RawSignerLeaf, - }, - } - - // Create a real payload for testing - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - - // Test with real Secp256k1.recoverAddress! This covers lines 1106+ - const result = await recover(hashSignature, testAddress, ChainId.MAINNET, testPayload) - - // Verify the signature was actually recovered (not assumed valid) - expect(result.configuration.topology).toHaveProperty('type', 'signer') - expect(result.weight).toBe(1n) - - // The recovered address should be deterministic from the real signature - if (typeof result.configuration.topology === 'object' && 'address' in result.configuration.topology) { - expect(result.configuration.topology.address).toMatch(/^0x[a-fA-F0-9]{40}$/) - // The address should be consistently recovered from the same signature - expect(result.configuration.topology.address).toBeTruthy() - } - }) - - it('should recover addresses from real eth_sign signatures with working test vectors', async () => { - // Use the same working test vector but with eth_sign type - const ethSignSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'eth_sign', - ...rfc6979TestVector.signature, // Use the working test vector - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - - // Test real eth_sign recovery - const result = await recover(ethSignSignature, testAddress, ChainId.MAINNET, testPayload) - - expect(result.configuration.topology).toHaveProperty('type', 'signer') - expect(result.weight).toBe(1n) - }) - - it('should recover addresses from real hash signatures using different payloads', async () => { - // Test with a different payload to exercise more code paths - const hashSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - ...rfc6979TestVector.signature, - }, - } as RawSignerLeaf, - }, - } - - // Test with message payload - const messagePayload = Payload.fromMessage('0x48656c6c6f576f726c64' as Hex.Hex) - - const result = await recover(hashSignature, testAddress, ChainId.MAINNET, messagePayload) - - expect(result.configuration.topology).toHaveProperty('type', 'signer') - expect(result.weight).toBe(1n) - }) - }) - - describe('ERC-1271 Signature Validation with Real Provider', () => { - it('should validate ERC-1271 signatures with real provider calls and proper ABI encoding', async () => { - const mockProvider = createRealisticMockProvider() - - const erc1271Signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'erc1271', - address: testAddress, - data: '0x1234567890abcdef', - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress2, - value: 100n, - data: '0xabcdef', - gasLimit: 50000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'ignore', - }, - ]) - - // Test with real provider - this covers uncovered lines 1200+! - const result = await recover(erc1271Signature, testAddress, ChainId.MAINNET, testPayload, { - provider: mockProvider as any, - }) - - // Verify provider was called correctly for ERC-1271 validation - expect(mockProvider.request).toHaveBeenCalledWith({ - method: 'eth_call', - params: expect.arrayContaining([ - expect.objectContaining({ - to: testAddress, - data: expect.stringMatching(/^0x1626ba7e/), // IS_VALID_SIGNATURE selector - }), - ]), - }) - - expect(result.weight).toBe(1n) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'signer', - address: testAddress, - weight: 1n, - signed: true, - }) - } - }) - - it('should handle ERC-1271 validation failures with proper error checking', async () => { - const mockProvider = createRealisticMockProvider() - // Mock invalid signature response - proper ABI encoding but wrong value - mockProvider.request.mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000000') - - const erc1271Signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'erc1271', - address: testAddress, - data: '0x1234567890abcdef', - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress2, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'abort', - }, - ]) - - // Should throw for invalid signature - await expect( - recover(erc1271Signature, testAddress, ChainId.MAINNET, testPayload, { - provider: mockProvider as any, - }), - ).rejects.toThrow('invalid signer') - }) - }) - - describe('Sapient Signature Validation with Real Encoding', () => { - it('should validate sapient signatures with provider calls and proper payload encoding', async () => { - const mockProvider = createRealisticMockProvider() - - const sapientSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'sapient', - address: testAddress, - // Use exactly 32 bytes of signature data (64 hex chars + 0x) - data: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress2, - value: 1000n, - data: '0xdeadbeef', - gasLimit: 100000n, - delegateCall: true, - onlyFallback: false, - behaviorOnError: 'abort', - }, - ]) - - // This covers the encode() helper function in lines 1335-1399! - const result = await recover(sapientSignature, testAddress, ChainId.MAINNET, testPayload, { - provider: mockProvider as any, - }) - - // Verify provider was called for sapient signature recovery - expect(mockProvider.request).toHaveBeenCalled() - expect(result.weight).toBe(1n) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'sapient-signer', - address: testAddress, - weight: 1n, - }) - } - }) - - it('should validate sapient_compact signatures with proper ABI encoding', async () => { - const mockProvider = createRealisticMockProvider() - - const sapientCompactSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'sapient_compact', - address: testAddress2, - // Use exactly 32 bytes of signature data - data: '0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba', - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: true, - behaviorOnError: 'ignore', - }, - ]) - - const result = await recover(sapientCompactSignature, testAddress, ChainId.MAINNET, testPayload, { - provider: mockProvider as any, - }) - - expect(result.weight).toBe(1n) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'sapient-signer', - address: testAddress2, - weight: 1n, - }) - } - }) - }) - - describe('Encode Helper Function Coverage', () => { - it('should encode different payload types correctly and test all encode paths', async () => { - const mockProvider = createRealisticMockProvider() - - // Test all different payload types to cover encode() helper lines 1335-1399 - const payloadTypes = [ - { - name: 'call payload', - payload: Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 500n, - data: '0x12345678', - gasLimit: 75000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]), - }, - { - name: 'message payload', - payload: Payload.fromMessage('0x48656c6c6f20576f726c64' as Hex.Hex), - }, - // Temporarily skip config-update to isolate the bytes33 issue - // { - // name: 'config-update payload', - // payload: Payload.fromConfigUpdate( - // '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex, - // ), - // }, - { - name: 'digest payload', - payload: Payload.fromDigest( - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex, - ), - }, - ] - - for (const { payload } of payloadTypes) { - const sapientSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'sapient', - address: testAddress, - data: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - }, - } as RawSignerLeaf, - }, - } - - // This exercises the encode function for different payload types - const result = await recover(sapientSignature, testAddress, ChainId.MAINNET, payload, { - provider: mockProvider as any, - }) - - expect(result.weight).toBe(1n) - expect(mockProvider.request).toHaveBeenCalled() - } - }) - - it('should handle behaviorOnError variations in encode function', async () => { - const mockProvider = createRealisticMockProvider() - - // Test different behaviorOnError values to ensure all paths in encode are covered - const behaviorVariations = ['ignore', 'revert', 'abort'] as const - - for (const behavior of behaviorVariations) { - const sapientSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'sapient', - address: testAddress, - data: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - }, - } as RawSignerLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: behavior, // This tests the encode function's behaviorOnError mapping - }, - ]) - - const result = await recover(sapientSignature, testAddress, ChainId.MAINNET, testPayload, { - provider: mockProvider as any, - }) - - expect(result.weight).toBe(1n) - } - }) - }) - - describe('Topology Type Coverage Tests', () => { - it('should handle RawNestedLeaf topology (line 1302)', async () => { - const nestedLeaf: RawNestedLeaf = { - type: 'nested', - tree: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n, - s: 0xf7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8n, - yParity: 0 as const, - }, - }, - weight: 2n, - threshold: 1n, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: nestedLeaf, // This covers line 1302 (isRawNestedLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.weight).toBeGreaterThanOrEqual(0n) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology.type).toBe('nested') - } - }) - - it('should handle SignerLeaf topology (line 1307)', async () => { - const signerLeaf: SignerLeaf = { - type: 'signer', - address: testAddress, - weight: 1n, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: signerLeaf, // This covers line 1307 (isSignerLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.weight).toBe(0n) // SignerLeaf without signature returns 0 weight - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'signer', - address: testAddress, - weight: 1n, - }) - } - }) - - it('should handle SapientSignerLeaf topology (line 1309)', async () => { - const sapientSignerLeaf: SapientSignerLeaf = { - type: 'sapient-signer', - address: testAddress, - weight: 1n, - imageHash: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' as Hex.Hex, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: sapientSignerLeaf as any, // This covers line 1309 (isSapientSignerLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.weight).toBe(0n) // SapientSignerLeaf without signature returns 0 weight - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'sapient-signer', - address: testAddress, - weight: 1n, - }) - } - }) - - it('should handle SubdigestLeaf topology with matching digest (line 1314)', async () => { - // Import hash function for this test - const { hash } = await import('../src/payload.js') - - // Create a payload and calculate its digest to match - const digest = hash(testAddress, ChainId.MAINNET, samplePayload) - - const subdigestLeaf = { - type: 'subdigest' as const, - digest: Bytes.toHex(digest) as `0x${string}`, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: subdigestLeaf, // This covers line 1314 (isSubdigestLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - // Should return max weight when digest matches - expect(result.weight).toBe(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'subdigest', - digest: Bytes.toHex(digest), - }) - } - }) - - it('should handle SubdigestLeaf topology with non-matching digest', async () => { - const subdigestLeaf = { - type: 'subdigest' as const, - digest: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as `0x${string}`, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: subdigestLeaf, - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - // Should return 0 weight when digest doesn't match - expect(result.weight).toBe(0n) - }) - - it('should handle AnyAddressSubdigestLeaf topology (lines 1318-1332)', async () => { - // Import hash function for this test - const { hash } = await import('../src/payload.js') - - // Create a payload and calculate its any-address digest - const anyAddressOpHash = hash( - '0x0000000000000000000000000000000000000000' as Address.Address, - ChainId.MAINNET, - samplePayload, - ) - - const anyAddressSubdigestLeaf = { - type: 'any-address-subdigest' as const, - digest: Bytes.toHex(anyAddressOpHash) as `0x${string}`, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: anyAddressSubdigestLeaf, // This covers lines 1318-1332 (isAnyAddressSubdigestLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - // Should return max weight when any-address digest matches - expect(result.weight).toBe(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toMatchObject({ - type: 'any-address-subdigest', - digest: Bytes.toHex(anyAddressOpHash), - }) - } - }) - - it('should handle AnyAddressSubdigestLeaf with non-matching digest', async () => { - const anyAddressSubdigestLeaf = { - type: 'any-address-subdigest' as const, - digest: '0x9999999999999999999999999999999999999999999999999999999999999999' as `0x${string}`, - } - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: anyAddressSubdigestLeaf, - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - // Should return 0 weight when any-address digest doesn't match - expect(result.weight).toBe(0n) - }) - - it('should handle NodeLeaf topology (line 1325)', async () => { - const nodeLeaf = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hex.Hex - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: nodeLeaf, // This covers line 1325 (isNodeLeaf) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.weight).toBe(0n) // NodeLeaf returns 0 weight - expect(result.configuration.topology).toBe(nodeLeaf) - }) - - it('should handle binary tree topology (lines 1327-1331)', async () => { - const binaryTree: [SignerLeaf, SignerLeaf] = [ - { type: 'signer', address: testAddress, weight: 1n }, - { type: 'signer', address: testAddress2, weight: 1n }, - ] - - const signature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: binaryTree, // This covers lines 1327-1331 (binary tree handling) - }, - } - - const result = await recover(signature, testAddress, ChainId.MAINNET, samplePayload) - - expect(result.weight).toBe(0n) // Both signers without signatures = 0 weight - expect(Array.isArray(result.configuration.topology)).toBe(true) - if (Array.isArray(result.configuration.topology)) { - expect(result.configuration.topology).toHaveLength(2) - } - }) - }) - - describe('Chained Signatures with Real Crypto', () => { - it.skip('should handle chained signature recovery with real signatures', async () => { - // Skip this test as the second test vector is problematic - const chainedSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - ...rfc6979TestVector.signature, - }, - } as RawSignerLeaf, - }, - suffix: [ - { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 1n, - topology: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - ...rfc6979TestVector2.signature, - }, - } as RawSignerLeaf, - }, - }, - ], - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - - // Test chained signature recovery - this covers the suffix handling in recover() - const result = await recover(chainedSignature, testAddress, ChainId.MAINNET, testPayload) - - expect(result.weight).toBeGreaterThanOrEqual(0n) - expect(result.configuration).toBeDefined() - }) - }) - - describe('Nested Signatures with Real Crypto', () => { - it('should handle nested signature recovery with real signatures', async () => { - const nestedSignature: RawSignature = { - noChainId: false, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'nested', - weight: 2n, - threshold: 1n, - tree: { - type: 'unrecovered-signer', - weight: 1n, - signature: { - type: 'hash', - ...rfc6979TestVector.signature, - }, - } as RawSignerLeaf, - } as RawNestedLeaf, - }, - } - - const testPayload = Payload.fromCall(1n, 0n, [ - { - to: testAddress, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - - const result = await recover(nestedSignature, testAddress, ChainId.MAINNET, testPayload) - - expect(result.weight).toBeGreaterThanOrEqual(0n) - if (typeof result.configuration.topology === 'object' && 'type' in result.configuration.topology) { - expect(result.configuration.topology).toHaveProperty('type', 'nested') - } - }) - }) - }) -}) diff --git a/packages/wallet/primitives/test/utils.test.ts b/packages/wallet/primitives/test/utils.test.ts deleted file mode 100644 index dc8392c328..0000000000 --- a/packages/wallet/primitives/test/utils.test.ts +++ /dev/null @@ -1,541 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Bytes } from 'ox' - -import { - minBytesFor, - packRSY, - unpackRSY, - createJSONReplacer, - createJSONReviver, - toJSON, - fromJSON, -} from '../src/utils.js' - -describe('Utils', () => { - describe('minBytesFor', () => { - it('should return correct byte count for small numbers', () => { - expect(minBytesFor(0n)).toBe(1) // 0 still needs 1 byte - expect(minBytesFor(1n)).toBe(1) - expect(minBytesFor(15n)).toBe(1) // 0xF - expect(minBytesFor(16n)).toBe(1) // 0x10 - expect(minBytesFor(255n)).toBe(1) // 0xFF - }) - - it('should return correct byte count for medium numbers', () => { - expect(minBytesFor(256n)).toBe(2) // 0x100 - expect(minBytesFor(65535n)).toBe(2) // 0xFFFF - expect(minBytesFor(65536n)).toBe(3) // 0x10000 - expect(minBytesFor(16777215n)).toBe(3) // 0xFFFFFF - }) - - it('should return correct byte count for large numbers', () => { - expect(minBytesFor(16777216n)).toBe(4) // 0x1000000 - expect(minBytesFor(4294967295n)).toBe(4) // 0xFFFFFFFF - expect(minBytesFor(4294967296n)).toBe(5) // 0x100000000 - }) - - it('should handle very large BigInt values', () => { - const largeBigInt = BigInt('0x' + 'FF'.repeat(32)) // 32 bytes of 0xFF - expect(minBytesFor(largeBigInt)).toBe(32) - - const evenLargerBigInt = BigInt('0x1' + '00'.repeat(32)) // 33 bytes - expect(minBytesFor(evenLargerBigInt)).toBe(33) - }) - - it('should handle odd hex length numbers', () => { - expect(minBytesFor(0xfffn)).toBe(2) // 3 hex chars -> 2 bytes - expect(minBytesFor(0xfffffn)).toBe(3) // 5 hex chars -> 3 bytes - }) - }) - - describe('packRSY and unpackRSY (ERC-2098)', () => { - const sampleSignature = { - r: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn, - s: 0x7777777777777777777777777777777777777777777777777777777777777777n, - yParity: 0, - } - - const sampleSignatureOddParity = { - r: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn, - s: 0x7777777777777777777777777777777777777777777777777777777777777777n, - yParity: 1, - } - - describe('packRSY', () => { - it('should pack signature with even yParity correctly', () => { - const packed = packRSY(sampleSignature) - - expect(packed.length).toBe(64) // 32 bytes r + 32 bytes s - - // Check r part (first 32 bytes) - const rPart = packed.slice(0, 32) - expect(Bytes.toBigInt(rPart)).toBe(sampleSignature.r) - - // Check s part (last 32 bytes) - should not have high bit set - const sPart = packed.slice(32, 64) - expect(sPart[0]! & 0x80).toBe(0) // High bit should be 0 for even parity - expect(Bytes.toBigInt(sPart)).toBe(sampleSignature.s) - }) - - it('should pack signature with odd yParity correctly', () => { - const packed = packRSY(sampleSignatureOddParity) - - expect(packed.length).toBe(64) - - // Check r part (first 32 bytes) - const rPart = packed.slice(0, 32) - expect(Bytes.toBigInt(rPart)).toBe(sampleSignatureOddParity.r) - - // Check s part (last 32 bytes) - should have high bit set - const sPart = packed.slice(32, 64) - expect(sPart[0]! & 0x80).toBe(0x80) // High bit should be 1 for odd parity - }) - - it('should handle zero values', () => { - const zeroSignature = { r: 0n, s: 0n, yParity: 0 } - const packed = packRSY(zeroSignature) - - expect(packed.length).toBe(64) - expect(packed.every((byte) => byte === 0)).toBe(true) - }) - - it('should handle maximum values', () => { - const maxSignature = { - r: BigInt('0x' + 'FF'.repeat(32)), - s: BigInt('0x7F' + 'FF'.repeat(31)), // Max s without high bit - yParity: 1, - } - const packed = packRSY(maxSignature) - - expect(packed.length).toBe(64) - expect(packed[0]).toBe(0xff) // First byte of r - expect(packed[32]! & 0x80).toBe(0x80) // High bit set for odd parity - }) - }) - - describe('unpackRSY', () => { - it('should unpack signature with even yParity correctly', () => { - const packed = packRSY(sampleSignature) - const unpacked = unpackRSY(packed) - - expect(unpacked.r).toBe(sampleSignature.r) - expect(unpacked.s).toBe(sampleSignature.s) - expect(unpacked.yParity).toBe(0) - }) - - it('should unpack signature with odd yParity correctly', () => { - const packed = packRSY(sampleSignatureOddParity) - const unpacked = unpackRSY(packed) - - expect(unpacked.r).toBe(sampleSignatureOddParity.r) - expect(unpacked.s).toBe(sampleSignatureOddParity.s) - expect(unpacked.yParity).toBe(1) - }) - - it('should handle round-trip packing/unpacking', () => { - const original = sampleSignature - const packed = packRSY(original) - const unpacked = unpackRSY(packed) - - expect(unpacked).toEqual(original) - }) - - it('should handle round-trip with odd parity', () => { - const original = sampleSignatureOddParity - const packed = packRSY(original) - const unpacked = unpackRSY(packed) - - expect(unpacked).toEqual(original) - }) - - it('should handle edge case where s has high bit naturally set', () => { - const signatureWithHighS = { - r: 0x1111111111111111111111111111111111111111111111111111111111111111n, - s: 0x7888888888888888888888888888888888888888888888888888888888888888n, // High bit naturally set but below 0x8000... - yParity: 0, - } - - const packed = packRSY(signatureWithHighS) - const unpacked = unpackRSY(packed) - - expect(unpacked.r).toBe(signatureWithHighS.r) - expect(unpacked.s).toBe(signatureWithHighS.s) - expect(unpacked.yParity).toBe(0) - }) - - it('should properly extract yParity when s naturally has high bit and yParity is 1', () => { - const signatureWithHighS = { - r: 0x1111111111111111111111111111111111111111111111111111111111111111n, - s: 0x7888888888888888888888888888888888888888888888888888888888888888n, - yParity: 1, - } - - const packed = packRSY(signatureWithHighS) - const unpacked = unpackRSY(packed) - - expect(unpacked.r).toBe(signatureWithHighS.r) - expect(unpacked.s).toBe(signatureWithHighS.s) - expect(unpacked.yParity).toBe(1) - }) - }) - }) - - describe('JSON utilities', () => { - describe('createJSONReplacer', () => { - it('should handle BigInt values', () => { - const replacer = createJSONReplacer() - const result = replacer('test', 123456789n) - - expect(result).toEqual({ __bigint: '0x75bcd15' }) - }) - - it('should handle Uint8Array values', () => { - const replacer = createJSONReplacer() - const uint8Array = new Uint8Array([1, 2, 3, 255]) - const result = replacer('test', uint8Array) - - expect(result).toEqual({ __uint8array: [1, 2, 3, 255] }) - }) - - it('should handle regular values unchanged', () => { - const replacer = createJSONReplacer() - - expect(replacer('key', 'string')).toBe('string') - expect(replacer('key', 42)).toBe(42) - expect(replacer('key', true)).toBe(true) - expect(replacer('key', null)).toBe(null) - expect(replacer('key', { a: 1 })).toEqual({ a: 1 }) - }) - - it('should apply custom replacer after BigInt/Uint8Array handling', () => { - const customReplacer = (key: string, value: any) => { - if (typeof value === 'string' && value === 'replace-me') { - return 'replaced' - } - return value - } - - const replacer = createJSONReplacer(customReplacer) - - expect(replacer('key', 'replace-me')).toBe('replaced') - expect(replacer('key', 'normal')).toBe('normal') - expect(replacer('key', 123n)).toEqual({ __bigint: '0x7b' }) - }) - - it('should handle zero BigInt', () => { - const replacer = createJSONReplacer() - const result = replacer('test', 0n) - - expect(result).toEqual({ __bigint: '0x0' }) - }) - - it('should handle large BigInt', () => { - const replacer = createJSONReplacer() - const largeBigInt = BigInt('0x' + 'FF'.repeat(32)) - const result = replacer('test', largeBigInt) - - expect(result).toEqual({ __bigint: '0x' + 'ff'.repeat(32) }) - }) - }) - - describe('createJSONReviver', () => { - it('should revive BigInt values', () => { - const reviver = createJSONReviver() - const result = reviver('test', { __bigint: '0x75bcd15' }) - - expect(result).toBe(123456789n) - }) - - it('should revive Uint8Array values', () => { - const reviver = createJSONReviver() - const result = reviver('test', { __uint8array: [1, 2, 3, 255] }) as Uint8Array - - expect(result).toBeInstanceOf(Uint8Array) - expect(Array.from(result)).toEqual([1, 2, 3, 255]) - }) - - it('should handle regular values unchanged', () => { - const reviver = createJSONReviver() - - expect(reviver('key', 'string')).toBe('string') - expect(reviver('key', 42)).toBe(42) - expect(reviver('key', true)).toBe(true) - expect(reviver('key', null)).toBe(null) - expect(reviver('key', { a: 1 })).toEqual({ a: 1 }) - }) - - it('should apply custom reviver after BigInt/Uint8Array handling', () => { - const customReviver = (key: string, value: any) => { - if (typeof value === 'string' && value === 'revive-me') { - return 'revived' - } - return value - } - - const reviver = createJSONReviver(customReviver) - - expect(reviver('key', 'revive-me')).toBe('revived') - expect(reviver('key', 'normal')).toBe('normal') - expect(reviver('key', { __bigint: '0x7b' })).toBe(123n) - }) - - it('should not revive malformed BigInt objects', () => { - const reviver = createJSONReviver() - - // Missing 0x prefix - expect(reviver('test', { __bigint: '75bcd15' })).toEqual({ __bigint: '75bcd15' }) - - // Extra properties - expect(reviver('test', { __bigint: '0x7b', extra: 'prop' })).toEqual({ __bigint: '0x7b', extra: 'prop' }) - - // Wrong type - expect(reviver('test', { __bigint: 123 })).toEqual({ __bigint: 123 }) - }) - - it('should not revive malformed Uint8Array objects', () => { - const reviver = createJSONReviver() - - // Not an array - expect(reviver('test', { __uint8array: 'not-array' })).toEqual({ __uint8array: 'not-array' }) - - // Extra properties - expect(reviver('test', { __uint8array: [1, 2], extra: 'prop' })).toEqual({ - __uint8array: [1, 2], - extra: 'prop', - }) - }) - - it('should handle zero BigInt', () => { - const reviver = createJSONReviver() - const result = reviver('test', { __bigint: '0x0' }) - - expect(result).toBe(0n) - }) - - it('should handle empty Uint8Array', () => { - const reviver = createJSONReviver() - const result = reviver('test', { __uint8array: [] }) as Uint8Array - - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBe(0) - }) - }) - - describe('toJSON', () => { - it('should serialize simple objects', () => { - const obj = { a: 1, b: 'test', c: true } - const result = toJSON(obj) - - expect(result).toBe('{"a":1,"b":"test","c":true}') - }) - - it('should serialize objects with BigInt', () => { - const obj = { value: 123456789n, name: 'test' } - const result = toJSON(obj) - - const parsed = JSON.parse(result) - expect(parsed.value).toEqual({ __bigint: '0x75bcd15' }) - expect(parsed.name).toBe('test') - }) - - it('should serialize objects with Uint8Array', () => { - const obj = { data: new Uint8Array([1, 2, 3]), name: 'test' } - const result = toJSON(obj) - - const parsed = JSON.parse(result) - expect(parsed.data).toEqual({ __uint8array: [1, 2, 3] }) - expect(parsed.name).toBe('test') - }) - - it('should serialize complex nested objects', () => { - const obj = { - id: 42n, - buffer: new Uint8Array([255, 0, 128]), - nested: { - value: 999n, - array: [1, 2n, new Uint8Array([10, 20])], - }, - } - - const result = toJSON(obj) - const parsed = JSON.parse(result) - - expect(parsed.id).toEqual({ __bigint: '0x2a' }) - expect(parsed.buffer).toEqual({ __uint8array: [255, 0, 128] }) - expect(parsed.nested.value).toEqual({ __bigint: '0x3e7' }) - expect(parsed.nested.array[1]).toEqual({ __bigint: '0x2' }) - expect(parsed.nested.array[2]).toEqual({ __uint8array: [10, 20] }) - }) - - it('should handle space parameter for pretty printing', () => { - const obj = { a: 1, b: 2n } - const result = toJSON(obj, null, 2) - - expect(result).toContain('\n') - expect(result).toContain(' ') - }) - - it('should work with custom replacer function', () => { - const customReplacer = (key: string, value: any) => { - if (key === 'secret') return undefined - return value - } - - const obj = { public: 'visible', secret: 'hidden', big: 123n } - const result = toJSON(obj, customReplacer) - const parsed = JSON.parse(result) - - expect(parsed.public).toBe('visible') - expect(parsed.secret).toBeUndefined() - expect(parsed.big).toEqual({ __bigint: '0x7b' }) - }) - }) - - describe('fromJSON', () => { - it('should deserialize simple objects', () => { - const json = '{"a":1,"b":"test","c":true}' - const result = fromJSON(json) - - expect(result).toEqual({ a: 1, b: 'test', c: true }) - }) - - it('should deserialize objects with BigInt', () => { - const json = '{"value":{"__bigint":"0x75bcd15"},"name":"test"}' - const result = fromJSON(json) as { value: bigint; name: string } - - expect(result.value).toBe(123456789n) - expect(result.name).toBe('test') - }) - - it('should deserialize objects with Uint8Array', () => { - const json = '{"data":{"__uint8array":[1,2,3]},"name":"test"}' - const result = fromJSON(json) as { data: Uint8Array; name: string } - - expect(result.data).toBeInstanceOf(Uint8Array) - expect(Array.from(result.data)).toEqual([1, 2, 3]) - expect(result.name).toBe('test') - }) - - it('should handle round-trip serialization', () => { - const original = { - id: 42n, - buffer: new Uint8Array([255, 0, 128]), - nested: { - value: 999n, - array: [1, 2n, new Uint8Array([10, 20])], - }, - normal: 'string', - } - - const json = toJSON(original) - const result = fromJSON(json) - - expect(result.id).toBe(42n) - expect(result.buffer).toBeInstanceOf(Uint8Array) - expect(Array.from(result.buffer)).toEqual([255, 0, 128]) - expect(result.nested.value).toBe(999n) - expect(result.nested.array[1]).toBe(2n) - expect(result.nested.array[2]).toBeInstanceOf(Uint8Array) - expect(Array.from(result.nested.array[2])).toEqual([10, 20]) - expect(result.normal).toBe('string') - }) - - it('should work with custom reviver function', () => { - const customReviver = (key: string, value: any) => { - if (key === 'timestamp' && typeof value === 'number') { - return new Date(value) - } - return value - } - - const json = '{"timestamp":1640995200000,"big":{"__bigint":"0x7b"}}' - const result = fromJSON(json, customReviver) - - expect(result.timestamp).toBeInstanceOf(Date) - expect(result.big).toBe(123n) - }) - - it('should handle malformed JSON gracefully', () => { - expect(() => fromJSON('invalid json')).toThrow() - }) - }) - - describe('Edge cases and integration', () => { - it('should handle arrays with mixed types', () => { - const original = [1, 'string', 42n, new Uint8Array([1, 2]), { nested: 99n }] - const json = toJSON(original) - const result = fromJSON(json) - - expect(result[0]).toBe(1) - expect(result[1]).toBe('string') - expect(result[2]).toBe(42n) - expect(result[3]).toBeInstanceOf(Uint8Array) - expect(Array.from(result[3])).toEqual([1, 2]) - expect(result[4].nested).toBe(99n) - }) - - it('should preserve object types after round-trip', () => { - // Test that demonstrates how custom replacer/reviver work with the utility functions - const original = { - bigint: 123n, - uint8: new Uint8Array([1, 2, 3]), - timestamp: Date.now(), // Use a number instead of Date object for simplicity - } - - // Test that custom transformations work correctly - const customReplacer = (key: string, value: any) => { - if (key === 'timestamp' && typeof value === 'number') { - return { __timestamp: value } - } - return value - } - - const customReviver = (key: string, value: any) => { - if (value && typeof value === 'object' && '__timestamp' in value && Object.keys(value).length === 1) { - return value.__timestamp * 2 // Transform the value to show reviver worked - } - return value - } - - const replacerFunc = createJSONReplacer(customReplacer) - const json = JSON.stringify(original, replacerFunc) - const result = fromJSON(json, customReviver) - - expect(result.timestamp).toBe(original.timestamp * 2) // Should be doubled by reviver - expect(result.bigint).toBe(123n) - expect(result.uint8).toBeInstanceOf(Uint8Array) - expect(Array.from(result.uint8)).toEqual([1, 2, 3]) - }) - - it('should handle deeply nested structures', () => { - const deep = { level1: { level2: { level3: { big: 999n } } } } - const json = toJSON(deep) - const result = fromJSON(json) - - expect(result.level1.level2.level3.big).toBe(999n) - }) - - it('should handle empty and null values', () => { - const obj = { - empty: {}, - nullValue: null, - undefinedValue: undefined, - emptyArray: [], - emptyUint8: new Uint8Array(0), - zeroBig: 0n, - } - - const json = toJSON(obj) - const result = fromJSON(json) - - expect(result.empty).toEqual({}) - expect(result.nullValue).toBe(null) - expect(result.undefinedValue).toBeUndefined() - expect(result.emptyArray).toEqual([]) - expect(result.emptyUint8).toBeInstanceOf(Uint8Array) - expect(result.emptyUint8.length).toBe(0) - expect(result.zeroBig).toBe(0n) - }) - }) - }) -}) diff --git a/packages/wallet/primitives/tsconfig.json b/packages/wallet/primitives/tsconfig.json deleted file mode 100644 index 1e325a596c..0000000000 --- a/packages/wallet/primitives/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "sourceMap": true - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/wallet/primitives/vitest.config.ts b/packages/wallet/primitives/vitest.config.ts deleted file mode 100644 index 0b2f7c6c76..0000000000 --- a/packages/wallet/primitives/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - poolOptions: { - singleThread: true, - }, - }, -}) diff --git a/packages/wallet/wdk/CHANGELOG.md b/packages/wallet/wdk/CHANGELOG.md deleted file mode 100644 index 5101c28c5a..0000000000 --- a/packages/wallet/wdk/CHANGELOG.md +++ /dev/null @@ -1,339 +0,0 @@ -# @0xsequence/wallet-wdk - -## 3.0.5 - -### Patch Changes - -- Account federation support -- Updated dependencies - - @0xsequence/guard@3.0.5 - - @0xsequence/identity-instrument@3.0.5 - - @0xsequence/relayer@3.0.5 - - @0xsequence/wallet-core@3.0.5 - - @0xsequence/wallet-primitives@3.0.5 - -## 3.0.4 - -### Patch Changes - -- id-token login support -- Updated dependencies - - @0xsequence/guard@3.0.4 - - @0xsequence/identity-instrument@3.0.4 - - @0xsequence/relayer@3.0.4 - - @0xsequence/wallet-core@3.0.4 - - @0xsequence/wallet-primitives@3.0.4 - -## 3.0.3 - -### Patch Changes - -- 3.0.3 -- Updated dependencies - - @0xsequence/guard@3.0.3 - - @0xsequence/identity-instrument@3.0.3 - - @0xsequence/relayer@3.0.3 - - @0xsequence/wallet-core@3.0.3 - - @0xsequence/wallet-primitives@3.0.3 - -## 3.0.2 - -### Patch Changes - -- allow native self transfer -- Updated dependencies - - @0xsequence/guard@3.0.2 - - @0xsequence/identity-instrument@3.0.2 - - @0xsequence/relayer@3.0.2 - - @0xsequence/wallet-core@3.0.2 - - @0xsequence/wallet-primitives@3.0.2 - -## 3.0.1 - -### Patch Changes - -- Network and session fixes -- Updated dependencies - - @0xsequence/guard@3.0.1 - - @0xsequence/identity-instrument@3.0.1 - - @0xsequence/relayer@3.0.1 - - @0xsequence/wallet-core@3.0.1 - - @0xsequence/wallet-primitives@3.0.1 - -## 3.0.0 - -### Patch Changes - -- f68be62: ethauth support -- 49d8a2f: New chains, minor fixes -- 3411232: Beta release with dapp connector fixes -- 23cb9e9: New chains, relayer rpc fix -- f5f6a7a: dapp-client updates -- e7de3b1: Fix signer 404 error, minor fixes -- 493836f: multicall3 optimization -- 30e1f1a: 3.0.0 beta -- d5017e8: Beta release for v3 -- 24a5fab: Final RC before 3.0.0 -- e5e1a03: Apple auth fixes -- 0b63113: Apple auth fix -- a89134a: Userdata service updates -- 7c6c811: 3.0.0-beta.3 with fixes -- 3.0.0 release -- 98ce38b: 3.0.0-beta.2 with identity instrument updates -- 747e6b5: Relayer fee options fix -- 40c19ff: dapp client updates for EOA login -- 6d5de25: 3.0.0-beta.1 -- 934acd1: RC5 upgrade -- Updated dependencies [f68be62] -- Updated dependencies [49d8a2f] -- Updated dependencies [3411232] -- Updated dependencies [23cb9e9] -- Updated dependencies [f5f6a7a] -- Updated dependencies [e7de3b1] -- Updated dependencies [493836f] -- Updated dependencies [30e1f1a] -- Updated dependencies [d5017e8] -- Updated dependencies [24a5fab] -- Updated dependencies [e5e1a03] -- Updated dependencies [0b63113] -- Updated dependencies [a89134a] -- Updated dependencies [7c6c811] -- Updated dependencies -- Updated dependencies [98ce38b] -- Updated dependencies [747e6b5] -- Updated dependencies [40c19ff] -- Updated dependencies [6d5de25] -- Updated dependencies [934acd1] - - @0xsequence/guard@3.0.0 - - @0xsequence/identity-instrument@3.0.0 - - @0xsequence/relayer@3.0.0 - - @0xsequence/wallet-core@3.0.0 - - @0xsequence/wallet-primitives@3.0.0 - -## 3.0.0-beta.19 - -### Patch Changes - -- Final RC before 3.0.0 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.19 - - @0xsequence/identity-instrument@3.0.0-beta.19 - - @0xsequence/relayer@3.0.0-beta.19 - - @0xsequence/wallet-core@3.0.0-beta.19 - - @0xsequence/wallet-primitives@3.0.0-beta.19 - -## 3.0.0-beta.18 - -### Patch Changes - -- multicall3 optimization -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.18 - - @0xsequence/identity-instrument@3.0.0-beta.18 - - @0xsequence/relayer@3.0.0-beta.18 - - @0xsequence/wallet-core@3.0.0-beta.18 - - @0xsequence/wallet-primitives@3.0.0-beta.18 - -## 3.0.0-beta.17 - -### Patch Changes - -- New chains, relayer rpc fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.17 - - @0xsequence/identity-instrument@3.0.0-beta.17 - - @0xsequence/relayer@3.0.0-beta.17 - - @0xsequence/wallet-core@3.0.0-beta.17 - - @0xsequence/wallet-primitives@3.0.0-beta.17 - -## 3.0.0-beta.16 - -### Patch Changes - -- ethauth support -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.16 - - @0xsequence/identity-instrument@3.0.0-beta.16 - - @0xsequence/relayer@3.0.0-beta.16 - - @0xsequence/wallet-core@3.0.0-beta.16 - - @0xsequence/wallet-primitives@3.0.0-beta.16 - -## 3.0.0-beta.15 - -### Patch Changes - -- New chains, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.15 - - @0xsequence/identity-instrument@3.0.0-beta.15 - - @0xsequence/relayer@3.0.0-beta.15 - - @0xsequence/wallet-core@3.0.0-beta.15 - - @0xsequence/wallet-primitives@3.0.0-beta.15 - -## 3.0.0-beta.14 - -### Patch Changes - -- Relayer fee options fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.14 - - @0xsequence/identity-instrument@3.0.0-beta.14 - - @0xsequence/relayer@3.0.0-beta.14 - - @0xsequence/wallet-core@3.0.0-beta.14 - - @0xsequence/wallet-primitives@3.0.0-beta.14 - -## 3.0.0-beta.13 - -### Patch Changes - -- Userdata service updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.13 - - @0xsequence/identity-instrument@3.0.0-beta.13 - - @0xsequence/relayer@3.0.0-beta.13 - - @0xsequence/wallet-core@3.0.0-beta.13 - - @0xsequence/wallet-primitives@3.0.0-beta.13 - -## 3.0.0-beta.12 - -### Patch Changes - -- Beta release with dapp connector fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.12 - - @0xsequence/identity-instrument@3.0.0-beta.12 - - @0xsequence/relayer@3.0.0-beta.12 - - @0xsequence/wallet-core@3.0.0-beta.12 - - @0xsequence/wallet-primitives@3.0.0-beta.12 - -## 3.0.0-beta.11 - -### Patch Changes - -- 3.0.0 beta -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.11 - - @0xsequence/identity-instrument@3.0.0-beta.11 - - @0xsequence/relayer@3.0.0-beta.11 - - @0xsequence/wallet-core@3.0.0-beta.11 - - @0xsequence/wallet-primitives@3.0.0-beta.11 - -## 3.0.0-beta.10 - -### Patch Changes - -- dapp-client updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.10 - - @0xsequence/identity-instrument@3.0.0-beta.10 - - @0xsequence/relayer@3.0.0-beta.10 - - @0xsequence/wallet-core@3.0.0-beta.10 - - @0xsequence/wallet-primitives@3.0.0-beta.10 - -## 3.0.0-beta.9 - -### Patch Changes - -- dapp client updates for EOA login -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.9 - - @0xsequence/identity-instrument@3.0.0-beta.9 - - @0xsequence/relayer@3.0.0-beta.9 - - @0xsequence/wallet-core@3.0.0-beta.9 - - @0xsequence/wallet-primitives@3.0.0-beta.9 - -## 3.0.0-beta.8 - -### Patch Changes - -- Apple auth fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.8 - - @0xsequence/identity-instrument@3.0.0-beta.8 - - @0xsequence/relayer@3.0.0-beta.8 - - @0xsequence/wallet-core@3.0.0-beta.8 - - @0xsequence/wallet-primitives@3.0.0-beta.8 - -## 3.0.0-beta.7 - -### Patch Changes - -- Apple auth fix -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.7 - - @0xsequence/identity-instrument@3.0.0-beta.7 - - @0xsequence/relayer@3.0.0-beta.7 - - @0xsequence/wallet-core@3.0.0-beta.7 - - @0xsequence/wallet-primitives@3.0.0-beta.7 - -## 3.0.0-beta.6 - -### Patch Changes - -- Fix signer 404 error, minor fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.6 - - @0xsequence/identity-instrument@3.0.0-beta.6 - - @0xsequence/relayer@3.0.0-beta.6 - - @0xsequence/wallet-core@3.0.0-beta.6 - - @0xsequence/wallet-primitives@3.0.0-beta.6 - -## 3.0.0-beta.5 - -### Patch Changes - -- Beta release for v3 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.5 - - @0xsequence/identity-instrument@3.0.0-beta.5 - - @0xsequence/relayer@3.0.0-beta.5 - - @0xsequence/wallet-core@3.0.0-beta.5 - - @0xsequence/wallet-primitives@3.0.0-beta.5 - -## 3.0.0-beta.4 - -### Patch Changes - -- RC5 upgrade -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.4 - - @0xsequence/identity-instrument@3.0.0-beta.4 - - @0xsequence/relayer@3.0.0-beta.4 - - @0xsequence/wallet-core@3.0.0-beta.4 - - @0xsequence/wallet-primitives@3.0.0-beta.4 - -## 3.0.0-beta.3 - -### Patch Changes - -- 3.0.0-beta.3 with fixes -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.3 - - @0xsequence/identity-instrument@3.0.0-beta.3 - - @0xsequence/relayer@3.0.0-beta.3 - - @0xsequence/wallet-core@3.0.0-beta.3 - - @0xsequence/wallet-primitives@3.0.0-beta.3 - -## 3.0.0-beta.2 - -### Patch Changes - -- 3.0.0-beta.2 with identity instrument updates -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.2 - - @0xsequence/identity-instrument@3.0.0-beta.2 - - @0xsequence/relayer@3.0.0-beta.2 - - @0xsequence/wallet-core@3.0.0-beta.2 - - @0xsequence/wallet-primitives@3.0.0-beta.2 - -## 3.0.0-beta.1 - -### Patch Changes - -- 3.0.0-beta.1 -- Updated dependencies - - @0xsequence/guard@3.0.0-beta.1 - - @0xsequence/identity-instrument@3.0.0-beta.1 - - @0xsequence/relayer@3.0.0-beta.1 - - @0xsequence/wallet-core@3.0.0-beta.1 - - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/wdk/eslint.config.js b/packages/wallet/wdk/eslint.config.js deleted file mode 100644 index d10bbd1e97..0000000000 --- a/packages/wallet/wdk/eslint.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { config as baseConfig } from '@repo/eslint-config/base' - -/** @type {import("eslint").Linter.Config} */ -export default [ - ...baseConfig, - { - // files: ['**/*.{test,spec}.ts'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - }, -] diff --git a/packages/wallet/wdk/package.json b/packages/wallet/wdk/package.json deleted file mode 100644 index 2d62c27953..0000000000 --- a/packages/wallet/wdk/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@0xsequence/wallet-wdk", - "version": "3.0.5", - "license": "Apache-2.0", - "type": "module", - "publishConfig": { - "access": "public" - }, - "private": false, - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "vitest run && npm run test:ssr", - "test:coverage": "vitest run --coverage", - "test:ssr": "node test/test-ssr-safety.js", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist", - "lint": "eslint . --max-warnings 0" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@types/node": "^25.3.0", - "@vitest/coverage-v8": "^4.0.18", - "dotenv": "^17.3.1", - "fake-indexeddb": "^6.2.5", - "happy-dom": "^20.8.9", - "typescript": "^5.9.3", - "vitest": "^4.0.18" - }, - "dependencies": { - "@0xsequence/guard": "workspace:^", - "@0xsequence/identity-instrument": "workspace:^", - "@0xsequence/relayer": "workspace:^", - "@0xsequence/tee-verifier": "^0.1.2", - "@0xsequence/wallet-core": "workspace:^", - "@0xsequence/wallet-primitives": "workspace:^", - "idb": "^8.0.3", - "jwt-decode": "^4.0.0", - "ox": "^0.9.17", - "uuid": "^13.0.0" - } -} diff --git a/packages/wallet/wdk/src/dbs/auth-commitments.ts b/packages/wallet/wdk/src/dbs/auth-commitments.ts deleted file mode 100644 index 613d4bd574..0000000000 --- a/packages/wallet/wdk/src/dbs/auth-commitments.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'auth-commitments' - -export type CommitAuthArgs = - | { type: 'auth'; state?: string } - | { type: 'reauth'; state: string; signer: string } - | { type: 'add-signer'; wallet: string; state?: string } - -export type AuthCommitment = { - id: string - kind: 'google-pkce' | 'apple' | `custom-${string}` - metadata: { [key: string]: string } - verifier?: string - challenge?: string - target: string -} & ({ type: 'auth' } | { type: 'reauth'; signer: string } | { type: 'add-signer'; wallet: string }) - -export class AuthCommitments extends Generic { - constructor(dbName: string = 'sequence-auth-commitments') { - super(dbName, TABLE_NAME, 'id', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/dbs/auth-keys.ts b/packages/wallet/wdk/src/dbs/auth-keys.ts deleted file mode 100644 index 690d18cd99..0000000000 --- a/packages/wallet/wdk/src/dbs/auth-keys.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' -import type { WdkEnv } from '../env.js' - -const TABLE_NAME = 'auth-keys' - -export type AuthKey = { - address: string - privateKey: CryptoKey - identitySigner: string - expiresAt: Date -} - -export class AuthKeys extends Generic { - private expirationTimers = new Map>() - - constructor( - dbName: string = 'sequence-auth-keys', - private readonly env?: WdkEnv, - ) { - super(dbName, TABLE_NAME, 'address', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - const store = db.createObjectStore(TABLE_NAME) - - store.createIndex('identitySigner', 'identitySigner', { unique: true }) - } - }, - ]) - } - - async handleOpenDB(): Promise { - const authKeys = await this.list() - for (const authKey of authKeys) { - await this.scheduleExpiration(authKey) - } - } - - async set(item: AuthKey): Promise { - const result = await super.set({ - ...item, - address: item.address.toLowerCase(), - identitySigner: item.identitySigner.toLowerCase(), - }) - this.scheduleExpiration(item) - return result - } - - async del(address: AuthKey['address']): Promise { - const result = await super.del(address.toLowerCase()) - this.clearExpiration(address) - return result - } - - async getBySigner(signer: string, attempt: number = 1): Promise { - const normalizedSigner = signer.toLowerCase() - const store = await this.getStore('readonly') - const index = store.index('identitySigner') - - // Below code has a workaround where get does not work as expected - // and we fall back to getAll to find the key by identitySigner. - try { - const result = await index.get(normalizedSigner) - if (result !== undefined) { - return result - } else if (attempt < 2) { - const setTimeoutFn = this.env?.timers?.setTimeout ?? (globalThis as any).setTimeout - if (setTimeoutFn) { - await new Promise((resolve) => setTimeoutFn(resolve, 50)) - } - return this.getBySigner(signer, attempt + 1) - } else { - try { - const allKeys = await store.getAll() - if (allKeys && allKeys.length > 0) { - const foundKey = allKeys.find((key) => key.identitySigner.toLowerCase() === normalizedSigner) - return foundKey - } - return undefined - } catch (getAllError) { - console.error( - `[AuthKeys.getBySigner] Fallback: Error during getAll() for signer ${normalizedSigner}:`, - getAllError, - ) - throw getAllError - } - } - } catch (error) { - console.error( - `[AuthKeys.getBySigner attempt #${attempt}] Index query error for signer ${normalizedSigner}:`, - error, - ) - - throw error - } - } - - async delBySigner(signer: string): Promise { - const authKey = await this.getBySigner(signer.toLowerCase()) - if (authKey) { - await this.del(authKey.address.toLowerCase()) - } - } - - private async scheduleExpiration(authKey: AuthKey): Promise { - this.clearExpiration(authKey.address.toLowerCase()) - - const now = Date.now() - const delay = authKey.expiresAt.getTime() - now - if (delay <= 0) { - await this.del(authKey.address.toLowerCase()) - return - } - const setTimeoutFn = this.env?.timers?.setTimeout ?? (globalThis as any).setTimeout - if (!setTimeoutFn) { - return - } - const timer = setTimeoutFn(() => { - console.log('removing expired auth key', authKey) - this.del(authKey.address.toLowerCase()) - }, delay) - this.expirationTimers.set(authKey.address.toLowerCase(), timer) - } - - private clearExpiration(address: string): void { - const timer = this.expirationTimers.get(address.toLowerCase()) - if (timer) { - const clearTimeoutFn = this.env?.timers?.clearTimeout ?? (globalThis as any).clearTimeout - if (clearTimeoutFn) { - clearTimeoutFn(timer) - } - this.expirationTimers.delete(address.toLowerCase()) - } - } -} diff --git a/packages/wallet/wdk/src/dbs/generic.ts b/packages/wallet/wdk/src/dbs/generic.ts deleted file mode 100644 index 329769b4c2..0000000000 --- a/packages/wallet/wdk/src/dbs/generic.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { openDB, IDBPDatabase, IDBPTransaction } from 'idb' - -export type DbUpdateType = 'added' | 'updated' | 'removed' - -export type DbUpdateListener = ( - keyValue: T[K], - updateType: DbUpdateType, - oldItem?: T, - newItem?: T, -) => void - -export type Migration = ( - db: IDBPDatabase, - transaction: IDBPTransaction, - event: IDBVersionChangeEvent, -) => void - -function deepEqual(a: any, b: any): boolean { - if (a === b) { - return true - } - - if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') { - return false - } - - const keysA = Object.keys(a) - const keysB = Object.keys(b) - if (keysA.length !== keysB.length) return false - - for (const key of keysA) { - if (!keysB.includes(key)) return false - if (!deepEqual(a[key], b[key])) return false - } - - return true -} - -export class Generic { - private _db: IDBPDatabase | null = null - private listeners: DbUpdateListener[] = [] - private broadcastChannel?: BroadcastChannel - - /** - * @param dbName The name of the IndexedDB database. - * @param storeName The name of the object store. - * @param key The property key in T to be used as the primary key. - * @param migrations An array of migration functions; the database version is migrations.length + 1. - */ - constructor( - private dbName: string, - private storeName: string, - private key: K, - private migrations: Migration[] = [], - ) { - if (typeof BroadcastChannel !== 'undefined') { - this.broadcastChannel = new BroadcastChannel(this.dbName + '-observer') - this.broadcastChannel.onmessage = (event) => { - if (event.data && event.data.keyValue !== undefined && event.data.updateType) { - this.listeners.forEach((cb) => - cb(event.data.keyValue, event.data.updateType, event.data.oldItem, event.data.newItem), - ) - } - } - } - } - - private async openDB(): Promise> { - if (this._db) return this._db - - const targetDbVersion = this.migrations.length + 1 - - this._db = await openDB(this.dbName, targetDbVersion, { - upgrade: (db, oldVersion, newVersion, tx, event) => { - if (newVersion !== null) { - for (let targetSchemaToBuild = oldVersion + 1; targetSchemaToBuild <= newVersion; targetSchemaToBuild++) { - const migrationIndex = targetSchemaToBuild - 2 - - if (migrationIndex >= 0 && migrationIndex < this.migrations.length) { - const migrationFunc = this.migrations[migrationIndex] - if (migrationFunc) { - migrationFunc(db, tx, event) - } else { - throw new Error( - `Migration for schema version ${targetSchemaToBuild} (using migrations[${migrationIndex}]) not found but expected.`, - ) - } - } - } - } - }, - blocked: () => { - console.error(`IndexedDB ${this.dbName} upgrade blocked.`) - }, - blocking: () => { - console.warn(`IndexedDB ${this.dbName} upgrade is being blocked by other connections. Closing this connection.`) - if (this._db) { - this._db.close() - this._db = null - } - }, - terminated: () => { - console.warn(`IndexedDB ${this.dbName} connection terminated.`) - this._db = null - }, - }) - - await this.handleOpenDB() - return this._db - } - - protected async handleOpenDB(): Promise {} - - protected async getStore(mode: IDBTransactionMode) { - const db = await this.openDB() - const tx = db.transaction(this.storeName, mode) - return tx.objectStore(this.storeName) - } - - async get(keyValue: T[K]): Promise { - const store = await this.getStore('readonly') - return store.get(keyValue) - } - - async list(): Promise { - const store = await this.getStore('readonly') - return store.getAll() - } - - async set(item: T): Promise { - const db = await this.openDB() - const keyValue = item[this.key] - - const tx = db.transaction(this.storeName, 'readwrite') - const store = tx.objectStore(this.storeName) - - const oldItem = (await store.get(keyValue)) as T | undefined - await store.put(item, keyValue) - await tx.done - - let updateType: DbUpdateType | null = null - if (!oldItem) { - updateType = 'added' - } else if (!deepEqual(oldItem, item)) { - updateType = 'updated' - } - - if (updateType) { - this.notifyUpdate(keyValue, updateType, oldItem, item) - } - return keyValue - } - - async del(keyValue: T[K]): Promise { - const oldItem = await this.get(keyValue) - - const db = await this.openDB() - const tx = db.transaction(this.storeName, 'readwrite') - const store = tx.objectStore(this.storeName) - - await store.delete(keyValue) - await tx.done - - if (oldItem) { - this.notifyUpdate(keyValue, 'removed', oldItem, undefined) - } - } - - private notifyUpdate(keyValue: T[K], updateType: DbUpdateType, oldItem?: T, newItem?: T): void { - this.listeners.forEach((listener) => listener(keyValue, updateType, oldItem, newItem)) - if (this.broadcastChannel) { - this.broadcastChannel.postMessage({ keyValue, updateType, oldItem, newItem }) - } - } - - addListener(listener: DbUpdateListener): () => void { - this.listeners.push(listener) - - return () => this.removeListener(listener) - } - - removeListener(listener: DbUpdateListener): void { - this.listeners = this.listeners.filter((l) => l !== listener) - } - - public async close(): Promise { - if (this._db) { - this._db.close() - this._db = null - } - if (this.broadcastChannel) { - this.broadcastChannel.close() - this.broadcastChannel = undefined - } - } -} diff --git a/packages/wallet/wdk/src/dbs/index.ts b/packages/wallet/wdk/src/dbs/index.ts deleted file mode 100644 index 1cf7f30f03..0000000000 --- a/packages/wallet/wdk/src/dbs/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type { AuthCommitment, CommitAuthArgs } from './auth-commitments.js' -export { AuthCommitments } from './auth-commitments.js' - -export type { AuthKey } from './auth-keys.js' -export { AuthKeys } from './auth-keys.js' - -export type { DbUpdateType, DbUpdateListener, Migration } from './generic.js' -export { Generic } from './generic.js' -export { Messages } from './messages.js' -export { Signatures } from './signatures.js' -export { Transactions } from './transactions.js' -export { Wallets } from './wallets.js' -export { Recovery } from './recovery.js' - -export type { PasskeyCredential } from './passkey-credentials.js' -export { PasskeyCredentials } from './passkey-credentials.js' diff --git a/packages/wallet/wdk/src/dbs/messages.ts b/packages/wallet/wdk/src/dbs/messages.ts deleted file mode 100644 index 54459f06fd..0000000000 --- a/packages/wallet/wdk/src/dbs/messages.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Message } from '../sequence/types/message-request.js' -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'messages' - -export class Messages extends Generic { - constructor(dbName: string = 'sequence-messages') { - super(dbName, TABLE_NAME, 'id', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/dbs/passkey-credentials.ts b/packages/wallet/wdk/src/dbs/passkey-credentials.ts deleted file mode 100644 index 5e9e6bc318..0000000000 --- a/packages/wallet/wdk/src/dbs/passkey-credentials.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' -import { Address } from 'ox' -import { Extensions } from '@0xsequence/wallet-primitives' - -const TABLE_NAME = 'passkey-credentials' - -export type PasskeyCredential = { - credentialId: string - publicKey: Extensions.Passkeys.PublicKey - walletAddress: Address.Address - createdAt: string - lastLoginAt?: string -} - -export class PasskeyCredentials extends Generic { - constructor(dbName: string = 'sequence-passkey-credentials') { - super(dbName, TABLE_NAME, 'credentialId', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } - - /** - * Get a passkey credential by credential ID - */ - async getByCredentialId(credentialId: string): Promise { - return this.get(credentialId) - } - - /** - * Store a new passkey credential - */ - async saveCredential( - credentialId: string, - publicKey: Extensions.Passkeys.PublicKey, - walletAddress: Address.Address, - ): Promise { - const now = new Date().toISOString() - const credential: PasskeyCredential = { - credentialId, - publicKey, - walletAddress, - createdAt: now, - lastLoginAt: now, // Set initially on creation - } - - await this.set(credential) - } - - async updateCredential( - credentialId: string, - { lastLoginAt, walletAddress }: { lastLoginAt: string; walletAddress: Address.Address }, - ): Promise { - const existingCredential = await this.getByCredentialId(credentialId) - if (existingCredential) { - const updatedCredential: PasskeyCredential = { ...existingCredential, lastLoginAt, walletAddress } - await this.set(updatedCredential) - } - } -} diff --git a/packages/wallet/wdk/src/dbs/recovery.ts b/packages/wallet/wdk/src/dbs/recovery.ts deleted file mode 100644 index e035825f3a..0000000000 --- a/packages/wallet/wdk/src/dbs/recovery.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Generic } from './generic.js' -import { QueuedRecoveryPayload } from '../sequence/types/recovery.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'queued-recovery-payloads' -export class Recovery extends Generic { - constructor(dbName: string = 'sequence-recovery') { - super(dbName, TABLE_NAME, 'id', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/dbs/signatures.ts b/packages/wallet/wdk/src/dbs/signatures.ts deleted file mode 100644 index 3f7328187d..0000000000 --- a/packages/wallet/wdk/src/dbs/signatures.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BaseSignatureRequest } from '../sequence/index.js' -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'envelopes' -export class Signatures extends Generic { - constructor(dbName: string = 'sequence-signature-requests') { - super(dbName, TABLE_NAME, 'id', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/dbs/transactions.ts b/packages/wallet/wdk/src/dbs/transactions.ts deleted file mode 100644 index 48f7680c87..0000000000 --- a/packages/wallet/wdk/src/dbs/transactions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Transaction } from '../sequence/types/transaction-request.js' -import { Generic } from './generic.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'transactions' - -export class Transactions extends Generic { - constructor(dbName: string = 'sequence-transactions') { - super(dbName, TABLE_NAME, 'id', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/dbs/wallets.ts b/packages/wallet/wdk/src/dbs/wallets.ts deleted file mode 100644 index 4b17776103..0000000000 --- a/packages/wallet/wdk/src/dbs/wallets.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Generic } from './generic.js' -import { Wallet } from '../sequence/types/wallet.js' -import { IDBPDatabase, IDBPTransaction } from 'idb' - -const TABLE_NAME = 'wallets' - -export class Wallets extends Generic { - constructor(dbName: string = 'sequence-manager') { - super(dbName, TABLE_NAME, 'address', [ - ( - db: IDBPDatabase, - _tx: IDBPTransaction, - _event: IDBVersionChangeEvent, - ) => { - if (!db.objectStoreNames.contains(TABLE_NAME)) { - db.createObjectStore(TABLE_NAME) - } - }, - ]) - } -} diff --git a/packages/wallet/wdk/src/env.ts b/packages/wallet/wdk/src/env.ts deleted file mode 100644 index 4e3cecb1a5..0000000000 --- a/packages/wallet/wdk/src/env.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { CoreEnv } from '@0xsequence/wallet-core' -import { resolveCoreEnv } from '@0xsequence/wallet-core' - -export type TimersLike = { - setTimeout: typeof setTimeout - clearTimeout: typeof clearTimeout - setInterval: typeof setInterval - clearInterval: typeof clearInterval -} - -export type LockManagerLike = { - request: (name: string, callback: (lock: Lock | null) => Promise | void) => Promise -} - -export type NavigationLike = { - getPathname: () => string - redirect: (url: string) => void -} - -export type WdkEnv = CoreEnv & { - timers?: TimersLike - locks?: LockManagerLike - navigation?: NavigationLike - urlSearchParams?: typeof URLSearchParams -} - -export function resolveWdkEnv(env?: WdkEnv): WdkEnv { - const core = resolveCoreEnv(env) - const globalObj = globalThis as any - const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {}) - const location = windowObj.location ?? globalObj.location - - return { - ...core, - timers: - env?.timers ?? - (typeof globalObj.setTimeout === 'function' - ? { - setTimeout: globalObj.setTimeout.bind(globalObj), - clearTimeout: globalObj.clearTimeout.bind(globalObj), - setInterval: globalObj.setInterval.bind(globalObj), - clearInterval: globalObj.clearInterval.bind(globalObj), - } - : undefined), - locks: env?.locks ?? globalObj.navigator?.locks ?? windowObj.navigator?.locks, - navigation: - env?.navigation ?? - (location - ? { - getPathname: () => location.pathname, - redirect: (url: string) => { - location.href = url - }, - } - : undefined), - urlSearchParams: env?.urlSearchParams ?? globalObj.URLSearchParams ?? windowObj.URLSearchParams, - } -} diff --git a/packages/wallet/wdk/src/identity/signer.ts b/packages/wallet/wdk/src/identity/signer.ts deleted file mode 100644 index 0cb74bbbd6..0000000000 --- a/packages/wallet/wdk/src/identity/signer.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Address, Signature, Hex, Bytes } from 'ox' -import { Signers, State, type CryptoLike } from '@0xsequence/wallet-core' -import { IdentityInstrument } from '@0xsequence/identity-instrument' -import { AuthKey } from '../dbs/auth-keys.js' -import { Payload, Signature as SequenceSignature } from '@0xsequence/wallet-primitives' -import * as Identity from '@0xsequence/identity-instrument' - -export function toIdentityAuthKey(authKey: AuthKey, crypto?: CryptoLike): Identity.AuthKey { - const globalObj = globalThis as any - const resolvedCrypto = crypto ?? globalObj.window?.crypto ?? globalObj.crypto - if (!resolvedCrypto?.subtle) { - throw new Error('crypto.subtle is not available') - } - return { - address: authKey.address, - keyType: Identity.KeyType.WebCrypto_Secp256r1, - signer: authKey.identitySigner, - async sign(digest: Bytes.Bytes) { - const authKeySignature = await resolvedCrypto.subtle.sign( - { - name: 'ECDSA', - hash: 'SHA-256', - }, - authKey.privateKey, - new Uint8Array(digest), - ) - return Hex.fromBytes(new Uint8Array(authKeySignature)) - }, - } -} - -export class IdentitySigner implements Signers.Signer { - constructor( - readonly identityInstrument: IdentityInstrument, - readonly authKey: AuthKey, - private readonly crypto?: CryptoLike, - ) {} - - get address(): Address.Address { - if (!Address.validate(this.authKey.identitySigner)) { - throw new Error('No signer address found') - } - return Address.checksum(this.authKey.identitySigner) - } - - async sign( - wallet: Address.Address, - chainId: number, - payload: Payload.Parented, - ): Promise { - const payloadHash = Payload.hash(wallet, chainId, payload) - return this.signDigest(payloadHash) - } - - async signDigest(digest: Bytes.Bytes): Promise { - const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey, this.crypto), digest) - const sig = Signature.fromHex(sigHex) - return { - type: 'hash', - ...sig, - } - } - - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { - const payload = Payload.fromMessage( - Hex.fromString( - JSON.stringify({ - action: 'consent-to-be-part-of-wallet', - wallet, - signer: this.address, - timestamp: Date.now(), - ...extra, - }), - ), - ) - - const signature = await this.sign(wallet, 0, payload) - await stateWriter.saveWitnesses(wallet, 0, payload, { - type: 'unrecovered-signer', - weight: 1n, - signature, - }) - } -} diff --git a/packages/wallet/wdk/src/index.ts b/packages/wallet/wdk/src/index.ts deleted file mode 100644 index 003abdd722..0000000000 --- a/packages/wallet/wdk/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * as Identity from './identity/signer.js' -export * as Sequence from './sequence/index.js' -export * from './env.js' diff --git a/packages/wallet/wdk/src/sequence/cron.ts b/packages/wallet/wdk/src/sequence/cron.ts deleted file mode 100644 index f95117109a..0000000000 --- a/packages/wallet/wdk/src/sequence/cron.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { Shared } from './manager.js' - -interface CronJob { - id: string - interval: number - lastRun: number - handler: () => Promise -} - -/** - * Cron manages scheduled jobs, persisting their last run times and ensuring - * jobs are executed at their specified intervals. - */ -export class Cron { - private jobs: Map = new Map() - private checkInterval?: ReturnType - private readonly STORAGE_KEY = 'sequence-cron-jobs' - private isStopping: boolean = false - private currentCheckJobsPromise: Promise = Promise.resolve() - private readonly env: Shared['env'] - - /** - * Initializes the Cron scheduler and starts the periodic job checker. - * @param shared Shared context for modules and logging. - */ - constructor(private readonly shared: Shared) { - this.env = shared.env - this.start() - } - - /** - * Starts the periodic job checking loop. - * Does nothing if the Cron is stopping. - */ - private start() { - if (this.isStopping) return - this.executeCheckJobsChain() - const setIntervalFn = this.env.timers?.setInterval ?? (globalThis as any).setInterval - if (!setIntervalFn) { - return - } - this.checkInterval = setIntervalFn(() => this.executeCheckJobsChain(), 60 * 1000) - } - - /** - * Chains job checks to ensure sequential execution. - * Handles errors from previous executions to avoid breaking the chain. - */ - private executeCheckJobsChain(): void { - this.currentCheckJobsPromise = this.currentCheckJobsPromise - .catch(() => {}) // Ignore errors from previous chain link for sequencing - .then(() => { - if (!this.isStopping) { - return this.checkJobs() - } - return Promise.resolve() - }) - } - - /** - * Stops the Cron scheduler, clears the interval, and waits for any running job checks to finish. - */ - public async stop(): Promise { - this.isStopping = true - - if (this.checkInterval) { - const clearIntervalFn = this.env.timers?.clearInterval ?? (globalThis as any).clearInterval - if (clearIntervalFn) { - clearIntervalFn(this.checkInterval) - } - this.checkInterval = undefined - this.shared.modules.logger.log('Cron: Interval cleared.') - } - - // Wait for the promise of the last (or current) checkJobs execution - await this.currentCheckJobsPromise.catch((err) => { - console.error('Cron: Error during currentCheckJobsPromise settlement in stop():', err) - }) - } - - /** - * Registers a new cron job. - * @param id Unique job identifier. - * @param interval Execution interval in milliseconds. - * @param handler Async function to execute. - * @throws If a job with the same ID already exists. - */ - registerJob(id: string, interval: number, handler: () => Promise) { - if (this.jobs.has(id)) { - throw new Error(`Job with ID ${id} already exists`) - } - const job: CronJob = { id, interval, lastRun: 0, handler } - this.jobs.set(id, job) - // No syncWithStorage needed here, it happens in checkJobs - } - - /** - * Unregisters a cron job by its ID. - * @param id Job identifier to remove. - */ - unregisterJob(id: string) { - this.jobs.delete(id) - } - - /** - * Checks all registered jobs and executes those whose interval has elapsed. - * Updates last run times and persists state. - * Uses a lock to prevent concurrent execution. - */ - private async checkJobs(): Promise { - if (this.isStopping) { - return - } - - try { - const locks = this.env.locks ?? (globalThis as any).navigator?.locks - if (locks?.request) { - await locks.request('sequence-cron-jobs', async (lock: Lock | null) => { - if (this.isStopping) { - return - } - if (!lock) { - return - } - await this.runJobs() - }) - } else { - await this.runJobs() - } - } catch (error) { - if (this.isAbortError(error)) { - this.shared.modules.logger.log('Cron: navigator.locks.request was aborted.') - } else { - console.error('Cron: Error in navigator.locks.request:', error) - } - } - } - - private async runJobs(): Promise { - const now = Date.now() - const storage = await this.getStorageState() - - for (const [id, job] of this.jobs) { - if (this.isStopping) { - break - } - - const lastRun = storage.get(id)?.lastRun ?? job.lastRun - const timeSinceLastRun = now - lastRun - - if (timeSinceLastRun >= job.interval) { - try { - await job.handler() - if (!this.isStopping) { - job.lastRun = now - storage.set(id, { lastRun: now }) - } - } catch (error) { - if (this.isAbortError(error)) { - this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`) - } else { - console.error(`Cron job ${id} failed:`, error) - } - } - } - } - - if (!this.isStopping) { - await this.syncWithStorage() - } - } - - /** - * Loads the persisted last run times for jobs from localStorage. - * @returns Map of job IDs to their last run times. - */ - private async getStorageState(): Promise> { - if (this.isStopping) return new Map() - const storage = this.env.storage - if (!storage) { - return new Map() - } - const state = storage.getItem(this.STORAGE_KEY) - return new Map(state ? JSON.parse(state) : []) - } - - /** - * Persists the current last run times of all jobs to localStorage. - */ - private async syncWithStorage() { - if (this.isStopping) return - const storage = this.env.storage - if (!storage) { - return - } - const state = Array.from(this.jobs.entries()).map(([id, job]) => [id, { lastRun: job.lastRun }]) - storage.setItem(this.STORAGE_KEY, JSON.stringify(state)) - } - - private isAbortError(error: unknown): boolean { - const domException = (globalThis as any).DOMException - if (domException && error instanceof domException) { - return (error as DOMException).name === 'AbortError' - } - return (error as any)?.name === 'AbortError' - } -} diff --git a/packages/wallet/wdk/src/sequence/devices.ts b/packages/wallet/wdk/src/sequence/devices.ts deleted file mode 100644 index 94d3f3ff29..0000000000 --- a/packages/wallet/wdk/src/sequence/devices.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Signers } from '@0xsequence/wallet-core' -import { Address } from 'ox' -import { Kinds, WitnessExtraSignerKind } from './types/signer.js' -import { Shared } from './manager.js' - -export class Devices { - constructor(private readonly shared: Shared) {} - - async list() { - return this.shared.databases.encryptedPks.listAddresses() - } - - async has(address: Address.Address) { - const entry = await this.shared.databases.encryptedPks.getEncryptedEntry(address) - return entry !== undefined - } - - async create() { - const e = await this.shared.databases.encryptedPks.generateAndStore() - const s = await this.shared.databases.encryptedPks.getEncryptedPkStore(e.address) - - if (!s) { - throw new Error('Failed to create session') - } - - this.shared.modules.logger.log('Created new session:', s.address) - return new Signers.Pk.Pk(s) - } - - async get(address: Address.Address) { - const s = await this.shared.databases.encryptedPks.getEncryptedPkStore(address) - if (!s) { - return undefined - } - - return new Signers.Pk.Pk(s) - } - - async witness(address: Address.Address, wallet: Address.Address) { - const signer = await this.get(address) - if (!signer) { - throw new Error('Signer not found') - } - - await signer.witness(this.shared.sequence.stateProvider, wallet, { - signerKind: Kinds.LocalDevice, - } as WitnessExtraSignerKind) - } - - async remove(address: Address.Address) { - await this.shared.databases.encryptedPks.remove(address) - } -} diff --git a/packages/wallet/wdk/src/sequence/errors.ts b/packages/wallet/wdk/src/sequence/errors.ts deleted file mode 100644 index e0b833f513..0000000000 --- a/packages/wallet/wdk/src/sequence/errors.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class AnswerIncorrectError extends Error { - constructor(message: string = 'The provided answer is incorrect.') { - super(message) - this.name = 'AnswerIncorrectError' - } -} - -export class ChallengeExpiredError extends Error { - constructor(message: string = 'The challenge has expired.') { - super(message) - this.name = 'ChallengeExpiredError' - } -} - -export class TooManyAttemptsError extends Error { - constructor(message: string = 'Too many incorrect attempts.') { - super(message) - this.name = 'TooManyAttemptsError' - } -} diff --git a/packages/wallet/wdk/src/sequence/guards.ts b/packages/wallet/wdk/src/sequence/guards.ts deleted file mode 100644 index a005a16190..0000000000 --- a/packages/wallet/wdk/src/sequence/guards.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Address, Bytes } from 'ox' -import { Shared } from './manager.js' -import * as Guard from '@0xsequence/guard' -import { Signers } from '@0xsequence/wallet-core' -import { Config, Constants } from '@0xsequence/wallet-primitives' - -export type GuardRole = 'wallet' | 'sessions' - -export class Guards { - constructor(private readonly shared: Shared) {} - - getByRole(role: GuardRole): Signers.Guard { - const guardAddress = this.shared.sequence.guardAddresses[role] - if (!guardAddress) { - throw new Error(`Guard address for role ${role} not found`) - } - - return new Signers.Guard(new Guard.Sequence.Guard(this.shared.sequence.guardUrl, guardAddress)) - } - - getByAddress(address: Address.Address): [GuardRole, Signers.Guard] | undefined { - const roles = Object.entries(this.shared.sequence.guardAddresses) as [GuardRole, Address.Address][] - for (const [role, guardAddress] of roles) { - if (Address.isEqual(guardAddress, address)) { - return [role, this.getByRole(role)] - } - } - return undefined - } - - topology(role: GuardRole): Config.Topology | undefined { - const guardAddress = this.shared.sequence.guardAddresses[role] - if (!guardAddress) { - return undefined - } - - const topology = Config.replaceAddress( - this.shared.sequence.defaultGuardTopology, - Constants.PlaceholderAddress, - guardAddress, - ) - - // If the imageHash did not change it means the replacement failed - if ( - Bytes.isEqual( - Config.hashConfiguration(topology), - Config.hashConfiguration(this.shared.sequence.defaultGuardTopology), - ) - ) { - throw new Error(`Guard address replacement failed for role ${role}`) - } - - return topology - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts deleted file mode 100644 index 308e2809fa..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Hex, Bytes } from 'ox' -import { Handler } from './handler.js' -import * as Db from '../../dbs/index.js' -import { Signatures } from '../signatures.js' -import * as Identity from '@0xsequence/identity-instrument' -import { IdentitySigner } from '../../identity/signer.js' -import { AuthCodeHandler } from './authcode.js' -import type { WdkEnv } from '../../env.js' -import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' - -export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { - constructor( - signupKind: 'google-pkce' | `custom-${string}`, - issuer: string, - oauthUrl: string, - audience: string, - nitro: Identity.IdentityInstrument, - signatures: Signatures, - commitments: Db.AuthCommitments, - authKeys: Db.AuthKeys, - env?: WdkEnv, - ) { - super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys, env) - } - - public async commitAuth(target: string, args: CommitAuthArgs) { - let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) - if (args.type === 'reauth') { - challenge = challenge.withSigner({ address: args.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) - } - const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) - const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - - const base = { - id: state, - kind: this.signupKind as Db.AuthCommitment['kind'], - verifier, - challenge: codeChallenge, - target, - metadata: {}, - } - - if (args.type === 'reauth') { - await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) - } else if (args.type === 'add-signer') { - await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) - } else { - await this.commitments.set({ ...base, type: 'auth' }) - } - - const searchParams = this.serializeQuery({ - code_challenge: codeChallenge, - code_challenge_method: 'S256', - client_id: this.audience, - redirect_uri: this.redirectUri, - login_hint: loginHint, - response_type: 'code', - scope: 'openid profile email', - state, - }) - - return `${this.oauthUrl}?${searchParams}` - } - - public async completeAuth( - commitment: Db.AuthCommitment, - code: string, - ): Promise<[IdentitySigner, { [key: string]: string }]> { - const challenge = new Identity.AuthCodePkceChallenge('', '', '') - if (!commitment.verifier) { - throw new Error('Missing verifier in commitment') - } - const { signer, email } = await this.nitroCompleteAuth(challenge.withAnswer(commitment.verifier, code)) - - await this.commitments.del(commitment.id) - - return [signer, { ...commitment.metadata, email }] - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode.ts b/packages/wallet/wdk/src/sequence/handlers/authcode.ts deleted file mode 100644 index 4b5d5922b5..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/authcode.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Hex, Address, Bytes } from 'ox' -import { Handler } from './handler.js' -import * as Db from '../../dbs/index.js' -import { Signatures } from '../signatures.js' -import * as Identity from '@0xsequence/identity-instrument' -import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js' -import { IdentitySigner } from '../../identity/signer.js' -import { IdentityHandler } from './identity.js' -import { Kinds } from '../types/signer.js' -import type { NavigationLike, WdkEnv } from '../../env.js' -import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' - -export class AuthCodeHandler extends IdentityHandler implements Handler { - protected redirectUri: string = '' - - constructor( - public readonly signupKind: 'apple' | 'google-pkce' | `custom-${string}`, - public readonly issuer: string, - protected readonly oauthUrl: string, - public readonly audience: string, - nitro: Identity.IdentityInstrument, - signatures: Signatures, - protected readonly commitments: Db.AuthCommitments, - authKeys: Db.AuthKeys, - env?: WdkEnv, - ) { - super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env) - } - - public get kind() { - if (this.signupKind === 'google-pkce') { - // Keep Google PKCE on the canonical kind so Google signers created before - // canonicalization still resolve as `login-google`. - return Kinds.LoginGoogle - } - return 'login-' + this.signupKind - } - - public setRedirectUri(redirectUri: string) { - this.redirectUri = redirectUri - } - - public async commitAuth(target: string, args: CommitAuthArgs) { - const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - - const base = { - id: state, - kind: this.signupKind as Db.AuthCommitment['kind'], - target, - metadata: {}, - } - - if (args.type === 'reauth') { - await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) - } else if (args.type === 'add-signer') { - await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) - } else { - await this.commitments.set({ ...base, type: 'auth' }) - } - - const searchParams = this.serializeQuery({ - client_id: this.audience, - redirect_uri: this.redirectUri, - response_type: 'code', - state, - ...(this.signupKind === 'apple' ? {} : { scope: 'openid profile email' }), - }) - - return `${this.oauthUrl}?${searchParams}` - } - - public async completeAuth( - commitment: Db.AuthCommitment, - code: string, - ): Promise<[IdentitySigner, { [key: string]: string }]> { - let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code) - if (commitment.type === 'reauth') { - challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) - } - await this.nitroCommitVerifier(challenge) - const { signer, email } = await this.nitroCompleteAuth(challenge) - - return [signer, { email }] - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const signer = await this.getAuthKeySigner(address) - if (signer) { - return { - address, - handler: this, - status: 'ready', - handle: async () => { - await this.sign(signer, request) - return true - }, - } - } - - return { - address, - handler: this, - status: 'actionable', - message: 'request-redirect', - handle: async () => { - const navigation = this.getNavigation() - const url = await this.commitAuth(navigation.getPathname(), { - type: 'reauth', - state: request.id, - signer: address, - }) - navigation.redirect(url) - return true - }, - } - } - - protected serializeQuery(params: Record): string { - const searchParamsCtor = this.env.urlSearchParams ?? (globalThis as any).URLSearchParams - if (searchParamsCtor) { - return new searchParamsCtor(params).toString() - } - return Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&') - } - - private getNavigation(): NavigationLike { - const navigation = this.env.navigation - if (!navigation) { - throw new Error('navigation is not available') - } - return navigation - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/devices.ts b/packages/wallet/wdk/src/sequence/handlers/devices.ts deleted file mode 100644 index 4d525d37e1..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/devices.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Kinds } from '../types/signer.js' -import { Signatures } from '../signatures.js' -import { Address, Hex } from 'ox' -import { Devices } from '../devices.js' -import { Handler } from './handler.js' -import { SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/index.js' - -export class DevicesHandler implements Handler { - kind = Kinds.LocalDevice - - constructor( - private readonly signatures: Signatures, - private readonly devices: Devices, - ) {} - - onStatusChange(_cb: () => void): () => void { - return () => {} - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const signer = await this.devices.get(address) - if (!signer) { - const status: SignerUnavailable = { - address, - handler: this, - reason: 'not-local-key', - status: 'unavailable', - } - return status - } - - const status: SignerReady = { - address, - handler: this, - status: 'ready', - handle: async () => { - const signature = await signer.sign(request.envelope.wallet, request.envelope.chainId, request.envelope.payload) - - await this.signatures.addSignature(request.id, { - address, - signature, - }) - - return true - }, - } - return status - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/guard.ts b/packages/wallet/wdk/src/sequence/handlers/guard.ts deleted file mode 100644 index b495c95d8b..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/guard.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Address, Hex } from 'ox' -import * as Guard from '@0xsequence/guard' -import { Signers } from '@0xsequence/wallet-core' -import { Handler } from './handler.js' -import { BaseSignatureRequest, SignerUnavailable, SignerReady, SignerActionable, Kinds } from '../types/index.js' -import { Signatures } from '../signatures.js' -import { Guards } from '../guards.js' - -type RespondFn = (token: Signers.GuardToken) => Promise - -export type PromptCodeHandler = ( - request: BaseSignatureRequest, - codeType: 'TOTP' | 'PIN', - respond: RespondFn, -) => Promise - -export class GuardHandler implements Handler { - kind = Kinds.Guard - - private onPromptCode: undefined | PromptCodeHandler - - constructor( - private readonly signatures: Signatures, - private readonly guards: Guards, - ) {} - - public registerUI(onPromptCode: PromptCodeHandler) { - this.onPromptCode = onPromptCode - return () => { - this.onPromptCode = undefined - } - } - - public unregisterUI() { - this.onPromptCode = undefined - } - - onStatusChange(_cb: () => void): () => void { - return () => {} - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const guardInfo = this.guards.getByAddress(address) - if (!guardInfo) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'guard-not-found', - } - } - - const [role, guard] = guardInfo - if (role !== 'wallet') { - return { - address, - handler: this, - status: 'unavailable', - reason: 'not-wallet-guard', - } - } - - const onPromptCode = this.onPromptCode - if (!onPromptCode) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'guard-ui-not-registered', - } - } - - if (request.envelope.signatures.length === 0) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'must-not-sign-first', - } - } - - return { - address, - handler: this, - status: 'ready', - handle: () => { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - try { - const signature = await guard.signEnvelope(request.envelope) - await this.signatures.addSignature(request.id, signature) - resolve(true) - } catch (e) { - if (e instanceof Guard.AuthRequiredError) { - const respond: RespondFn = async (token) => { - const signature = await guard.signEnvelope(request.envelope, token) - await this.signatures.addSignature(request.id, signature) - resolve(true) - } - - await onPromptCode(request, e.id, respond) - } else { - reject(e) - } - } - }) - }, - } - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/handler.ts b/packages/wallet/wdk/src/sequence/handlers/handler.ts deleted file mode 100644 index 8cd4b72f5a..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/handler.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Address, Hex } from 'ox' -import { SignerActionable, SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/index.js' - -export interface Handler { - kind: string - - onStatusChange(cb: () => void): () => void - - status( - address: Address.Address, - imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise -} diff --git a/packages/wallet/wdk/src/sequence/handlers/identity.ts b/packages/wallet/wdk/src/sequence/handlers/identity.ts deleted file mode 100644 index a991e88636..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/identity.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Hex } from 'ox' -import * as Db from '../../dbs/index.js' -import * as Identity from '@0xsequence/identity-instrument' -import { Signatures } from '../signatures.js' -import { BaseSignatureRequest } from '../types/signature-request.js' -import { IdentitySigner, toIdentityAuthKey } from '../../identity/signer.js' -import { resolveWdkEnv, type WdkEnv } from '../../env.js' - -export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex => { - // Bytes4 - switch (identityType) { - case Identity.IdentityType.Email: - return '0x00000001' - case Identity.IdentityType.OIDC: - return '0x00000002' - default: - // Unknown identity type - return '0xffffffff' - } -} - -export class IdentityHandler { - protected readonly env: WdkEnv - - constructor( - private readonly nitro: Identity.IdentityInstrument, - private readonly authKeys: Db.AuthKeys, - private readonly signatures: Signatures, - public readonly identityType: Identity.IdentityType, - env?: WdkEnv, - ) { - this.env = resolveWdkEnv(env) - } - - public onStatusChange(cb: () => void): () => void { - return this.authKeys.addListener(cb) - } - - protected async nitroCommitVerifier(challenge: Identity.Challenge) { - await this.authKeys.delBySigner('') - const authKey = await this.getAuthKey('') - if (!authKey) { - throw new Error('no-auth-key') - } - - const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey, this.env.crypto), challenge) - return res - } - - protected async nitroCompleteAuth(challenge: Identity.Challenge) { - const authKey = await this.getAuthKey('') - if (!authKey) { - throw new Error('no-auth-key') - } - - const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey, this.env.crypto), challenge) - - authKey.identitySigner = res.signer.address - authKey.expiresAt = new Date(Date.now() + 1000 * 60 * 3) // 3 minutes - await this.authKeys.delBySigner('') - await this.authKeys.delBySigner(authKey.identitySigner) - await this.authKeys.set(authKey) - - const signer = new IdentitySigner(this.nitro, authKey, this.env.crypto) - return { signer, email: res.identity.email } - } - - protected async sign(signer: IdentitySigner, request: BaseSignatureRequest) { - const signature = await signer.sign(request.envelope.wallet, request.envelope.chainId, request.envelope.payload) - await this.signatures.addSignature(request.id, { - address: signer.address, - signature, - }) - } - - protected async getAuthKeySigner(address: string): Promise { - const authKey = await this.getAuthKey(address) - if (!authKey) { - return undefined - } - return new IdentitySigner(this.nitro, authKey, this.env.crypto) - } - - protected async clearAuthKeySigner(address: string): Promise { - await this.authKeys.delBySigner(address) - } - - private async getAuthKey(signer: string): Promise { - let authKey = await this.authKeys.getBySigner(signer) - if (!signer && !authKey) { - const crypto = this.env.crypto ?? (globalThis as any).crypto - if (!crypto?.subtle) { - throw new Error('crypto.subtle is not available') - } - const keyPair = await crypto.subtle.generateKey( - { - name: 'ECDSA', - namedCurve: 'P-256', - }, - false, - ['sign', 'verify'], - ) - const publicKey = await crypto.subtle.exportKey('raw', keyPair.publicKey) - authKey = { - address: Hex.fromBytes(new Uint8Array(publicKey)), - identitySigner: '', - expiresAt: new Date(Date.now() + 1000 * 60 * 60), // 1 hour - privateKey: keyPair.privateKey, - } - await this.authKeys.set(authKey) - } - return authKey - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/idtoken.ts b/packages/wallet/wdk/src/sequence/handlers/idtoken.ts deleted file mode 100644 index 58d5b5df9f..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/idtoken.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { Address, Hex } from 'ox' -import { Signers } from '@0xsequence/wallet-core' -import { Handler } from './handler.js' -import * as Identity from '@0xsequence/identity-instrument' -import * as Db from '../../dbs/index.js' -import { Signatures } from '../signatures.js' -import { SignerActionable, SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/signature-request.js' -import { IdentitySigner } from '../../identity/signer.js' -import { IdentityHandler } from './identity.js' -import { Kinds } from '../types/signer.js' -import type { WdkEnv } from '../../env.js' - -type RespondFn = (idToken: string) => Promise - -export type PromptIdTokenHandler = ( - kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, - respond: RespondFn, -) => Promise - -export class IdTokenHandler extends IdentityHandler implements Handler { - private onPromptIdToken: undefined | PromptIdTokenHandler - - constructor( - public readonly signupKind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, - public readonly issuer: string, - public readonly audience: string, - nitro: Identity.IdentityInstrument, - signatures: Signatures, - authKeys: Db.AuthKeys, - env?: WdkEnv, - ) { - super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env) - } - - public get kind() { - if (this.signupKind === 'google-id-token') { - return Kinds.LoginGoogle - } - if (this.signupKind === 'apple-id-token') { - return Kinds.LoginApple - } - return 'login-' + this.signupKind - } - - public registerUI(onPromptIdToken: PromptIdTokenHandler) { - this.onPromptIdToken = onPromptIdToken - return () => { - this.onPromptIdToken = undefined - } - } - - public unregisterUI() { - this.onPromptIdToken = undefined - } - - public async completeAuth(idToken: string): Promise<[IdentitySigner, { [key: string]: string }]> { - const challenge = new Identity.IdTokenChallenge(this.issuer, this.audience, idToken) - await this.nitroCommitVerifier(challenge) - const { signer: identitySigner, email } = await this.nitroCompleteAuth(challenge) - - return [identitySigner, { email }] - } - - public async getSigner(): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { - const onPromptIdToken = this.onPromptIdToken - if (!onPromptIdToken) { - throw new Error('id-token-handler-ui-not-registered') - } - - return await this.handleAuth(onPromptIdToken) - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const signer = await this.getAuthKeySigner(address) - if (signer) { - return { - address, - handler: this, - status: 'ready', - handle: async () => { - await this.sign(signer, request) - return true - }, - } - } - - const onPromptIdToken = this.onPromptIdToken - if (!onPromptIdToken) { - return { - address, - handler: this, - reason: 'ui-not-registered', - status: 'unavailable', - } - } - - return { - address, - handler: this, - status: 'actionable', - message: 'request-id-token', - handle: async () => { - try { - const { signer } = await this.handleAuth(onPromptIdToken) - const signerAddress = (await signer.address) as Address.Address - if (!Address.isEqual(signerAddress, address)) { - // ID-token auth prompts are keyed by provider kind, not the requested signer address. - // For example, a user can pick a different Google account in the account picker and - // return a token for a different identity than this request expects. - await this.clearAuthKeySigner(signerAddress) - throw new Error('id-token-signer-mismatch') - } - return true - } catch { - return false - } - }, - } - } - - private handleAuth( - onPromptIdToken: PromptIdTokenHandler, - ): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - try { - const respond: RespondFn = async (idToken) => { - try { - const [signer, metadata] = await this.completeAuth(idToken) - resolve({ signer, email: metadata.email || '' }) - } catch (error) { - reject(error) - } - } - - await onPromptIdToken(this.signupKind, respond) - } catch (error) { - reject(error) - } - }) - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/index.ts b/packages/wallet/wdk/src/sequence/handlers/index.ts deleted file mode 100644 index 9c1b1f0f57..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type { Handler } from './handler.js' -export { DevicesHandler } from './devices.js' -export { PasskeysHandler } from './passkeys.js' -export { OtpHandler } from './otp.js' -export { AuthCodePkceHandler } from './authcode-pkce.js' -export { IdTokenHandler } from './idtoken.js' -export { MnemonicHandler } from './mnemonic.js' diff --git a/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts b/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts deleted file mode 100644 index 3d932a5331..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Signers } from '@0xsequence/wallet-core' -import { Address, Hex, Mnemonic } from 'ox' -import { Handler } from './handler.js' -import { Signatures } from '../signatures.js' -import { Kinds } from '../types/signer.js' -import { SignerReady, SignerUnavailable, BaseSignatureRequest, SignerActionable } from '../types/index.js' - -type RespondFn = (mnemonic: string) => Promise - -export type PromptMnemonicHandler = (respond: RespondFn) => Promise - -export class MnemonicHandler implements Handler { - kind = Kinds.LoginMnemonic - - private onPromptMnemonic: undefined | PromptMnemonicHandler - private readySigners = new Map() - - constructor(private readonly signatures: Signatures) {} - - public registerUI(onPromptMnemonic: PromptMnemonicHandler) { - this.onPromptMnemonic = onPromptMnemonic - return () => { - this.onPromptMnemonic = undefined - } - } - - public unregisterUI() { - this.onPromptMnemonic = undefined - } - - public addReadySigner(signer: Signers.Pk.Pk) { - this.readySigners.set(signer.address.toLowerCase() as Address.Address, signer) - } - - onStatusChange(_cb: () => void): () => void { - return () => {} - } - - public static toSigner(mnemonic: string): Signers.Pk.Pk | undefined { - try { - const pk = Mnemonic.toPrivateKey(mnemonic) - return new Signers.Pk.Pk(Hex.from(pk)) - } catch { - return undefined - } - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - // Check if we have a cached signer for this address - const signer = this.readySigners.get(address.toLowerCase() as Address.Address) - - if (signer) { - return { - address, - handler: this, - status: 'ready', - handle: async () => { - const signature = await signer.sign( - request.envelope.wallet, - request.envelope.chainId, - request.envelope.payload, - ) - - await this.signatures.addSignature(request.id, { - address, - signature, - }) - - // Remove the ready signer after use - this.readySigners.delete(address.toLowerCase() as Address.Address) - - return true - }, - } - } - - const onPromptMnemonic = this.onPromptMnemonic - if (!onPromptMnemonic) { - return { - address, - handler: this, - reason: 'ui-not-registered', - status: 'unavailable', - } - } - - return { - address, - handler: this, - status: 'actionable', - message: 'enter-mnemonic', - handle: () => { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - const respond: RespondFn = async (mnemonic) => { - const signer = MnemonicHandler.toSigner(mnemonic) - if (!signer) { - return reject('invalid-mnemonic') - } - - if (!Address.isEqual(signer.address, address)) { - return reject('wrong-mnemonic') - } - - const signature = await signer.sign( - request.envelope.wallet, - request.envelope.chainId, - request.envelope.payload, - ) - await this.signatures.addSignature(request.id, { - address, - signature, - }) - resolve(true) - } - await onPromptMnemonic(respond) - }) - }, - } - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/otp.ts b/packages/wallet/wdk/src/sequence/handlers/otp.ts deleted file mode 100644 index 42bf85c9e4..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/otp.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Hex, Address } from 'ox' -import { Signers } from '@0xsequence/wallet-core' -import * as Identity from '@0xsequence/identity-instrument' -import { Handler } from './handler.js' -import * as Db from '../../dbs/index.js' -import { Signatures } from '../signatures.js' -import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js' -import { Kinds } from '../types/signer.js' -import { IdentityHandler } from './identity.js' -import { AnswerIncorrectError, ChallengeExpiredError, TooManyAttemptsError } from '../errors.js' -import type { WdkEnv } from '../../env.js' - -type RespondFn = (otp: string) => Promise - -export type PromptOtpHandler = (recipient: string, respond: RespondFn) => Promise - -export class OtpHandler extends IdentityHandler implements Handler { - kind = Kinds.LoginEmailOtp - - private onPromptOtp: undefined | PromptOtpHandler - - constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys, env?: WdkEnv) { - super(nitro, authKeys, signatures, Identity.IdentityType.Email, env) - } - - public registerUI(onPromptOtp: PromptOtpHandler) { - this.onPromptOtp = onPromptOtp - return () => { - this.onPromptOtp = undefined - } - } - - public unregisterUI() { - this.onPromptOtp = undefined - } - - public async getSigner(email: string): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { - const onPromptOtp = this.onPromptOtp - if (!onPromptOtp) { - throw new Error('otp-handler-ui-not-registered') - } - - const challenge = Identity.OtpChallenge.fromRecipient(this.identityType, email) - return await this.handleAuth(challenge, onPromptOtp) - } - - async status( - address: Address.Address, - _imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const signer = await this.getAuthKeySigner(address) - if (signer) { - return { - address, - handler: this, - status: 'ready', - handle: async () => { - await this.sign(signer, request) - return true - }, - } - } - - const onPromptOtp = this.onPromptOtp - if (!onPromptOtp) { - return { - address, - handler: this, - reason: 'ui-not-registered', - status: 'unavailable', - } - } - - return { - address, - handler: this, - status: 'actionable', - message: 'request-otp', - handle: async () => { - const challenge = Identity.OtpChallenge.fromSigner(this.identityType, { - address, - keyType: Identity.KeyType.Ethereum_Secp256k1, - }) - try { - await this.handleAuth(challenge, onPromptOtp) - return true - } catch { - return false - } - }, - } - } - - private handleAuth( - challenge: Identity.OtpChallenge, - onPromptOtp: PromptOtpHandler, - ): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - try { - const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) - - const respond: RespondFn = async (otp) => { - try { - const { signer, email: returnedEmail } = await this.nitroCompleteAuth( - challenge.withAnswer(codeChallenge, otp), - ) - resolve({ signer, email: returnedEmail }) - } catch (e) { - if (e instanceof Identity.Client.AnswerIncorrectError) { - // Keep the handle promise unresolved so that respond can be retried - throw new AnswerIncorrectError() - } else if (e instanceof Identity.Client.ChallengeExpiredError) { - reject(e) - throw new ChallengeExpiredError() - } else if (e instanceof Identity.Client.TooManyAttemptsError) { - reject(e) - throw new TooManyAttemptsError() - } else { - reject(e) - } - } - } - - await onPromptOtp(loginHint, respond) - } catch (e) { - reject(e) - } - }) - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/passkeys.ts b/packages/wallet/wdk/src/sequence/handlers/passkeys.ts deleted file mode 100644 index 17c3196ca2..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/passkeys.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { State } from '@0xsequence/wallet-core' -import { Address, Hex } from 'ox' -import { Kinds } from '../types/signer.js' -import { Signatures } from '../signatures.js' -import { Config, Extensions } from '@0xsequence/wallet-primitives' -import { Handler } from './handler.js' -import { SignerActionable, SignerUnavailable, BaseSignatureRequest } from '../types/index.js' -import type { PasskeyProvider, PasskeySigner } from '../passkeys-provider.js' - -export class PasskeysHandler implements Handler { - kind = Kinds.LoginPasskey - private readySigners = new Map() - - constructor( - private readonly signatures: Signatures, - private readonly extensions: Pick, - private readonly stateReader: State.Reader, - private readonly passkeyProvider: PasskeyProvider, - ) {} - - onStatusChange(_cb: () => void): () => void { - return () => {} - } - - public addReadySigner(signer: PasskeySigner) { - // Use credentialId as key to match specific passkey instances - this.readySigners.set(signer.credentialId, signer) - } - - private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise { - try { - return await this.passkeyProvider.loadFromWitness(this.stateReader, this.extensions, wallet, imageHash) - } catch (e) { - console.warn('Failed to load passkey:', e) - return undefined - } - } - - async status( - address: Address.Address, - imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const base = { address, imageHash, handler: this } - if (address !== this.extensions.passkeys) { - console.warn( - 'PasskeySigner: status address does not match passkey module address', - address, - this.extensions.passkeys, - ) - const status: SignerUnavailable = { - ...base, - status: 'unavailable', - reason: 'unknown-error', - } - return status - } - - // First check if we have a ready signer that matches the imageHash - let passkey: PasskeySigner | undefined - - // Look for a ready signer with matching imageHash - for (const readySigner of this.readySigners.values()) { - if (imageHash && readySigner.imageHash === imageHash) { - passkey = readySigner - break - } - } - - // If no ready signer found, fall back to loading from witness - if (!passkey && imageHash) { - passkey = await this.loadPasskey(request.envelope.wallet, imageHash) - } - - if (!passkey) { - console.warn('PasskeySigner: status failed to load passkey', address, imageHash) - const status: SignerUnavailable = { - ...base, - status: 'unavailable', - reason: 'unknown-error', - } - return status - } - - // At this point, we know imageHash is defined because we have a passkey - if (!imageHash) { - throw new Error('imageHash is required for passkey operations') - } - - const status: SignerActionable = { - ...base, - status: 'actionable', - message: 'request-interaction-with-passkey', - imageHash: imageHash, - handle: async () => { - const normalized = Config.normalizeSignerSignature( - passkey.signSapient(request.envelope.wallet, request.envelope.chainId, request.envelope.payload, imageHash), - ) - const signature = await normalized.signature - await this.signatures.addSignature(request.id, { - address, - imageHash, - signature, - }) - return true - }, - } - return status - } -} diff --git a/packages/wallet/wdk/src/sequence/handlers/recovery.ts b/packages/wallet/wdk/src/sequence/handlers/recovery.ts deleted file mode 100644 index 9c0ca654d8..0000000000 --- a/packages/wallet/wdk/src/sequence/handlers/recovery.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Address } from 'ox/Address' -import { BaseSignatureRequest, SignerUnavailable, SignerReady, SignerActionable, Kinds } from '../types/index.js' -import { Handler } from './handler.js' -import { Recovery } from '../recovery.js' -import { Payload } from '@0xsequence/wallet-primitives' -import { Hex } from 'ox' -import { Signatures } from '../signatures.js' - -export class RecoveryHandler implements Handler { - kind = Kinds.Recovery - - constructor( - private readonly signatures: Signatures, - public readonly recovery: Recovery, - ) {} - - onStatusChange(cb: () => void): () => void { - return this.recovery.onQueuedPayloadsUpdate(undefined, cb) - } - - async status( - address: Address, - imageHash: Hex.Hex | undefined, - request: BaseSignatureRequest, - ): Promise { - const queued = await this.recovery.getQueuedRecoveryPayloads(request.wallet, request.envelope.chainId) - - // If there is no queued payload for this request then we are unavailable - const requestHash = Hex.fromBytes( - Payload.hash(request.envelope.wallet, request.envelope.chainId, request.envelope.payload), - ) - const found = queued.find((p) => p.payloadHash === requestHash) - if (!found) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'no-recovery-payload-queued', - } - } - - if (!imageHash) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'no-image-hash', - } - } - - if (found.endTimestamp > Date.now() / 1000) { - return { - address, - handler: this, - status: 'unavailable', - reason: 'timelock-not-met', - } - } - - try { - const signature = await this.recovery.encodeRecoverySignature(imageHash, found.signer) - - return { - address, - handler: this, - status: 'ready', - handle: async () => { - this.signatures.addSignature(request.id, { - imageHash, - signature: { - address, - data: Hex.fromBytes(signature), - type: 'sapient_compact', - }, - }) - return true - }, - } - } catch { - return { - address, - handler: this, - status: 'unavailable', - reason: 'failed-to-encode-recovery-signature', - } - } - } -} diff --git a/packages/wallet/wdk/src/sequence/index.ts b/packages/wallet/wdk/src/sequence/index.ts deleted file mode 100644 index d2afe15a37..0000000000 --- a/packages/wallet/wdk/src/sequence/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Network } from '@0xsequence/wallet-primitives' -export { Network as Networks } - -export type { ManagerOptions, Databases, Sequence, Modules, Shared } from './manager.js' -export { ManagerOptionsDefaults, CreateWalletOptionsDefaults, applyManagerOptionsDefaults, Manager } from './manager.js' -export { defaultPasskeyProvider } from './passkeys-provider.js' -export type { PasskeyProvider, PasskeySigner } from './passkeys-provider.js' -export { Sessions } from './sessions.js' -export { Signatures } from './signatures.js' -export type { - StartSignUpWithRedirectArgs, - StartAddLoginSignerWithRedirectArgs, - CommonSignupArgs, - PasskeySignupArgs, - MnemonicSignupArgs, - EmailOtpSignupArgs, - CompleteRedirectArgs, - SignupArgs, - AddLoginSignerArgs, - RemoveLoginSignerArgs, - LoginToWalletArgs, - LoginToMnemonicArgs, - LoginToPasskeyArgs, - LoginArgs, -} from './wallets.js' -export { isLoginToWalletArgs, isLoginToMnemonicArgs, isLoginToPasskeyArgs, Wallets } from './wallets.js' - -export * from './types/index.js' -import * as Handlers from './handlers/index.js' -export { Handlers } diff --git a/packages/wallet/wdk/src/sequence/logger.ts b/packages/wallet/wdk/src/sequence/logger.ts deleted file mode 100644 index d2113ec748..0000000000 --- a/packages/wallet/wdk/src/sequence/logger.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Shared } from './manager.js' - -export class Logger { - constructor(private readonly shared: Shared) {} - - log(...args: any[]) { - if (this.shared.verbose) { - console.log(...args) - } - } -} diff --git a/packages/wallet/wdk/src/sequence/manager.ts b/packages/wallet/wdk/src/sequence/manager.ts deleted file mode 100644 index c7eecf3294..0000000000 --- a/packages/wallet/wdk/src/sequence/manager.ts +++ /dev/null @@ -1,878 +0,0 @@ -import { Bundler, Signers as CoreSigners, State } from '@0xsequence/wallet-core' -import { Relayer } from '@0xsequence/relayer' -import { IdentityInstrument } from '@0xsequence/identity-instrument' -import { createAttestationVerifyingFetch } from '@0xsequence/tee-verifier' -import { Config, Constants, Context, Extensions, Network } from '@0xsequence/wallet-primitives' -import { Address } from 'ox' -import * as Db from '../dbs/index.js' -import { resolveWdkEnv, type WdkEnv } from '../env.js' -import { Cron } from './cron.js' -import { Devices } from './devices.js' -import { Guards, GuardRole } from './guards.js' -import { AuthCodeHandler } from './handlers/authcode.js' -import { - AuthCodePkceHandler, - DevicesHandler, - Handler, - IdTokenHandler, - MnemonicHandler, - OtpHandler, - PasskeysHandler, -} from './handlers/index.js' -import { RecoveryHandler } from './handlers/recovery.js' -import { Logger } from './logger.js' -import { Messages, MessagesInterface } from './messages.js' -import { Recovery, RecoveryInterface } from './recovery.js' -import { Sessions, SessionsInterface } from './sessions.js' -import { Signatures, SignaturesInterface } from './signatures.js' -import { Signers } from './signers.js' -import { Transactions, TransactionsInterface } from './transactions.js' -import { Kinds } from './types/signer.js' -import { Wallets, WalletsInterface } from './wallets.js' -import { GuardHandler, PromptCodeHandler } from './handlers/guard.js' -import { PasskeyCredential } from '../dbs/index.js' -import { PromptMnemonicHandler } from './handlers/mnemonic.js' -import { PromptIdTokenHandler } from './handlers/idtoken.js' -import { PromptOtpHandler } from './handlers/otp.js' -import { defaultPasskeyProvider, type PasskeyProvider } from './passkeys-provider.js' - -type CustomIdentityProvider = - | { - kind: `custom-${string}` - authMethod: 'id-token' - issuer: string - clientId: string - } - | { - kind: `custom-${string}` - authMethod: 'authcode' | 'authcode-pkce' - issuer: string - oauthUrl: string - clientId: string - } - -export type ManagerOptions = { - verbose?: boolean - - extensions?: Extensions.Extensions - context?: Context.Context - context4337?: Context.Context - guest?: Address.Address - - encryptedPksDb?: CoreSigners.Pk.Encrypted.EncryptedPksDb - managerDb?: Db.Wallets - transactionsDb?: Db.Transactions - signaturesDb?: Db.Signatures - messagesDb?: Db.Messages - authCommitmentsDb?: Db.AuthCommitments - authKeysDb?: Db.AuthKeys - recoveryDb?: Db.Recovery - passkeyCredentialsDb?: Db.PasskeyCredentials - - dbPruningInterval?: number - - env?: WdkEnv - passkeyProvider?: PasskeyProvider - - stateProvider?: State.Provider - networks?: Network.Network[] - relayers?: Relayer.Relayer[] | (() => Relayer.Relayer[]) - bundlers?: Bundler.Bundler[] - guardUrl?: string - guardAddresses?: Record - - nonWitnessableSigners?: Address.Address[] - - // The default guard topology MUST have a placeholder address for the guard address - defaultGuardTopology?: Config.Topology - defaultRecoverySettings?: RecoverySettings - - // EIP-6963 support - multiInjectedProviderDiscovery?: boolean - - identity?: { - url?: string - fetch?: typeof fetch - verifyAttestation?: boolean - expectedPcr0?: string[] - scope?: string - email?: { - enabled: boolean - } - google?: { - enabled: boolean - clientId: string - authMethod?: 'authcode-pkce' | 'id-token' - } - apple?: { - enabled: boolean - clientId: string - authMethod?: 'authcode' | 'id-token' - } - customProviders?: CustomIdentityProvider[] - } -} - -export type ResolvedIdentityOptions = { - url: string - fetch?: typeof fetch - verifyAttestation: boolean - expectedPcr0?: string[] - scope?: string - email: { - enabled: boolean - } - google: { - enabled: boolean - clientId: string - authMethod: 'authcode-pkce' | 'id-token' - } - apple: { - enabled: boolean - clientId: string - authMethod: 'authcode' | 'id-token' - } - customProviders?: CustomIdentityProvider[] -} - -export type ResolvedManagerOptions = { - verbose: boolean - - extensions: Extensions.Extensions - context: Context.Context - context4337: Context.Context - guest: Address.Address - - encryptedPksDb: CoreSigners.Pk.Encrypted.EncryptedPksDb - managerDb: Db.Wallets - transactionsDb: Db.Transactions - signaturesDb: Db.Signatures - messagesDb: Db.Messages - authCommitmentsDb: Db.AuthCommitments - authKeysDb: Db.AuthKeys - recoveryDb: Db.Recovery - passkeyCredentialsDb: Db.PasskeyCredentials - - dbPruningInterval: number - - env: WdkEnv - passkeyProvider: PasskeyProvider - - stateProvider: State.Provider - networks: Network.Network[] - relayers: Relayer.Relayer[] | (() => Relayer.Relayer[]) - bundlers: Bundler.Bundler[] - guardUrl: string - guardAddresses: Record - - nonWitnessableSigners: Address.Address[] - - defaultGuardTopology: Config.Topology - defaultRecoverySettings: RecoverySettings - - multiInjectedProviderDiscovery: boolean - - identity: ResolvedIdentityOptions -} - -export const ManagerOptionsDefaults = { - verbose: false, - - extensions: Extensions.Rc5, - context: Context.Rc5, - context4337: Context.Rc5_4337, - guest: Constants.DefaultGuestAddress, - - encryptedPksDb: new CoreSigners.Pk.Encrypted.EncryptedPksDb(), - managerDb: new Db.Wallets(), - signaturesDb: new Db.Signatures(), - transactionsDb: new Db.Transactions(), - messagesDb: new Db.Messages(), - authCommitmentsDb: new Db.AuthCommitments(), - recoveryDb: new Db.Recovery(), - authKeysDb: new Db.AuthKeys(), - passkeyCredentialsDb: new Db.PasskeyCredentials(), - - dbPruningInterval: 1000 * 60 * 60 * 24, // 24 hours - - passkeyProvider: defaultPasskeyProvider, - - stateProvider: typeof fetch !== 'undefined' ? new State.Sequence.Provider(undefined, fetch) : undefined, - networks: Network.ALL, - relayers: () => { - if (typeof window !== 'undefined') { - return [Relayer.LocalRelayer.createFromWindow(window)].filter((r) => r !== undefined) - } - return [] - }, - bundlers: [], - - nonWitnessableSigners: [] as Address.Address[], - - guardUrl: 'https://guard.sequence.app', - guardAddresses: { - wallet: '0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742', - sessions: '0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1', - } as Record, - - defaultGuardTopology: { - type: 'nested', - weight: 1n, - threshold: 1n, - tree: [ - { - type: 'signer', - address: Constants.PlaceholderAddress, - weight: 1n, - }, - { - type: 'signer', - // Sequence dev multisig, as recovery guard signer - address: '0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4', - weight: 1n, - }, - ], - } as Config.NestedLeaf, - - defaultSessionsTopology: { - type: 'sapient-signer', - weight: 1n, - } as Omit, - - defaultRecoverySettings: { - requiredDeltaTime: 2592000n, // 30 days (in seconds) - minTimestamp: 0n, - includeTestnets: false, - }, - - multiInjectedProviderDiscovery: true, - - identity: { - url: 'https://identity.sequence.app', - fetch: typeof window !== 'undefined' ? window.fetch : undefined, - verifyAttestation: true, - email: { - enabled: false, - }, - google: { - enabled: false, - clientId: '', - authMethod: 'authcode-pkce' as const, - }, - apple: { - enabled: false, - clientId: '', - authMethod: 'authcode' as const, - }, - }, -} - -export const CreateWalletOptionsDefaults = { - useGuard: false, -} - -export function applyManagerOptionsDefaults(options?: ManagerOptions): ResolvedManagerOptions { - const env = resolveWdkEnv(options?.env) - - const identity: ResolvedIdentityOptions = { - ...ManagerOptionsDefaults.identity, - ...options?.identity, - email: { ...ManagerOptionsDefaults.identity.email, ...options?.identity?.email }, - google: { ...ManagerOptionsDefaults.identity.google, ...options?.identity?.google }, - apple: { ...ManagerOptionsDefaults.identity.apple, ...options?.identity?.apple }, - } - - if (!identity.fetch && env.fetch) { - identity.fetch = env.fetch - } - - let encryptedPksDb = options?.encryptedPksDb ?? ManagerOptionsDefaults.encryptedPksDb - if (!options?.encryptedPksDb && options?.env) { - encryptedPksDb = new CoreSigners.Pk.Encrypted.EncryptedPksDb(undefined, undefined, env) - } - - let authKeysDb = options?.authKeysDb ?? ManagerOptionsDefaults.authKeysDb - if (!options?.authKeysDb && options?.env) { - authKeysDb = new Db.AuthKeys(undefined, env) - } - - let stateProvider = options?.stateProvider ?? ManagerOptionsDefaults.stateProvider - if (!options?.stateProvider && options?.env?.fetch) { - stateProvider = new State.Sequence.Provider(undefined, options.env.fetch) - } else if (!stateProvider && env.fetch) { - stateProvider = new State.Sequence.Provider(undefined, env.fetch) - } - - if (!stateProvider) { - throw new Error('stateProvider is required. Provide ManagerOptions.stateProvider or env.fetch') - } - - const extensions = options?.extensions ?? ManagerOptionsDefaults.extensions - const defaultGuardTopology = options?.defaultGuardTopology ?? ManagerOptionsDefaults.defaultGuardTopology - - // Merge and normalize non-witnessable signers. - // We always include the sessions extension address for the active extensions set. - const nonWitnessable = new Set() - for (const address of ManagerOptionsDefaults.nonWitnessableSigners ?? []) { - nonWitnessable.add(address.toLowerCase()) - } - for (const address of options?.nonWitnessableSigners ?? []) { - nonWitnessable.add(address.toLowerCase()) - } - nonWitnessable.add(extensions.sessions.toLowerCase()) - - // Include static signer leaves from the guard topology (e.g. recovery guard signer), - // but ignore the placeholder address that is later replaced per-role. - if (defaultGuardTopology) { - const guardTopologySigners = Config.getSigners(defaultGuardTopology) - for (const signer of guardTopologySigners.signers) { - if (Address.isEqual(signer, Constants.PlaceholderAddress)) { - continue - } - nonWitnessable.add(signer.toLowerCase()) - } - for (const signer of guardTopologySigners.sapientSigners) { - nonWitnessable.add(signer.address.toLowerCase()) - } - } - - return { - verbose: options?.verbose ?? ManagerOptionsDefaults.verbose, - - extensions, - context: options?.context ?? ManagerOptionsDefaults.context, - context4337: options?.context4337 ?? ManagerOptionsDefaults.context4337, - guest: options?.guest ?? ManagerOptionsDefaults.guest, - - encryptedPksDb, - managerDb: options?.managerDb ?? ManagerOptionsDefaults.managerDb, - transactionsDb: options?.transactionsDb ?? ManagerOptionsDefaults.transactionsDb, - signaturesDb: options?.signaturesDb ?? ManagerOptionsDefaults.signaturesDb, - messagesDb: options?.messagesDb ?? ManagerOptionsDefaults.messagesDb, - authCommitmentsDb: options?.authCommitmentsDb ?? ManagerOptionsDefaults.authCommitmentsDb, - recoveryDb: options?.recoveryDb ?? ManagerOptionsDefaults.recoveryDb, - authKeysDb, - passkeyCredentialsDb: options?.passkeyCredentialsDb ?? ManagerOptionsDefaults.passkeyCredentialsDb, - - dbPruningInterval: options?.dbPruningInterval ?? ManagerOptionsDefaults.dbPruningInterval, - - env, - passkeyProvider: options?.passkeyProvider ?? ManagerOptionsDefaults.passkeyProvider, - - stateProvider, - networks: options?.networks ?? ManagerOptionsDefaults.networks, - relayers: options?.relayers ?? ManagerOptionsDefaults.relayers, - bundlers: options?.bundlers ?? ManagerOptionsDefaults.bundlers, - guardUrl: options?.guardUrl ?? ManagerOptionsDefaults.guardUrl, - guardAddresses: options?.guardAddresses ?? ManagerOptionsDefaults.guardAddresses, - - nonWitnessableSigners: Array.from(nonWitnessable) as Address.Address[], - - defaultGuardTopology, - defaultRecoverySettings: options?.defaultRecoverySettings ?? ManagerOptionsDefaults.defaultRecoverySettings, - - multiInjectedProviderDiscovery: - options?.multiInjectedProviderDiscovery ?? ManagerOptionsDefaults.multiInjectedProviderDiscovery, - - identity, - } -} - -export type RecoverySettings = { - requiredDeltaTime: bigint - minTimestamp: bigint - includeTestnets?: boolean -} - -export type Databases = { - readonly encryptedPks: CoreSigners.Pk.Encrypted.EncryptedPksDb - readonly manager: Db.Wallets - readonly signatures: Db.Signatures - readonly messages: Db.Messages - readonly transactions: Db.Transactions - readonly authCommitments: Db.AuthCommitments - readonly authKeys: Db.AuthKeys - readonly recovery: Db.Recovery - readonly passkeyCredentials: Db.PasskeyCredentials - - readonly pruningInterval: number -} - -export type Sequence = { - readonly context: Context.Context - readonly context4337: Context.Context - readonly extensions: Extensions.Extensions - readonly guest: Address.Address - - readonly stateProvider: State.Provider - - readonly networks: Network.Network[] - readonly relayers: Relayer.Relayer[] - readonly bundlers: Bundler.Bundler[] - - readonly nonWitnessableSigners: ReadonlySet - - readonly defaultGuardTopology: Config.Topology - readonly defaultRecoverySettings: RecoverySettings - - readonly guardUrl: string - readonly guardAddresses: Record -} - -export type Modules = { - readonly logger: Logger - readonly devices: Devices - readonly guards: Guards - readonly wallets: Wallets - readonly sessions: Sessions - readonly signers: Signers - readonly signatures: Signatures - readonly transactions: Transactions - readonly messages: Messages - readonly recovery: Recovery - readonly cron: Cron -} - -export type Shared = { - readonly verbose: boolean - - readonly sequence: Sequence - readonly databases: Databases - readonly env: WdkEnv - readonly passkeyProvider: PasskeyProvider - - readonly handlers: Map - - modules: Modules -} - -export class Manager { - private readonly shared: Shared - - private readonly mnemonicHandler: MnemonicHandler - private readonly devicesHandler: DevicesHandler - private readonly passkeysHandler: PasskeysHandler - private readonly recoveryHandler: RecoveryHandler - private readonly guardHandler: GuardHandler - - private readonly otpHandler?: OtpHandler - - // ======== Begin Public Modules ======== - - /** - * Manages the lifecycle of user wallets within the WDK, from creation (sign-up) - * to session management (login/logout). - * - * This is the primary entry point for users. It handles the association of login - * credentials (like mnemonics or passkeys) with on-chain wallet configurations. - * - * Key behaviors: - * - `signUp()`: Creates a new wallet configuration and deploys it. - * - `login()`: Adds the current device as a new authorized signer to an existing wallet. This is a 2-step process requiring a signature from an existing signer. - * - `logout()`: Can perform a "soft" logout (local session removal) or a "hard" logout (on-chain key removal), which is also a 2-step process. - * - * This module orchestrates with the `signatures` module to handle the signing of - * configuration updates required for login and hard-logout operations. - * - * @see {WalletsInterface} for all available methods. - */ - public readonly wallets: WalletsInterface - - /** - * Acts as the central coordinator for all signing operations. It does not perform - * the signing itself but manages the entire process. - * - * When an action requires a signature (e.g., sending a transaction, updating configuration), - * a `SignatureRequest` is created here. This module then determines which signers - * (devices, passkeys, etc.) are required to meet the wallet's security threshold. - * - * Key features: - * - Tracks the real-time status of each required signer (`ready`, `actionable`, `signed`, `unavailable`). - * - Calculates the collected signature weight against the required threshold. - * - Provides hooks (`onSignatureRequestUpdate`) for building reactive UIs that guide the user through the signing process. - * - * Developers will primarily interact with this module to monitor the state of a signing - * request initiated by other modules like `transactions` or `wallets`. - * - * @see {SignaturesInterface} for all available methods. - * @see {SignatureRequest} for the detailed structure of a request object. - */ - public readonly signatures: SignaturesInterface - - /** - * Manages the end-to-end lifecycle of on-chain transactions, from creation to final confirmation. - * - * This module follows a distinct state machine: - * 1. `request()`: Creates a new transaction request. - * 2. `define()`: Fetches quotes and fee options from all available relayers and ERC-4337 bundlers. - * 3. `selectRelayer()`: Finalizes the transaction payload based on the chosen relayer and creates a `SignatureRequest`. - * 4. `relay()`: Submits the signed transaction to the chosen relayer/bundler for execution. - * - * The final on-chain status (`confirmed` or `failed`) is updated asynchronously by a background - * process. Use `onTransactionUpdate` to monitor a transaction's progress. - * - * @see {TransactionsInterface} for all available methods. - * @see {Transaction} for the detailed structure of a transaction object and its states. - */ - public readonly transactions: TransactionsInterface - - /** - * Handles the signing of off-chain messages, such as EIP-191 personal_sign messages - * or EIP-712 typed data. - * - * The flow is simpler than on-chain transactions: - * 1. `request()`: Prepares the message and creates a `SignatureRequest`. - * 2. The user signs the request via the `signatures` module UI. - * 3. `complete()`: Builds the final, EIP-1271/EIP-6492 compliant signature string. - * - * This module is essential for dapps that require off-chain proof of ownership or authorization. - * The resulting signature is verifiable on-chain by calling `isValidSignature` on the wallet contract. - * - * @see {MessagesInterface} for all available methods. - */ - public readonly messages: MessagesInterface - - /** - * Manages session keys, which are temporary, often permissioned, signers for a wallet. - * This allows dapps to perform actions on the user's behalf without prompting for a signature - * for every transaction. - * - * Two types of sessions are supported: - * - **Implicit Sessions**: Authorized by an off-chain attestation from the user's primary identity - * signer. They are dapp-specific and don't require a configuration update to create. Ideal for - * low-risk, frequent actions within a single application. - * - **Explicit Sessions**: Authorized by a wallet configuration update. These sessions - * are more powerful and can be governed by detailed, on-chain permissions (e.g., value limits, - * contract targets, function call rules). - * - * This module handles the creation, removal, and configuration of both session types. - * - * @see {SessionsInterface} for all available methods. - */ - public readonly sessions: SessionsInterface - - /** - * Manages the wallet's recovery mechanism, allowing designated recovery signers - * to execute transactions after a time delay. - * - * This module is responsible for: - * - **Configuration**: Adding or removing recovery signers (e.g., a secondary mnemonic). This is a standard configuration update that must be signed by the wallet's primary signers. - * - **Execution**: A two-step process to use the recovery feature: - * 1. `queuePayload()`: A recovery signer signs a payload, which is then sent on-chain to start a timelock. - * 2. After the timelock, the `recovery` handler itself can sign a transaction to execute the queued payload. - * - **Monitoring**: `updateQueuedPayloads()` fetches on-chain data about pending recovery attempts, a crucial security feature. - * - * @see {RecoveryInterface} for all available methods. - */ - public readonly recovery: RecoveryInterface - - // ======== End Public Modules ======== - - constructor(options?: ManagerOptions) { - const ops = applyManagerOptionsDefaults(options) - - // Build relayers list - const relayers: Relayer.Relayer[] = [] - - // Add EIP-6963 relayers if enabled - if (ops.multiInjectedProviderDiscovery) { - try { - relayers.push(...Relayer.EIP6963.getRelayers()) - } catch (error) { - console.warn('Failed to initialize EIP-6963 relayers:', error) - } - } - - // Add configured relayers - const configuredRelayers = typeof ops.relayers === 'function' ? ops.relayers() : ops.relayers - relayers.push(...configuredRelayers) - - const shared: Shared = { - verbose: ops.verbose, - - sequence: { - context: ops.context, - context4337: ops.context4337, - extensions: ops.extensions, - guest: ops.guest, - - stateProvider: ops.stateProvider, - networks: ops.networks, - relayers, - bundlers: ops.bundlers, - - nonWitnessableSigners: new Set( - (ops.nonWitnessableSigners ?? []).map((address) => address.toLowerCase() as Address.Address), - ), - - defaultGuardTopology: ops.defaultGuardTopology, - defaultRecoverySettings: ops.defaultRecoverySettings, - - guardUrl: ops.guardUrl, - guardAddresses: ops.guardAddresses, - }, - - databases: { - encryptedPks: ops.encryptedPksDb, - manager: ops.managerDb, - signatures: ops.signaturesDb, - transactions: ops.transactionsDb, - messages: ops.messagesDb, - authCommitments: ops.authCommitmentsDb, - authKeys: ops.authKeysDb, - recovery: ops.recoveryDb, - passkeyCredentials: ops.passkeyCredentialsDb, - - pruningInterval: ops.dbPruningInterval, - }, - - env: ops.env, - passkeyProvider: ops.passkeyProvider, - - modules: {} as any, - handlers: new Map(), - } - - const modules: Modules = { - cron: new Cron(shared), - logger: new Logger(shared), - devices: new Devices(shared), - guards: new Guards(shared), - wallets: new Wallets(shared), - sessions: new Sessions(shared), - signers: new Signers(shared), - signatures: new Signatures(shared), - transactions: new Transactions(shared), - messages: new Messages(shared), - recovery: new Recovery(shared), - } - - this.wallets = modules.wallets - this.signatures = modules.signatures - this.transactions = modules.transactions - this.messages = modules.messages - this.sessions = modules.sessions - this.recovery = modules.recovery - - this.devicesHandler = new DevicesHandler(modules.signatures, modules.devices) - shared.handlers.set(Kinds.LocalDevice, this.devicesHandler) - - this.passkeysHandler = new PasskeysHandler( - modules.signatures, - shared.sequence.extensions, - shared.sequence.stateProvider, - shared.passkeyProvider, - ) - shared.handlers.set(Kinds.LoginPasskey, this.passkeysHandler) - - this.mnemonicHandler = new MnemonicHandler(modules.signatures) - shared.handlers.set(Kinds.LoginMnemonic, this.mnemonicHandler) - - this.recoveryHandler = new RecoveryHandler(modules.signatures, modules.recovery) - shared.handlers.set(Kinds.Recovery, this.recoveryHandler) - - this.guardHandler = new GuardHandler(modules.signatures, modules.guards) - shared.handlers.set(Kinds.Guard, this.guardHandler) - - const verifyingFetch = ops.identity.verifyAttestation - ? createAttestationVerifyingFetch({ - fetch: ops.identity.fetch, - expectedPCRs: ops.identity.expectedPcr0 ? new Map([[0, ops.identity.expectedPcr0]]) : undefined, - logTiming: true, - }) - : ops.identity.fetch - const identityInstrument = new IdentityInstrument(ops.identity.url, ops.identity.scope, verifyingFetch) - - if (ops.identity.email?.enabled) { - this.otpHandler = new OtpHandler(identityInstrument, modules.signatures, shared.databases.authKeys, shared.env) - shared.handlers.set(Kinds.LoginEmailOtp, this.otpHandler) - } - if (ops.identity.google?.enabled) { - if (ops.identity.google.authMethod === 'id-token') { - shared.handlers.set( - Kinds.LoginGoogle, - new IdTokenHandler( - 'google-id-token', - 'https://accounts.google.com', - ops.identity.google.clientId, - identityInstrument, - modules.signatures, - shared.databases.authKeys, - shared.env, - ), - ) - } else { - shared.handlers.set( - Kinds.LoginGoogle, - new AuthCodePkceHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - ops.identity.google.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) - } - } - if (ops.identity.apple?.enabled) { - if (ops.identity.apple.authMethod === 'id-token') { - shared.handlers.set( - Kinds.LoginApple, - new IdTokenHandler( - 'apple-id-token', - 'https://appleid.apple.com', - ops.identity.apple.clientId, - identityInstrument, - modules.signatures, - shared.databases.authKeys, - shared.env, - ), - ) - } else { - shared.handlers.set( - Kinds.LoginApple, - new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - ops.identity.apple.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) - } - } - if (ops.identity.customProviders?.length) { - for (const provider of ops.identity.customProviders) { - switch (provider.authMethod) { - case 'id-token': - shared.handlers.set( - provider.kind, - new IdTokenHandler( - provider.kind, - provider.issuer, - provider.clientId, - identityInstrument, - modules.signatures, - shared.databases.authKeys, - shared.env, - ), - ) - break - case 'authcode': - shared.handlers.set( - provider.kind, - new AuthCodeHandler( - provider.kind, - provider.issuer, - provider.oauthUrl, - provider.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) - break - case 'authcode-pkce': - shared.handlers.set( - provider.kind, - new AuthCodePkceHandler( - provider.kind, - provider.issuer, - provider.oauthUrl, - provider.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - shared.env, - ), - ) - break - default: - throw new Error('unsupported auth method') - } - } - } - - shared.modules = modules - this.shared = shared - - // Initialize modules - for (const module of Object.values(modules)) { - if ('initialize' in module && typeof module.initialize === 'function') { - module.initialize() - } - } - } - - public registerMnemonicUI(onPromptMnemonic: PromptMnemonicHandler) { - return this.mnemonicHandler.registerUI(onPromptMnemonic) - } - - public registerOtpUI(onPromptOtp: PromptOtpHandler) { - return this.otpHandler?.registerUI(onPromptOtp) || (() => {}) - } - - public registerIdTokenUI(onPromptIdToken: PromptIdTokenHandler) { - const unregisters: (() => void)[] = [] - - this.shared.handlers.forEach((handler) => { - if (handler instanceof IdTokenHandler) { - unregisters.push(handler.registerUI(onPromptIdToken)) - } - }) - - return () => { - unregisters.forEach((unregister) => unregister()) - } - } - - public registerGuardUI(onPromptCode: PromptCodeHandler) { - return this.guardHandler?.registerUI(onPromptCode) || (() => {}) - } - - public async setRedirectPrefix(prefix: string) { - this.shared.handlers.forEach((handler) => { - if (handler instanceof AuthCodeHandler) { - handler.setRedirectUri(prefix + '/' + handler.signupKind) - } - }) - } - - public getNetworks(): Network.Network[] { - return this.shared.sequence.networks - } - - public getNetwork(chainId: number): Network.Network | undefined { - return this.shared.sequence.networks.find((n) => n.chainId === chainId) - } - - public async getPasskeyCredentials(): Promise { - return this.shared.databases.passkeyCredentials.list() - } - - // DBs - - public async stop() { - await this.shared.modules.cron.stop() - - await Promise.all([ - this.shared.databases.authKeys.close(), - this.shared.databases.authCommitments.close(), - this.shared.databases.manager.close(), - this.shared.databases.recovery.close(), - this.shared.databases.signatures.close(), - this.shared.databases.transactions.close(), - ]) - } -} diff --git a/packages/wallet/wdk/src/sequence/messages.ts b/packages/wallet/wdk/src/sequence/messages.ts deleted file mode 100644 index d8fe480c23..0000000000 --- a/packages/wallet/wdk/src/sequence/messages.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { Envelope, Wallet } from '@0xsequence/wallet-core' -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider, RpcTransport } from 'ox' -import { v7 as uuidv7 } from 'uuid' -import { Shared } from './manager.js' -import { Message, MessageRequest, MessageRequested, MessageSigned } from './types/message-request.js' - -export interface MessagesInterface { - /** - * Retrieves a list of all message requests, both pending and signed, across all wallets - * managed by this instance. - * - * This is useful for displaying an overview or history of signing activities, or pending signature requests. - * - * @returns A promise that resolves to an array of `Message` objects. - */ - list(): Promise - - /** - * Retrieves the full state of a specific message request by its unique ID or its associated signature ID. - * The returned `Message` object contains the complete context, including the envelope, status, - * and, if signed, the final message signature. - * - * @param messageOrSignatureId The unique identifier of the message (`id`) or its corresponding signature request (`signatureId`). - * @returns A promise that resolves to the `Message` object. - * @throws An error if a message with the given ID is not found. - */ - get(messageOrSignatureId: string): Promise - - /** - * Initiates a request to sign a message. - * - * This method prepares a standard EIP-191 or EIP-712 payload, wraps it in a wallet-specific - * `Envelope`, and creates a signature request. It does **not** sign the message immediately. - * Instead, it returns a `signatureId` which is used to track the signing process. - * - * The actual signing is managed by the `Signatures` module, which handles collecting signatures - * from the required signers (devices, passkeys, etc.). - * - * @param wallet The address of the wallet that will be signing the message. - * @param message The message to be signed. Can be a plain string, a hex string, or an EIP-712 typed data object. - * The SDK will handle the appropriate encoding. - * @param chainId (Optional) The chain ID to include in the signature's EIP-712 domain separator. - * This is crucial for replay protection if the signature is intended for on-chain verification. Use `0n` or `undefined` for off-chain signatures. - * @param options (Optional) Additional metadata for the request. - * @param options.source A string identifying the origin of the request (e.g., 'dapp.com', 'wallet-webapp'). - * @returns A promise that resolves to a unique `signatureId`. This ID should be used to interact with the `Signatures` module or to complete the signing process. - * @see {SignaturesInterface} for managing the signing process. - * @see {complete} to finalize the signature after it has been signed. - */ - request( - wallet: Address.Address, - message: MessageRequest, - chainId?: number, - options?: { source?: string }, - ): Promise - - /** - * Finalizes a signed message request and returns the EIP-1271/EIP-6492 compliant signature. - * - * This method should be called after the associated signature request has been fulfilled (i.e., - * the required weight of signatures has been collected). It builds the final, encoded signature - * string that can be submitted for verification. If the wallet is not yet deployed, the signature - * will be automatically wrapped according to EIP-6492. - * - * If the message is already `signed`, this method is idempotent and will simply return the existing signature. - * - * @param messageOrSignatureId The ID of the message (`id`) or its signature request (`signatureId`). - * @returns A promise that resolves to the final, EIP-1271/EIP-6492 compliant signature as a hex string. - * @throws An error if the message request is not found or if the signature threshold has not been met. - */ - complete(messageOrSignatureId: string): Promise - - /** - * Deletes a message request from the local database. - * This action removes both the message record and its underlying signature request, - * effectively canceling the signing process if it was still pending. - * - * @param messageOrSignatureId The ID of the message (`id`) or its signature request (`signatureId`) to delete. - * @returns A promise that resolves when the deletion is complete. It does not throw if the item is not found. - */ - delete(messageOrSignatureId: string): Promise - - /** - * Subscribes to updates for the list of all message requests. - * - * The callback is fired whenever a message is created, its status changes, or it is deleted. - * This is ideal for keeping a high-level list view of message signing activities synchronized. - * - * @param cb The callback function to execute with the updated list of `Message` objects. - * @param trigger (Optional) If `true`, the callback will be immediately invoked with the current list of messages upon registration. - * @returns A function that, when called, will unsubscribe the listener. - */ - onMessagesUpdate(cb: (messages: Message[]) => void, trigger?: boolean): () => void - - /** - * Subscribes to real-time updates for a single, specific message request. - * - * The callback is invoked whenever the state of the specified message changes. - * This is useful for building reactive UI components that display the status of a - * specific signing process. - * - * @param messageId The unique ID of the message to monitor. - * @param cb The callback function to execute with the updated `Message` object. - * @param trigger (Optional) If `true`, the callback will be immediately invoked with the current state of the message. - * @returns A function that, when called, will unsubscribe the listener. - */ - onMessageUpdate(messageId: string, cb: (message: Message) => void, trigger?: boolean): () => void -} - -export class Messages implements MessagesInterface { - constructor(private readonly shared: Shared) {} - - public async list(): Promise { - return this.shared.databases.messages.list() - } - - public async get(messageOrSignatureId: string): Promise { - return this.getByMessageOrSignatureId(messageOrSignatureId) - } - - private async getByMessageOrSignatureId(messageOrSignatureId: string): Promise { - const messages = await this.list() - const message = messages.find((m) => m.id === messageOrSignatureId || m.signatureId === messageOrSignatureId) - if (!message) { - throw new Error(`Message ${messageOrSignatureId} not found`) - } - return message - } - - async request( - from: Address.Address, - message: MessageRequest, - chainId?: number, - options?: { - source?: string - }, - ): Promise { - const wallet = new Wallet(from, { stateProvider: this.shared.sequence.stateProvider }) - - // Prepare message payload - const envelope = await wallet.prepareMessageSignature(message, chainId ?? 0) - - // Prepare signature request - const signatureRequest = await this.shared.modules.signatures.request(envelope, 'sign-message', { - origin: options?.source, - }) - - const id = uuidv7() - await this.shared.databases.messages.set({ - id, - wallet: from, - message, - envelope, - source: options?.source ?? 'unknown', - status: 'requested', - signatureId: signatureRequest, - } as MessageRequested) - - return signatureRequest - } - - async complete(messageOrSignatureId: string): Promise { - const message = await this.getByMessageOrSignatureId(messageOrSignatureId) - - if (message.status === 'signed') { - // Return the message signature - return message.messageSignature - } - - const messageId = message.id - const signature = await this.shared.modules.signatures.get(message.signatureId) - if (!signature) { - throw new Error(`Signature ${message.signatureId} not found for message ${messageId}`) - } - - if (!Payload.isMessage(message.envelope.payload) || !Payload.isMessage(signature.envelope.payload)) { - throw new Error(`Message ${messageId} is not a message payload`) - } - - if (!Envelope.isSigned(signature.envelope)) { - throw new Error(`Message ${messageId} is not signed`) - } - - const signatureEnvelope = signature.envelope as Envelope.Signed - const { weight, threshold } = Envelope.weightOf(signatureEnvelope) - if (weight < threshold) { - throw new Error(`Message ${messageId} has insufficient weight`) - } - - // Get the provider for the message chain - let provider: Provider.Provider | undefined - if (message.envelope.chainId !== 0) { - const network = this.shared.sequence.networks.find((network) => network.chainId === message.envelope.chainId) - if (!network) { - throw new Error(`Network not found for ${message.envelope.chainId}`) - } - const transport = RpcTransport.fromHttp(network.rpcUrl) - provider = Provider.from(transport) - } - - const wallet = new Wallet(message.wallet, { stateProvider: this.shared.sequence.stateProvider }) - const messageSignature = Hex.from(await wallet.buildMessageSignature(signatureEnvelope, provider)) - - await this.shared.databases.messages.set({ - ...message, - envelope: signature.envelope, - status: 'signed', - messageSignature, - } as MessageSigned) - await this.shared.modules.signatures.complete(signature.id) - - return messageSignature - } - - onMessagesUpdate(cb: (messages: Message[]) => void, trigger?: boolean) { - const undo = this.shared.databases.messages.addListener(() => { - this.list().then((l) => cb(l)) - }) - - if (trigger) { - this.list().then((l) => cb(l)) - } - - return undo - } - - onMessageUpdate(messageId: string, cb: (message: Message) => void, trigger?: boolean) { - const undo = this.shared.databases.messages.addListener(() => { - this.get(messageId).then((t) => cb(t)) - }) - - if (trigger) { - this.get(messageId).then((t) => cb(t)) - } - - return undo - } - - async delete(messageOrSignatureId: string) { - try { - const message = await this.getByMessageOrSignatureId(messageOrSignatureId) - await this.shared.databases.signatures.del(message.signatureId) - await this.shared.databases.messages.del(message.id) - } catch { - // Ignore - } - } -} diff --git a/packages/wallet/wdk/src/sequence/passkeys-provider.ts b/packages/wallet/wdk/src/sequence/passkeys-provider.ts deleted file mode 100644 index 3ed2068fe3..0000000000 --- a/packages/wallet/wdk/src/sequence/passkeys-provider.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Signers, State } from '@0xsequence/wallet-core' -import type { Extensions } from '@0xsequence/wallet-primitives' -import type { Address, Hex } from 'ox' - -export type PasskeySigner = Signers.SapientSigner & - Signers.Witnessable & { - credentialId: string - publicKey: Extensions.Passkeys.PublicKey - imageHash: Hex.Hex - } - -export type PasskeyProvider = { - create: ( - extensions: Pick, - options?: Signers.Passkey.CreatePasskeyOptions, - ) => Promise - find: ( - stateReader: State.Reader, - extensions: Pick, - options?: Signers.Passkey.FindPasskeyOptions, - ) => Promise - loadFromWitness: ( - stateReader: State.Reader, - extensions: Pick, - wallet: Address.Address, - imageHash: Hex.Hex, - options?: Signers.Passkey.FindPasskeyOptions, - ) => Promise - fromCredential: (args: { - credentialId: string - publicKey: Extensions.Passkeys.PublicKey - extensions: Pick - embedMetadata?: boolean - metadata?: Extensions.Passkeys.PasskeyMetadata - webauthn?: Signers.Passkey.WebAuthnLike - }) => PasskeySigner - isSigner?: (signer: unknown) => signer is PasskeySigner -} - -export const defaultPasskeyProvider: PasskeyProvider = { - create: (extensions, options) => Signers.Passkey.Passkey.create(extensions, options), - find: (stateReader, extensions, options) => Signers.Passkey.Passkey.find(stateReader, extensions, options), - loadFromWitness: (stateReader, extensions, wallet, imageHash, options) => - Signers.Passkey.Passkey.loadFromWitness(stateReader, extensions, wallet, imageHash, options), - fromCredential: ({ credentialId, publicKey, extensions, embedMetadata, metadata, webauthn }) => - new Signers.Passkey.Passkey({ - credentialId, - publicKey, - extensions, - embedMetadata, - metadata, - webauthn, - }), - isSigner: (signer: unknown): signer is PasskeySigner => signer instanceof Signers.Passkey.Passkey, -} diff --git a/packages/wallet/wdk/src/sequence/recovery.ts b/packages/wallet/wdk/src/sequence/recovery.ts deleted file mode 100644 index 2e60743646..0000000000 --- a/packages/wallet/wdk/src/sequence/recovery.ts +++ /dev/null @@ -1,719 +0,0 @@ -import { Envelope } from '@0xsequence/wallet-core' -import { Config, Constants, Extensions, GenericTree, Payload } from '@0xsequence/wallet-primitives' -import { Abi, AbiFunction, Address, Hex, Provider, RpcTransport } from 'ox' -import { MnemonicHandler } from './handlers/mnemonic.js' -import { Shared } from './manager.js' -import { Actions, Module } from './types/index.js' -import { QueuedRecoveryPayload } from './types/recovery.js' -import { Kinds, RecoverySigner } from './types/signer.js' - -const AGGREGATE3 = Abi.from([ - 'function aggregate3((address target, bool allowFailure, bytes callData)[] calls) external payable returns ((bool success, bytes returnData)[])', -])[0]! - -export interface RecoveryInterface { - /** - * Retrieves the list of configured recovery signers for a given wallet. - * - * Recovery signers are special-purpose keys (e.g., a secondary mnemonic or device) that can execute - * transactions on a wallet's behalf after a mandatory time delay (timelock). This method reads the - * wallet's current configuration, finds the recovery module, and returns a detailed list of these signers. - * - * @param wallet The on-chain address of the wallet to query. - * @returns A promise that resolves to an array of `RecoverySigner` objects. If the wallet does not have - * the recovery module enabled, it returns `undefined`. - * @see {RecoverySigner} for details on the returned object structure. - */ - getSigners(wallet: Address.Address): Promise - - /** - * Initiates the process of queuing a recovery payload for future execution. This is the first of a two-part - * process to use the recovery mechanism. - * - * This method creates a special signature request that can *only* be signed by one of the wallet's designated - * recovery signers. It does **not** send a transaction to the blockchain. - * - * @param wallet The address of the wallet that will be recovered. - * @param chainId The chain ID on which the recovery payload is intended to be valid. - * @param payload The transaction calls to be executed after the recovery timelock. - * @returns A promise that resolves to a unique `requestId` for the signature request. This ID is then used - * with the signing UI and `completePayload`. - * @see {completePayload} for the next step. - */ - queuePayload(wallet: Address.Address, chainId: number, payload: Payload.Calls): Promise - - /** - * Finalizes a queued recovery payload request and returns the transaction data needed to start the timelock on-chain. - * - * This method must be called after the `requestId` from `queuePayload` has been successfully signed by a - * recovery signer. It constructs the calldata for a transaction to the Recovery contract. - * - * **Note:** This method does *not* send the transaction. It is the developer's responsibility to take the - * returned `to` and `data` and submit it to the network. - * - * When the timelock has passed, the transaction can be sent using the Recovery handler. To do this, a transaction - * with the same original payload must be constructed, and the Recovery handler will become available to sign. - * - * The Recovery handler has sufficient weight to sign the transaction by itself, but it will only do so after - * the timelock has passed, and only if the payload being sent matches the original one that was queued. - * - * @param requestId The ID of the fulfilled signature request from `queuePayload`. - * @returns A promise that resolves to an object containing the `to` (the Recovery contract address) and `data` - * (the encoded calldata) for the on-chain queuing transaction. - * @throws An error if the `requestId` is invalid, not for a recovery action, or not fully signed. - */ - completePayload(requestId: string): Promise<{ to: Address.Address; data: Hex.Hex }> - - /** - * Initiates a configuration update to add a new mnemonic as a recovery signer for a wallet. - * This mnemonic is intended for emergency use and is protected by the wallet's recovery timelock. - * - * This action requires a signature from the wallet's *primary* signers (e.g., login keys, devices), - * not the recovery signers. - * - * @param wallet The address of the wallet to modify. - * @param mnemonic The mnemonic phrase to add as a new recovery signer. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {completeUpdate} to finalize this change after it has been signed. - */ - addMnemonic(wallet: Address.Address, mnemonic: string): Promise - - /** - * Initiates a configuration update to add any generic address as a recovery signer. - * - * This is useful for adding other wallets or third-party keys as recovery agents. Note that if you add a key - * for which the WDK does not have a registered `Handler`, you will need to manually implement the signing - * flow for that key when it's time to use it for recovery. - * - * This action requires a signature from the wallet's *primary* signers. - * - * @param wallet The address of the wallet to modify. - * @param address The address of the new recovery signer to add. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {completeUpdate} to finalize this change after it has been signed. - */ - addSigner(wallet: Address.Address, address: Address.Address): Promise - - /** - * Initiates a configuration update to remove a recovery signer from a wallet. - * - * This action requires a signature from the wallet's *primary* signers. - * - * @param wallet The address of the wallet to modify. - * @param address The address of the recovery signer to remove. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {completeUpdate} to finalize this change after it has been signed. - */ - removeSigner(wallet: Address.Address, address: Address.Address): Promise - - /** - * Finalizes and saves a pending recovery configuration update. - * - * This method should be called after a signature request from `addMnemonic`, `addSigner`, or `removeSigner` - * has been fulfilled. It saves the new configuration to the state provider, queuing it to be included in - * the wallet's next regular transaction. - * - * **Important:** Initiating a new recovery configuration change (e.g., calling `addSigner`) will automatically - * cancel any other pending configuration update for the same wallet, including those from other modules like - * sessions. Only the most recent configuration change request will remain active. - * - * @param requestId The unique ID of the fulfilled signature request. - * @returns A promise that resolves when the update has been successfully processed and saved. - * @throws An error if the request is not a valid recovery update or has insufficient signatures. - */ - completeUpdate(requestId: string): Promise - - /** - * Fetches the on-chain state of all queued recovery payloads for all managed wallets and updates the local database. - * - * This is a crucial security function. It allows the WDK to be aware of any recovery attempts, including - * potentially malicious ones. It is run periodically by a background job but can be called manually to - * force an immediate refresh. - * - * @returns A promise that resolves when the update check is complete. - * @see {onQueuedPayloadsUpdate} to listen for changes discovered by this method. - */ - updateQueuedPayloads(): Promise - - /** - * Subscribes to changes in the list of queued recovery payloads for a specific wallet or all wallets. - * - * This is the primary method for building a UI that monitors pending recovery actions. The callback is fired - * whenever `updateQueuedPayloads` detects a change in the on-chain state. - * - * @param wallet (Optional) The address of a specific wallet to monitor. If omitted, the callback will receive - * updates for all managed wallets. - * @param cb The callback function to execute with the updated list of `QueuedRecoveryPayload` objects. - * @param trigger (Optional) If `true`, the callback is immediately invoked with the current state. - * @returns A function that, when called, unsubscribes the listener. - */ - onQueuedPayloadsUpdate( - wallet: Address.Address | undefined, - cb: (payloads: QueuedRecoveryPayload[]) => void, - trigger?: boolean, - ): () => void - - /** - * Fetches all queued recovery payloads for a specific wallet from the on-chain recovery contract. - * - * This method queries the Recovery contract across all configured networks to discover queued payloads - * that were initiated by any of the wallet's recovery signers. It checks each recovery signer on each - * network and retrieves all their queued payloads, including metadata such as timestamps and execution status. - * - * Unlike `updateQueuedPayloads`, this method only fetches data for a single wallet and does not update - * the local database. It's primarily used internally by `updateQueuedPayloads` but can be called directly - * for real-time queries without affecting the cached state. - * - * @param wallet The address of the wallet to fetch queued payloads for. - * @returns A promise that resolves to an array of `QueuedRecoveryPayload` objects representing all - * currently queued recovery actions for the specified wallet across all networks. - * @see {QueuedRecoveryPayload} for details on the returned object structure. - * @see {updateQueuedPayloads} for the method that fetches payloads for all wallets and updates the database. - */ - fetchQueuedPayloads(wallet: Address.Address): Promise -} - -export class Recovery implements RecoveryInterface { - constructor(private readonly shared: Shared) {} - - initialize() { - this.shared.modules.cron.registerJob( - 'update-queued-recovery-payloads', - 5 * 60 * 1000, // 5 minutes - async () => { - this.shared.modules.logger.log('Running job: update-queued-recovery-payloads') - await this.updateQueuedPayloads() - }, - ) - this.shared.modules.logger.log('Recovery module initialized and job registered.') - } - - private async updateRecoveryModule( - modules: Module[], - transformer: (leaves: Extensions.Recovery.RecoveryLeaf[]) => Extensions.Recovery.RecoveryLeaf[], - ) { - const ext = this.shared.sequence.extensions.recovery - const idx = modules.findIndex((m) => Address.isEqual(m.sapientLeaf.address, ext)) - if (idx === -1) { - return - } - - const recoveryModule = modules[idx] - if (!recoveryModule) { - throw new Error('recovery-module-not-found') - } - - const genericTree = await this.shared.sequence.stateProvider.getTree(recoveryModule.sapientLeaf.imageHash) - if (!genericTree) { - throw new Error('recovery-module-tree-not-found') - } - - const tree = Extensions.Recovery.fromGenericTree(genericTree) - const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(tree) - if (!isComplete) { - throw new Error('recovery-module-tree-incomplete') - } - - const nextTree = Extensions.Recovery.fromRecoveryLeaves(transformer(leaves)) - const nextGeneric = Extensions.Recovery.toGenericTree(nextTree) - await this.shared.sequence.stateProvider.saveTree(nextGeneric) - if (!modules[idx]) { - throw new Error('recovery-module-not-found-(unreachable)') - } - - modules[idx].sapientLeaf.imageHash = GenericTree.hash(nextGeneric) - } - - public async initRecoveryModule(modules: Module[], address: Address.Address) { - if (this.hasRecoveryModule(modules)) { - throw new Error('recovery-module-already-initialized') - } - - const recoveryTree = Extensions.Recovery.fromRecoveryLeaves([ - { - type: 'leaf' as const, - signer: address, - requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime, - minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp, - }, - ]) - - const recoveryGenericTree = Extensions.Recovery.toGenericTree(recoveryTree) - await this.shared.sequence.stateProvider.saveTree(recoveryGenericTree) - - const recoveryImageHash = GenericTree.hash(recoveryGenericTree) - - modules.push({ - sapientLeaf: { - type: 'sapient-signer', - address: this.shared.sequence.extensions.recovery, - weight: 255n, - imageHash: recoveryImageHash, - }, - weight: 255n, - }) - } - - hasRecoveryModule(modules: Module[]): boolean { - return modules.some((m) => Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.recovery)) - } - - async addRecoverySignerToModules(modules: Module[], address: Address.Address) { - if (!this.hasRecoveryModule(modules)) { - throw new Error('recovery-module-not-enabled') - } - - await this.updateRecoveryModule(modules, (leaves) => { - if (leaves.some((l) => Address.isEqual(l.signer, address))) { - return leaves - } - - const filtered = leaves.filter((l) => !Address.isEqual(l.signer, Constants.ZeroAddress)) - - return [ - ...filtered, - { - type: 'leaf', - signer: address, - requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime, - minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp, - }, - ] - }) - } - - async removeRecoverySignerFromModules(modules: Module[], address: Address.Address) { - if (!this.hasRecoveryModule(modules)) { - throw new Error('recovery-module-not-enabled') - } - - await this.updateRecoveryModule(modules, (leaves) => { - const next = leaves.filter((l) => !Address.isEqual(l.signer, address)) - if (next.length === 0) { - return [ - { - type: 'leaf', - signer: Constants.ZeroAddress, - requiredDeltaTime: 0n, - minTimestamp: 0n, - }, - ] - } - - return next - }) - } - - async addMnemonic(wallet: Address.Address, mnemonic: string) { - const signer = MnemonicHandler.toSigner(mnemonic) - if (!signer) { - throw new Error('invalid-mnemonic') - } - - await signer.witness(this.shared.sequence.stateProvider, wallet, { - isForRecovery: true, - signerKind: Kinds.LoginMnemonic, - }) - - return this.addSigner(wallet, signer.address) - } - - async addSigner(wallet: Address.Address, address: Address.Address) { - const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet) - await this.addRecoverySignerToModules(modules, address) - return this.shared.modules.wallets.requestConfigurationUpdate( - wallet, - { - modules, - }, - Actions.AddRecoverySigner, - 'wallet-webapp', - ) - } - - async removeSigner(wallet: Address.Address, address: Address.Address) { - const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet) - await this.removeRecoverySignerFromModules(modules, address) - return this.shared.modules.wallets.requestConfigurationUpdate( - wallet, - { modules }, - Actions.RemoveRecoverySigner, - 'wallet-webapp', - ) - } - - async completeUpdate(requestId: string) { - const request = await this.shared.modules.signatures.get(requestId) - if (request.action !== 'add-recovery-signer' && request.action !== 'remove-recovery-signer') { - throw new Error('invalid-recovery-update-action') - } - - return this.shared.modules.wallets.completeConfigurationUpdate(requestId) - } - - async getSigners(address: Address.Address): Promise { - const { raw } = await this.shared.modules.wallets.getConfiguration(address) - const recoveryModule = raw.modules.find((m) => - Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.recovery), - ) - if (!recoveryModule) { - return undefined - } - - const recoveryGenericTree = await this.shared.sequence.stateProvider.getTree(recoveryModule.sapientLeaf.imageHash) - if (!recoveryGenericTree) { - throw new Error('recovery-module-tree-not-found') - } - - const recoveryTree = Extensions.Recovery.fromGenericTree(recoveryGenericTree) - const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(recoveryTree) - if (!isComplete) { - throw new Error('recovery-module-tree-incomplete') - } - - const kos = await this.shared.modules.signers.resolveKinds( - address, - leaves.map((l) => l.signer), - ) - - return leaves - .filter((l) => !Address.isEqual(l.signer, Constants.ZeroAddress)) - .map((l) => ({ - address: l.signer, - kind: kos.find((s) => Address.isEqual(s.address, l.signer))?.kind || 'unknown', - isRecovery: true, - minTimestamp: l.minTimestamp, - requiredDeltaTime: l.requiredDeltaTime, - })) - } - - async queuePayload(wallet: Address.Address, chainId: number, payload: Payload.Calls) { - const signers = await this.getSigners(wallet) - if (!signers) { - throw new Error('recovery-signers-not-found') - } - - const recoveryPayload = Payload.toRecovery(payload) - const simulatedTopology = Config.flatLeavesToTopology( - signers.map((s) => ({ - type: 'signer', - address: s.address, - weight: 1n, - })), - ) - - // Save both versions of the payload in parallel - await Promise.all([ - this.shared.sequence.stateProvider.savePayload(wallet, payload, chainId), - this.shared.sequence.stateProvider.savePayload(wallet, recoveryPayload, chainId), - ]) - - const requestId = await this.shared.modules.signatures.request( - { - wallet, - chainId, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: simulatedTopology, - }, - payload: recoveryPayload, - }, - 'recovery', - ) - - return requestId - } - - // TODO: Handle this transaction instead of just returning the to and data - async completePayload(requestId: string): Promise<{ to: Address.Address; data: Hex.Hex }> { - const signature = await this.shared.modules.signatures.get(requestId) - if (signature.action !== 'recovery' || !Payload.isRecovery(signature.envelope.payload)) { - throw new Error('invalid-recovery-payload') - } - - if (!Envelope.isSigned(signature.envelope)) { - throw new Error('recovery-payload-not-signed') - } - - const { weight, threshold } = Envelope.weightOf(signature.envelope) - if (weight < threshold) { - throw new Error('recovery-payload-insufficient-weight') - } - - // Find any valid signature - const validSignature = signature.envelope.signatures[0] - if (Envelope.isSapientSignature(validSignature)) { - throw new Error('recovery-payload-sapient-signatures-not-supported') - } - - if (!validSignature) { - throw new Error('recovery-payload-no-valid-signature') - } - - const calldata = Extensions.Recovery.encodeCalldata( - signature.wallet, - signature.envelope.payload, - validSignature.address, - validSignature.signature, - ) - - return { - to: this.shared.sequence.extensions.recovery, - data: calldata, - } - } - - async getQueuedRecoveryPayloads(wallet?: Address.Address, chainId?: number): Promise { - // If no wallet is provided, always use the database - if (!wallet) { - return this.shared.databases.recovery.list() - } - - // If the wallet is logged in, then we can expect to have all the payloads in the database - // because the cronjob keeps it updated - if (await this.shared.modules.wallets.get(wallet)) { - const all = await this.shared.databases.recovery.list() - return all.filter((p) => Address.isEqual(p.wallet, wallet)) - } - - // If not, then we must fetch them from the chain - return this.fetchQueuedPayloads(wallet, chainId) - } - - onQueuedPayloadsUpdate( - wallet: Address.Address | undefined, - cb: (payloads: QueuedRecoveryPayload[]) => void, - trigger?: boolean, - ) { - if (trigger) { - this.getQueuedRecoveryPayloads(wallet).then(cb) - } - - return this.shared.databases.recovery.addListener(() => { - this.getQueuedRecoveryPayloads(wallet).then(cb) - }) - } - - async updateQueuedPayloads(): Promise { - const wallets = await this.shared.modules.wallets.list() - - for (const wallet of wallets) { - const payloads = await this.fetchQueuedPayloads(wallet.address) - for (const payload of payloads) { - await this.shared.databases.recovery.set(payload) - } - - // Delete any unseen queued payloads as they are no longer relevant - const seenInThisRun = new Set(payloads.map((p) => p.id)) - const allQueuedPayloads = await this.shared.databases.recovery.list() - for (const payload of allQueuedPayloads) { - if (!seenInThisRun.has(payload.id)) { - await this.shared.databases.recovery.del(payload.id) - } - } - } - } - - async fetchQueuedPayloads(wallet: Address.Address, chainId?: number): Promise { - // Create providers for each network - const providers = this.shared.sequence.networks - .filter((network) => - chainId - ? network.chainId === chainId - : !this.shared.sequence.defaultRecoverySettings.includeTestnets - ? network.type !== 'testnet' - : true, - ) - .map((network) => ({ - chainId: network.chainId, - multicall3Address: network.contracts?.multicall3, - provider: Provider.from(RpcTransport.fromHttp(network.rpcUrl)), - })) - - // See if they have any recover signers - const signers = await this.getSigners(wallet) - if (!signers || signers.length === 0) { - return [] - } - - const recoveryExtension = this.shared.sequence.extensions.recovery - const payloads: QueuedRecoveryPayload[] = [] - - await Promise.all( - providers.map(async ({ chainId, provider, multicall3Address }) => { - try { - let totalPayloadsBySigner: bigint[] - - if (multicall3Address) { - try { - // Batch all totalQueuedPayloads calls for every signer into a single Multicall3 request. - // This reduces N signer calls per network down to 1 call per network. - totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsBatched( - provider, - recoveryExtension, - wallet, - signers, - multicall3Address, - ) - } catch (err) { - console.error( - `Recovery.fetchQueuedPayloads multicall3 failed for chainId ${chainId}, retrying with individual calls:`, - err, - ) - totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback( - provider, - recoveryExtension, - wallet, - signers, - ) - } - } else { - totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback( - provider, - recoveryExtension, - wallet, - signers, - ) - } - - for (let s = 0; s < signers.length; s++) { - const signer = signers[s]! - const totalPayloads = totalPayloadsBySigner[s]! - if (totalPayloads === 0n) continue - - // Only make individual calls for the rare case where payloads actually exist - for (let i = 0n; i < totalPayloads; i++) { - const payloadHash = await Extensions.Recovery.queuedPayloadHashOf( - provider, - recoveryExtension, - wallet, - signer.address, - i, - ) - - const timestamp = await Extensions.Recovery.timestampForQueuedPayload( - provider, - recoveryExtension, - wallet, - signer.address, - payloadHash, - ) - - const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash) - - // If ready, we need to check if it was executed already - // for this, we check if the wallet nonce for the given space - // is greater than the nonce in the payload - if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) { - const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet, payload.payload.space) - if (nonce > i) { - continue - } - } - - // The id is the index + signer address + chainId + wallet address - const id = `${i}-${signer.address}-${chainId}-${wallet}` - - const payloadEntry: QueuedRecoveryPayload = { - id, - index: i, - recoveryModule: recoveryExtension, - wallet: wallet, - signer: signer.address, - chainId, - startTimestamp: timestamp, - endTimestamp: timestamp + signer.requiredDeltaTime, - payloadHash, - payload: payload?.payload, - } - - payloads.push(payloadEntry) - } - } - } catch (err) { - console.error(`Recovery.fetchQueuedPayloads error for chainId ${chainId}:`, err) - } - }), - ) - - return payloads - } - - private async fetchTotalQueuedPayloadsBatched( - provider: Provider.Provider, - recoveryExtension: Address.Address, - wallet: Address.Address, - signers: RecoverySigner[], - multicall3Address: Address.Address, - ): Promise { - const calls = signers.map((signer) => ({ - target: recoveryExtension, - allowFailure: true, - callData: AbiFunction.encodeData(Extensions.Recovery.TOTAL_QUEUED_PAYLOADS, [wallet, signer.address]), - })) - - const response = await provider.request({ - method: 'eth_call', - params: [ - { - to: multicall3Address, - data: AbiFunction.encodeData(AGGREGATE3, [calls]), - }, - 'latest', - ], - }) - - const results = AbiFunction.decodeResult(AGGREGATE3, response) as readonly { - success: boolean - returnData: Hex.Hex - }[] - - return results.map((result) => { - if (!result.success || result.returnData === '0x') { - return 0n - } - return Hex.toBigInt(result.returnData) - }) - } - - private fetchTotalQueuedPayloadsFallback = async ( - provider: Provider.Provider, - recoveryExtension: Address.Address, - wallet: Address.Address, - signers: RecoverySigner[], - ): Promise => { - const result: bigint[] = signers.map(() => 0n) - - // Fallback to individual calls if the multicall3 call fails - for (let s = 0; s < signers.length; s++) { - const signer = signers[s]! - const totalPayloads = await Extensions.Recovery.totalQueuedPayloads( - provider, - recoveryExtension, - wallet, - signer.address, - ) - result[s] = totalPayloads - } - - return result - } - - async encodeRecoverySignature(imageHash: Hex.Hex, signer: Address.Address) { - const genericTree = await this.shared.sequence.stateProvider.getTree(imageHash) - if (!genericTree) { - throw new Error('recovery-module-tree-not-found') - } - - const tree = Extensions.Recovery.fromGenericTree(genericTree) - const allSigners = Extensions.Recovery.getRecoveryLeaves(tree).leaves.map((l) => l.signer) - - if (!allSigners.includes(signer)) { - throw new Error('signer-not-found-in-recovery-module') - } - - const trimmed = Extensions.Recovery.trimTopology(tree, signer) - return Extensions.Recovery.encodeTopology(trimmed) - } -} diff --git a/packages/wallet/wdk/src/sequence/sessions.ts b/packages/wallet/wdk/src/sequence/sessions.ts deleted file mode 100644 index 198258279f..0000000000 --- a/packages/wallet/wdk/src/sequence/sessions.ts +++ /dev/null @@ -1,561 +0,0 @@ -import { IdentityType } from '@0xsequence/identity-instrument' -import { Envelope, type ExplicitSession } from '@0xsequence/wallet-core' -import { - Attestation, - Config, - GenericTree, - Payload, - Signature as SequenceSignature, - SessionConfig, -} from '@0xsequence/wallet-primitives' -import { Address, Bytes, Hash, Hex } from 'ox' -import { AuthCodeHandler } from './handlers/authcode.js' -import { AuthCodePkceHandler } from './handlers/authcode-pkce.js' -import { IdTokenHandler } from './handlers/idtoken.js' -import { IdentityHandler, identityTypeToHex } from './handlers/identity.js' -import { Handler } from './handlers/index.js' -import { ManagerOptionsDefaults, Shared } from './manager.js' -import { Kinds, Module } from './types/index.js' -import { AuthorizeImplicitSessionArgs } from './types/sessions.js' -import { Actions } from './types/signature-request.js' - -export interface SessionsInterface { - /** - * Retrieves the raw, detailed session topology for a given wallet. - * - * The session topology is a tree-like data structure that defines all session-related configurations for a wallet. - * This includes the identity signer (the primary credential that authorizes sessions), the list of explicit - * session keys with their permissions, and the blacklist of contracts forbidden from using implicit sessions. - * - * This method is useful for inspecting the low-level structure of the sessions extension. - * - * @param walletAddress The on-chain address of the wallet. - * @returns A promise that resolves to the wallet's `SessionsTopology` object. - * @throws An error if the wallet is not configured with a session manager or if the topology cannot be found. - */ - getTopology(walletAddress: Address.Address): Promise - - /** - * Initiates the authorization of an "implicit session". - * - * An implicit session allows a temporary key (`sessionAddress`) to sign on behalf of the wallet for specific, - * pre-approved smart contracts without requiring an on-chain configuration change. This is achieved by having the - * wallet's primary identity signer (e.g., a passkey, or the identity instrument) sign an "attestation". - * - * This method prepares the attestation and creates a signature request for the identity signer. - * The returned `requestId` must be used to get the signature from the user. - * - * @param walletAddress The address of the wallet authorizing the session. - * @param sessionAddress The address of the temporary key that will become the implicit session signer. - * @param args The authorization arguments. - * @param args.target A string, typically a URL, identifying the application or service (the "audience") - * that is being granted this session. This is a critical security parameter. - * @param args.applicationData (Optional) Extra data that can be included in the attestation. - * @returns A promise that resolves to a `requestId` for the signature request. - * @see {completeAuthorizeImplicitSession} to finalize the process after signing. - */ - prepareAuthorizeImplicitSession( - walletAddress: Address.Address, - sessionAddress: Address.Address, - args: AuthorizeImplicitSessionArgs, - ): Promise - - /** - * Completes the authorization of an implicit session. - * - * This method should be called after the signature request from `prepareAuthorizeImplicitSession` has been - * fulfilled by the user's identity signer. It finalizes the process and returns the signed attestation. - * - * The returned attestation and its signature are the credentials needed to initialize an `Implicit` - * session signer, which can then be used by a dapp to interact with approved contracts. - * - * @param requestId The unique ID of the signature request returned by `prepareAuthorizeImplicitSession`. - * @returns A promise that resolves to an object containing the signed `attestation` and the `signature` from the identity signer. - * @throws An error if the signature request is not found or has not been successfully signed. - */ - completeAuthorizeImplicitSession(requestId: string): Promise<{ - attestation: Attestation.Attestation - signature: SequenceSignature.RSY - }> - - /** - * Initiates an on-chain configuration update to add an "explicit session". - * - * An explicit session grants a specified key (`sessionAddress`) on-chain signing rights for the - * wallet, constrained by a set of defined permissions. This gives the session key the ability to send - * transactions on the wallet's behalf as long as they comply with the rules. - * - * This process is more powerful than creating an implicit session but requires explicit authorization. - * This method prepares the configuration update and returns a `requestId` that must be signed and then - * completed using the `complete` method. - * - * @param walletAddress The address of the wallet to modify. - * @param permissions The set of rules and limits that will govern this session key's capabilities. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {complete} to finalize the update after it has been signed. - */ - addExplicitSession(walletAddress: Address.Address, explicitSession: ExplicitSession): Promise - - /** - * Initiates an on-chain configuration update to modify an existing "explicit session". - * - * This method atomically replaces the permissions for a given session key. If the session - * key does not already exist, it will be added. This is the recommended way to update - * permissions for an active session. - * - * Like adding a session, this requires a signed configuration update. - * - * @param walletAddress The address of the wallet to modify. - * @param permissions The new, complete set of rules and limits for this session key. - * @param origin Optional string to identify the source of the request. - * @returns A promise that resolves to a `requestId` for the configuration update. - * @see {complete} to finalize the update after it has been signed. - */ - modifyExplicitSession( - walletAddress: Address.Address, - explicitSession: ExplicitSession, - origin?: string, - ): Promise - - /** - * Initiates an on-chain configuration update to remove an explicit session key. - * - * This revokes all on-chain permissions for the specified `sessionAddress`, effectively disabling it. - * Like adding a session, this requires a signed configuration update. - * - * @param walletAddress The address of the wallet to modify. - * @param sessionAddress The address of the session signer to remove. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {complete} to finalize the update after it has been signed. - */ - removeExplicitSession(walletAddress: Address.Address, sessionAddress: Address.Address): Promise - - /** - * Initiates an on-chain configuration update to add a contract address to the implicit session blacklist. - * - * Once blacklisted, a contract cannot be the target of transactions signed by any implicit session key for this wallet. - * - * @param walletAddress The address of the wallet to modify. - * @param address The contract address to add to the blacklist. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {complete} to finalize the update after it has been signed. - */ - addBlacklistAddress(walletAddress: Address.Address, address: Address.Address): Promise - - /** - * Initiates an on-chain configuration update to remove a contract address from the implicit session blacklist. - * - * @param walletAddress The address of the wallet to modify. - * @param address The contract address to remove from the blacklist. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {complete} to finalize the update after it has been signed. - */ - removeBlacklistAddress(walletAddress: Address.Address, address: Address.Address): Promise - - /** - * Finalizes and saves a pending session configuration update. - * - * This method should be called after a signature request generated by `addExplicitSession`, - * `removeExplicitSession`, `addBlacklistAddress`, or `removeBlacklistAddress` has been - * successfully signed and has met its weight threshold. It takes the signed configuration - * and saves it to the state provider, making it the new pending configuration for the wallet. - * The next regular transaction will then automatically include this update. - * - * **Important:** Calling any of the four modification methods (`addExplicitSession`, etc.) will - * automatically cancel any other pending configuration update for the same wallet. This is to - * prevent conflicts and ensure only the most recent intended state is applied. For example, if you - * call `addExplicitSession` and then `removeExplicitSession` before completing the first request, - * the first signature request will be cancelled, and only the second one will remain active. - * - * @param requestId The unique ID of the fulfilled signature request. - * @returns A promise that resolves when the update has been successfully processed and saved. - * @throws An error if the request is not a 'session-update' action, is not found, or has insufficient signatures. - */ - complete(requestId: string): Promise -} - -export class Sessions implements SessionsInterface { - constructor(private readonly shared: Shared) {} - - async getTopology(walletAddress: Address.Address, fixMissing = false): Promise { - const { loginTopology, devicesTopology, modules } = - await this.shared.modules.wallets.getConfigurationParts(walletAddress) - const managerModule = modules.find((m) => - Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.sessions), - ) - if (!managerModule) { - if (fixMissing) { - // Create the default session manager leaf - const authorizedSigners = [...Config.topologyToFlatLeaves([devicesTopology, loginTopology])].filter( - Config.isSignerLeaf, - ) - if (authorizedSigners.length === 0) { - throw new Error('No signer leaves found') - } - let sessionsTopology = SessionConfig.emptySessionsTopology(authorizedSigners[0]!.address) - for (let i = 1; i < authorizedSigners.length; i++) { - sessionsTopology = SessionConfig.addIdentitySigner(sessionsTopology, authorizedSigners[i]!.address) - } - const sessionsConfigTree = SessionConfig.sessionsTopologyToConfigurationTree(sessionsTopology) - this.shared.sequence.stateProvider.saveTree(sessionsConfigTree) - const imageHash = GenericTree.hash(sessionsConfigTree) - const leaf: Config.SapientSignerLeaf = { - ...ManagerOptionsDefaults.defaultSessionsTopology, - address: this.shared.sequence.extensions.sessions, - imageHash, - } - modules.push({ - sapientLeaf: leaf, - weight: 255n, - }) - return SessionConfig.configurationTreeToSessionsTopology(sessionsConfigTree) - } - throw new Error('Session manager not found') - } - const imageHash = managerModule.sapientLeaf.imageHash - const tree = await this.shared.sequence.stateProvider.getTree(imageHash) - if (!tree) { - throw new Error('Session topology not found') - } - return SessionConfig.configurationTreeToSessionsTopology(tree) - } - - private async updateSessionModule( - modules: Module[], - transformer: (topology: SessionConfig.SessionsTopology) => SessionConfig.SessionsTopology, - ) { - const ext = this.shared.sequence.extensions.sessions - const idx = modules.findIndex((m) => Address.isEqual(m.sapientLeaf.address, ext)) - if (idx === -1) { - return - } - - const sessionModule = modules[idx] - if (!sessionModule) { - throw new Error('session-module-not-found') - } - - const genericTree = await this.shared.sequence.stateProvider.getTree(sessionModule.sapientLeaf.imageHash) - if (!genericTree) { - throw new Error('session-module-tree-not-found') - } - - const topology = SessionConfig.configurationTreeToSessionsTopology(genericTree) - const nextTopology = transformer(topology) - const nextTree = SessionConfig.sessionsTopologyToConfigurationTree(nextTopology) - await this.shared.sequence.stateProvider.saveTree(nextTree) - if (!modules[idx]) { - throw new Error('session-module-not-found-(unreachable)') - } - - modules[idx].sapientLeaf.imageHash = GenericTree.hash(nextTree) - } - - hasSessionModule(modules: Module[]): boolean { - return modules.some((m) => Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.sessions)) - } - - async initSessionModule(modules: Module[], identitySigners: Address.Address[], guardTopology?: Config.Topology) { - if (this.hasSessionModule(modules)) { - throw new Error('session-module-already-initialized') - } - - if (identitySigners.length === 0) { - throw new Error('No identity signers provided') - } - - // Calculate image hash with the identity signers - const sessionsTopology = SessionConfig.emptySessionsTopology( - identitySigners as [Address.Address, ...Address.Address[]], - ) - // Store this tree in the state provider - const sessionsConfigTree = SessionConfig.sessionsTopologyToConfigurationTree(sessionsTopology) - this.shared.sequence.stateProvider.saveTree(sessionsConfigTree) - // Prepare the configuration leaf - const sessionsImageHash = GenericTree.hash(sessionsConfigTree) - const signer = { - ...ManagerOptionsDefaults.defaultSessionsTopology, - address: this.shared.sequence.extensions.sessions, - imageHash: sessionsImageHash, - } - modules.push({ - sapientLeaf: signer, - weight: 255n, - guardLeaf: guardTopology, - }) - } - - async addIdentitySignerToModules(modules: Module[], address: Address.Address) { - if (!this.hasSessionModule(modules)) { - throw new Error('session-module-not-enabled') - } - - await this.updateSessionModule(modules, (topology) => { - const existingSigners = SessionConfig.getIdentitySigners(topology) - if (existingSigners?.some((s) => Address.isEqual(s, address))) { - return topology - } - - return SessionConfig.addIdentitySigner(topology, address) - }) - } - - async removeIdentitySignerFromModules(modules: Module[], address: Address.Address) { - if (!this.hasSessionModule(modules)) { - throw new Error('session-module-not-enabled') - } - - await this.updateSessionModule(modules, (topology) => { - const newTopology = SessionConfig.removeIdentitySigner(topology, address) - if (!newTopology) { - // Can't remove the last identity signer - throw new Error('Cannot remove the last identity signer') - } - return newTopology - }) - } - - async prepareAuthorizeImplicitSession( - walletAddress: Address.Address, - sessionAddress: Address.Address, - args: AuthorizeImplicitSessionArgs, - ): Promise { - const topology = await this.getTopology(walletAddress) - const identitySigners = SessionConfig.getIdentitySigners(topology) - if (identitySigners.length === 0) { - throw new Error('No identity signers found') - } - let handler: Handler | undefined - let identitySignerAddress: Address.Address | undefined - for (const identitySigner of identitySigners) { - const identityKind = await this.shared.modules.signers.kindOf(walletAddress, identitySigner) - if (!identityKind) { - console.warn('No identity handler kind found for', identitySigner) - continue - } - if (identityKind === Kinds.LoginPasskey) { - console.warn('Implicit sessions do not support passkeys', identitySigner) - continue - } - const iHandler = this.shared.handlers.get(identityKind) - if (!iHandler) { - continue - } - if (identityKind === Kinds.LocalDevice) { - const hasLocalDevice = await this.shared.modules.devices.has(identitySigner) - if (!hasLocalDevice) { - console.warn('Identity signer not on this device, skipping', identitySigner) - continue - } - } - - handler = iHandler - identitySignerAddress = identitySigner - break - } - - if (!handler || !identitySignerAddress) { - throw new Error('No identity handler or address found') - } - - // Create the attestation to sign - let identityType: IdentityType | undefined - let issuerHash: Hex.Hex = '0x' - let audienceHash: Hex.Hex = '0x' - if (handler instanceof IdentityHandler) { - identityType = handler.identityType - if ( - handler instanceof AuthCodeHandler || - handler instanceof AuthCodePkceHandler || - handler instanceof IdTokenHandler - ) { - issuerHash = Hash.keccak256(Hex.fromString(handler.issuer)) - audienceHash = Hash.keccak256(Hex.fromString(handler.audience)) - } - } - const attestation: Attestation.Attestation = { - approvedSigner: sessionAddress, - identityType: Bytes.fromHex(identityTypeToHex(identityType), { size: 4 }), - issuerHash: Bytes.fromHex(issuerHash, { size: 32 }), - audienceHash: Bytes.fromHex(audienceHash, { size: 32 }), - applicationData: Bytes.fromHex(args.applicationData ?? '0x'), - authData: { - redirectUrl: args.target, - issuedAt: BigInt(Math.floor(Date.now() / 1000)), - }, - } - // Fake the configuration with the single required signer - const configuration: Config.Config = { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'signer', - address: identitySignerAddress, - weight: 1n, - }, - } - const envelope: Envelope.Envelope = { - payload: { - type: 'session-implicit-authorize', - sessionAddress, - attestation, - }, - wallet: walletAddress, - chainId: 0, - configuration, - } - - // Request the signature from the identity handler - return this.shared.modules.signatures.request(envelope, 'session-implicit-authorize', { - origin: args.target, - }) - } - - async completeAuthorizeImplicitSession(requestId: string): Promise<{ - attestation: Attestation.Attestation - signature: SequenceSignature.RSY - }> { - // Get the updated signature request - const signatureRequest = await this.shared.modules.signatures.get(requestId) - if ( - signatureRequest.action !== 'session-implicit-authorize' || - !Payload.isSessionImplicitAuthorize(signatureRequest.envelope.payload) - ) { - throw new Error('Invalid action') - } - - if (!Envelope.isSigned(signatureRequest.envelope) || !Envelope.reachedThreshold(signatureRequest.envelope)) { - throw new Error('Envelope not signed or threshold not reached') - } - - // Find any valid signature - const signature = signatureRequest.envelope.signatures[0] - if (!signature || !Envelope.isSignature(signature)) { - throw new Error('No valid signature found') - } - if (signature.signature.type !== 'hash') { - // Should never happen - throw new Error('Unsupported signature type') - } - - await this.shared.modules.signatures.complete(requestId) - - return { - attestation: signatureRequest.envelope.payload.attestation, - signature: signature.signature, - } - } - - async addExplicitSession( - walletAddress: Address.Address, - explicitSession: ExplicitSession, - origin?: string, - ): Promise { - const topology = await this.getTopology(walletAddress, true) - const newTopology = SessionConfig.addExplicitSession(topology, { - ...explicitSession, - signer: explicitSession.sessionAddress, - }) - return this.prepareSessionUpdate(walletAddress, newTopology, origin) - } - - async modifyExplicitSession( - walletAddress: Address.Address, - explicitSession: ExplicitSession, - origin?: string, - ): Promise { - // This will add the session manager if it's missing - const topology = await this.getTopology(walletAddress, true) - const intermediateTopology = SessionConfig.removeExplicitSession(topology, explicitSession.sessionAddress) - if (!intermediateTopology) { - throw new Error('Incomplete session topology') - } - const newTopology = SessionConfig.addExplicitSession(intermediateTopology, { - ...explicitSession, - signer: explicitSession.sessionAddress, - }) - return this.prepareSessionUpdate(walletAddress, newTopology, origin) - } - - async removeExplicitSession( - walletAddress: Address.Address, - sessionAddress: Address.Address, - origin?: string, - ): Promise { - const topology = await this.getTopology(walletAddress) - const newTopology = SessionConfig.removeExplicitSession(topology, sessionAddress) - if (!newTopology) { - throw new Error('Incomplete session topology') - } - return this.prepareSessionUpdate(walletAddress, newTopology, origin) - } - - async addBlacklistAddress( - walletAddress: Address.Address, - address: Address.Address, - origin?: string, - ): Promise { - const topology = await this.getTopology(walletAddress, true) - const newTopology = SessionConfig.addToImplicitBlacklist(topology, address) - return this.prepareSessionUpdate(walletAddress, newTopology, origin) - } - - async removeBlacklistAddress( - walletAddress: Address.Address, - address: Address.Address, - origin?: string, - ): Promise { - const topology = await this.getTopology(walletAddress) - const newTopology = SessionConfig.removeFromImplicitBlacklist(topology, address) - return this.prepareSessionUpdate(walletAddress, newTopology, origin) - } - - private async prepareSessionUpdate( - walletAddress: Address.Address, - topology: SessionConfig.SessionsTopology, - origin: string = 'wallet-webapp', - ): Promise { - // Store the new configuration - const tree = SessionConfig.sessionsTopologyToConfigurationTree(topology) - await this.shared.sequence.stateProvider.saveTree(tree) - const newImageHash = GenericTree.hash(tree) - - // Find the session manager in the old configuration - const { modules } = await this.shared.modules.wallets.getConfigurationParts(walletAddress) - const managerModule = modules.find((m) => - Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.sessions), - ) - if (!managerModule) { - // Missing. Add it - modules.push({ - sapientLeaf: { - ...ManagerOptionsDefaults.defaultSessionsTopology, - address: this.shared.sequence.extensions.sessions, - imageHash: newImageHash, - }, - weight: 255n, - }) - } else { - // Update the configuration to use the new session manager image hash - managerModule.sapientLeaf.imageHash = newImageHash - } - - return this.shared.modules.wallets.requestConfigurationUpdate( - walletAddress, - { - modules, - }, - Actions.SessionUpdate, - origin, - ) - } - - async complete(requestId: string) { - const sigRequest = await this.shared.modules.signatures.get(requestId) - if (sigRequest.action !== 'session-update' || !Payload.isConfigUpdate(sigRequest.envelope.payload)) { - throw new Error('Invalid action') - } - - return this.shared.modules.wallets.completeConfigurationUpdate(requestId) - } -} diff --git a/packages/wallet/wdk/src/sequence/signatures.ts b/packages/wallet/wdk/src/sequence/signatures.ts deleted file mode 100644 index 8c32f3844a..0000000000 --- a/packages/wallet/wdk/src/sequence/signatures.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { Envelope } from '@0xsequence/wallet-core' -import { Config, Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { v7 as uuidv7 } from 'uuid' -import { Shared } from './manager.js' -import { - Action, - ActionToPayload, - BaseSignatureRequest, - SignatureRequest, - SignerBase, - SignerSigned, - SignerUnavailable, -} from './types/signature-request.js' - -export interface SignaturesInterface { - /** - * Retrieves the detailed state of a specific signature request. - * - * This method returns a "fully hydrated" `SignatureRequest` object. It contains not only the - * static data about the request (like the wallet, action, and payload) but also a dynamic, - * up-to-the-moment list of all required signers and their current statuses (`ready`, `actionable`, - * `signed`, `unavailable`). This is the primary method to use when you need to display an - * interactive signing prompt to the user. - * - * @param requestId The unique identifier of the signature request to retrieve. - * @returns A promise that resolves to the detailed `SignatureRequest` object. - * @throws An error if the request is not found or if it has expired and been pruned from the database. - * @see {SignatureRequest} for the detailed structure of the returned object. - */ - get(requestId: string): Promise - - /** - * Returns a list of all signature requests across all wallets managed by this instance. - * - * This method is useful for displaying an overview of all pending and historical actions. - * The returned objects are the `SignatureRequest` type but may not be as "live" as the object from `get()`. - * For displaying an interactive UI for a specific request, it's recommended to use `get(requestId)` - * or subscribe via `onSignatureRequestUpdate` to get the most detailed and real-time state. - * - * @returns A promise that resolves to an array of `BaseSignatureRequest` objects. - */ - list(): Promise - - /** - * Cancel a specific signature request. - * - * @param requestId The ID of the request to cancel - */ - cancel(requestId: string): Promise - - /** - * Subscribes to real-time updates for a single, specific signature request. - * - * The provided callback is invoked whenever the state of the request changes. This is a powerful - * feature for building reactive UIs, as the callback fires not only when the request's database - * entry is updated (e.g., a signature is added) but also when the availability of its required - * signers changes (e.g., an auth session expires). - * - * @param requestId The ID of the signature request to monitor. - * @param cb The callback function to execute with the updated `SignatureRequest` object. - * @param onError (Optional) A callback to handle errors that may occur during the update, - * such as the request being deleted or expiring. - * @param trigger (Optional) If `true`, the callback will be immediately invoked with the current - * state of the request upon registration. - * @returns A function that, when called, will unsubscribe the listener and stop updates. - */ - onSignatureRequestUpdate( - requestId: string, - cb: (request: SignatureRequest) => void, - onError?: (error: Error) => void, - trigger?: boolean, - ): () => void - - /** - * Subscribes to updates on the list of all signature requests. - * - * The callback is fired whenever a signature request is created, updated (e.g., its status - * changes to 'completed' or 'cancelled'), or removed. This is ideal for keeping a list - * view of all signature requests synchronized. - * - * The callback receives an array of `BaseSignatureRequest` objects, which contain the core, - * static data for each request. - * - * @param cb The callback function to execute with the updated list of `BaseSignatureRequest` objects. - * @param trigger (Optional) If `true`, the callback will be immediately invoked with the current - * list of requests upon registration. - * @returns A function that, when called, will unsubscribe the listener. - */ - onSignatureRequestsUpdate(cb: (requests: BaseSignatureRequest[]) => void, trigger?: boolean): () => void - - /** - * Listen for a specific terminal status on a signature request. - * - * This provides a targeted way to handle request completion or cancellation and automatically - * disposes the listener when any terminal state is reached. - * - * @param status The terminal status to listen for ('completed' or 'cancelled'). - * @param requestId The ID of the signature request to monitor. - * @param callback Function to execute when the status is reached. - * @returns A function that, when called, will unsubscribe the listener. - * - * The listener automatically disposes after any terminal state is reached, - * ensuring no memory leaks from one-time status listeners. - * - * @example - * ```typescript - * // Listen for completion (auto-disposes when resolved) - * signatures.onSignatureRequestStatus('completed', requestId, (request) => { - * console.log('Request completed!', request) - * }) - * - * // Listen for cancellation (auto-disposes when resolved) - * signatures.onSignatureRequestStatus('cancelled', requestId, (request) => { - * console.log('Request cancelled') - * }) - * ``` - */ - onSignatureRequestStatus( - status: 'completed' | 'cancelled', - requestId: string, - callback: (request: SignatureRequest) => void, - ): () => void - - /** - * Convenience: listen for completion of a specific request. - * Disposes automatically when the request resolves (completed or cancelled). - */ - onComplete(requestId: string, callback: (request: SignatureRequest) => void): () => void - - /** - * Convenience: listen for cancellation of a specific request. - * Disposes automatically when the request resolves (completed or cancelled). - */ - onCancel(requestId: string, callback: (request: SignatureRequest) => void): () => void -} - -export class Signatures implements SignaturesInterface { - constructor(private readonly shared: Shared) {} - - initialize() { - this.shared.modules.cron.registerJob('prune-signatures', 10 * 60 * 1000, async () => { - const prunedSignatures = await this.prune() - if (prunedSignatures > 0) { - this.shared.modules.logger.log(`Pruned ${prunedSignatures} signatures`) - } - }) - this.shared.modules.logger.log('Signatures module initialized and job registered.') - } - - private async getBase(requestId: string): Promise { - const request = await this.shared.databases.signatures.get(requestId) - if (!request) { - throw new Error(`Request not found for ${requestId}`) - } - return request - } - - async list(): Promise { - return this.shared.databases.signatures.list() - } - - async get(requestId: string): Promise { - const request = await this.getBase(requestId) - - if (request.status !== 'pending' && request.scheduledPruning < Date.now()) { - await this.shared.databases.signatures.del(requestId) - throw new Error(`Request not found for ${requestId}`) - } - - const signers = Config.getSigners(request.envelope.configuration.topology) - const signersAndKinds = await Promise.all([ - ...signers.signers.map(async (signer) => { - const kind = await this.shared.modules.signers.kindOf(request.wallet, signer) - return { - address: signer, - imageHash: undefined, - kind, - } - }), - ...signers.sapientSigners.map(async (signer) => { - const kind = await this.shared.modules.signers.kindOf( - request.wallet, - signer.address, - Hex.from(signer.imageHash), - ) - return { - address: signer.address, - imageHash: signer.imageHash, - kind, - } - }), - ]) - - const statuses = await Promise.all( - signersAndKinds.map(async (sak) => { - const base: SignerBase = { - address: sak.address, - imageHash: sak.imageHash, - } - - // We may have a signature for this signer already - const signed = request.envelope.signatures.some((sig) => { - if (Envelope.isSapientSignature(sig)) { - return Address.isEqual(sig.signature.address, sak.address) && sig.imageHash === sak.imageHash - } - return Address.isEqual(sig.address, sak.address) - }) - - if (!sak.kind) { - const status: SignerUnavailable = { - ...base, - handler: undefined, - reason: 'unknown-signer-kind', - status: 'unavailable', - } - return status - } - - const handler = this.shared.handlers.get(sak.kind) - if (signed) { - const status: SignerSigned = { - ...base, - handler, - status: 'signed', - } - return status - } - - if (!handler) { - const status: SignerUnavailable = { - ...base, - handler: undefined, - reason: 'no-handler', - status: 'unavailable', - } - return status - } - - return handler.status(sak.address, sak.imageHash, request) - }), - ) - - const signatureRequest: SignatureRequest = { - ...request, - ...Envelope.weightOf(request.envelope), - signers: statuses, - } - return signatureRequest - } - - onSignatureRequestUpdate( - requestId: string, - cb: (requests: SignatureRequest) => void, - onError?: (error: Error) => void, - trigger?: boolean, - ) { - const undoDbListener = this.shared.databases.signatures.addListener(() => { - this.get(requestId) - .then((request) => cb(request)) - .catch((error) => onError?.(error)) - }) - - const undoHandlerListeners = Array.from(this.shared.handlers.values()).map((handler) => - handler.onStatusChange(() => { - this.get(requestId) - .then((request) => cb(request)) - .catch((error) => onError?.(error)) - }), - ) - - if (trigger) { - this.get(requestId) - .then((request) => cb(request)) - .catch((error) => onError?.(error)) - } - - return () => { - undoDbListener() - undoHandlerListeners.forEach((undoFn) => undoFn()) - } - } - - onSignatureRequestsUpdate(cb: (requests: BaseSignatureRequest[]) => void, trigger?: boolean) { - const undo = this.shared.databases.signatures.addListener(() => { - this.list().then((l) => cb(l)) - }) - - if (trigger) { - this.list().then((l) => cb(l)) - } - - return undo - } - - onSignatureRequestStatus( - status: 'completed' | 'cancelled', - requestId: string, - callback: (request: SignatureRequest) => void, - ): () => void { - let disposed = false - - const unsubscribe = this.onSignatureRequestUpdate( - requestId, - (request) => { - if (disposed) return - - const currentStatus = request.status - - // Check if we've reached a terminal state - if (currentStatus === 'completed' || currentStatus === 'cancelled') { - // Fire callback if this is the status we're listening for - if (currentStatus === status) { - callback(request) - } - - // Always dispose after any terminal state is reached - disposed = true - setTimeout(() => unsubscribe(), 0) // Dispose after callback completes - } - }, - undefined, // No error callback needed - false, // Don't trigger immediately - ) - - return () => { - disposed = true - unsubscribe() - } - } - - onComplete(requestId: string, callback: (request: SignatureRequest) => void): () => void { - return this.onSignatureRequestStatus('completed', requestId, callback) - } - - onCancel(requestId: string, callback: (request: SignatureRequest) => void): () => void { - return this.onSignatureRequestStatus('cancelled', requestId, callback) - } - - async complete(requestId: string) { - const request = await this.getBase(requestId) - - if (request?.envelope.payload.type === 'config-update') { - // Clear pending config updates for the same wallet with a checkpoint equal or lower than the completed update - const pendingRequests = await this.shared.databases.signatures.list() - const pendingConfigUpdatesToClear = pendingRequests.filter( - (sig) => - Address.isEqual(sig.wallet, request.wallet) && - sig.envelope.payload.type === 'config-update' && - sig.status === 'pending' && - sig.envelope.configuration.checkpoint <= request.envelope.configuration.checkpoint && - sig.id !== requestId, - ) - await Promise.all(pendingConfigUpdatesToClear.map((sig) => this.shared.modules.signatures.cancel(sig.id))) - } - - await this.shared.databases.signatures.set({ - ...request, - status: 'completed', - scheduledPruning: Date.now() + this.shared.databases.pruningInterval, - }) - } - - async request( - envelope: Envelope.Envelope, - action: A, - options: { - origin?: string - } = {}, - ): Promise { - // If the action is a config update, we need to remove all signature requests - // for the same wallet that also involve configuration updates - // as it may cause race conditions - // TODO: Eventually we should define a "delta configuration" signature request - if (Payload.isConfigUpdate(envelope.payload)) { - const pendingRequests = await this.shared.databases.signatures.list() - const pendingConfigUpdatesToClear = pendingRequests.filter( - (sig) => Address.isEqual(sig.wallet, envelope.wallet) && Payload.isConfigUpdate(sig.envelope.payload), - ) - - console.warn( - 'Deleting conflicting configuration updates for wallet', - envelope.wallet, - pendingConfigUpdatesToClear.map((pc) => pc.id), - ) - const cancellationResults = await Promise.allSettled( - pendingConfigUpdatesToClear.map((sig) => this.shared.modules.signatures.cancel(sig.id)), - ) - cancellationResults.forEach((result, index) => { - if (result.status === 'rejected') { - const failedSigId = pendingConfigUpdatesToClear[index]?.id - console.error( - `Failed to cancel conflicting signature request ${failedSigId || 'unknown ID'} during logout preparation:`, - result.reason, - ) - } - }) - } - - const id = uuidv7() - - await this.shared.databases.signatures.set({ - id, - wallet: envelope.wallet, - envelope: Envelope.toSigned(envelope), - origin: options.origin ?? 'unknown', - action, - createdAt: new Date().toISOString(), - status: 'pending', - }) - - return id - } - - async addSignature(requestId: string, signature: Envelope.SapientSignature | Envelope.Signature) { - const request = await this.getBase(requestId) - - Envelope.addSignature(request.envelope, signature) - - await this.shared.databases.signatures.set(request) - } - - async cancel(requestId: string) { - const request = await this.getBase(requestId) - - await this.shared.databases.signatures.set({ - ...request, - status: 'cancelled', - scheduledPruning: Date.now() + this.shared.databases.pruningInterval, - }) - } - - async prune() { - const now = Date.now() - const requests = await this.shared.databases.signatures.list() - const toPrune = requests.filter((req) => req.status !== 'pending' && req.scheduledPruning < now) - await Promise.all(toPrune.map((req) => this.shared.databases.signatures.del(req.id))) - return toPrune.length - } -} diff --git a/packages/wallet/wdk/src/sequence/signers.ts b/packages/wallet/wdk/src/sequence/signers.ts deleted file mode 100644 index ecb43cab76..0000000000 --- a/packages/wallet/wdk/src/sequence/signers.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { Shared } from './manager.js' -import { Kind, Kinds, SignerWithKind, WitnessExtraSignerKind } from './types/signer.js' - -export function isWitnessExtraSignerKind(extra: any): extra is WitnessExtraSignerKind { - return typeof extra === 'object' && extra !== null && 'signerKind' in extra -} - -function toKnownKind(kind: string): Kind { - if (kind.startsWith('custom-')) { - return kind as Kind - } - - if (kind === 'login-google-pkce') { - // Normalize legacy Google PKCE witnesses while the canonical signer kind is `login-google`. - return Kinds.LoginGoogle - } - - if (Object.values(Kinds).includes(kind as (typeof Kinds)[keyof typeof Kinds])) { - return kind as Kind - } - - console.warn(`Unknown signer kind: ${kind}`) - - return Kinds.Unknown -} - -// Signers is in charge to know (or figure out) the "kind" of each signer -// i.e., when a signature is requested, we only get address and imageHash (if sapient) -// this module takes care of figuring out the kind of signer (e.g., device, passkey, recovery, etc.) -export class Signers { - constructor(private readonly shared: Shared) {} - - async kindOf(wallet: Address.Address, address: Address.Address, imageHash?: Hex.Hex): Promise { - // // The device may be among the local devices, in that case it is a local device - // // TODO: Maybe signers shouldn't be getting in the way of devices, it feels like a - // // different concern - // if (await this.devices.has(address)) { - // return Kinds.LocalDevice - // } - - // Some signers are known by the configuration of the wallet development kit, specifically - // some of the sapient signers, who always share the same address - if (Address.isEqual(this.shared.sequence.extensions.recovery, address)) { - return Kinds.Recovery - } - if ( - Array.from(Object.values(this.shared.sequence.guardAddresses)).some((guardAddress) => - Address.isEqual(guardAddress, address), - ) - ) { - return Kinds.Guard - } - - // Passkeys are a sapient signer module: the address alone identifies the kind. - // Metadata (credential id, public key, etc.) is loaded later by the PasskeysHandler - // via the witness payload, so we can skip the witness probe here. - if (Address.isEqual(this.shared.sequence.extensions.passkeys, address)) { - return Kinds.LoginPasskey - } - - // Some signers are known to never publish a witness record (e.g. module signers). - // Skip probing the Sessions/Witness endpoint for them. - if (this.shared.sequence.nonWitnessableSigners.has(address.toLowerCase() as Address.Address)) { - return undefined - } - - // We need to use the state provider (and witness) this will tell us the kind of signer - // NOTICE: This looks expensive, but this operation should be cached by the state provider - const witness = await (imageHash - ? this.shared.sequence.stateProvider.getWitnessForSapient(wallet, address, imageHash) - : this.shared.sequence.stateProvider.getWitnessFor(wallet, address)) - - if (!witness) { - return undefined - } - - // Parse the payload, it may have the kind of signer - if (!Payload.isMessage(witness.payload)) { - return undefined - } - - try { - const message = JSON.parse(Hex.toString(witness.payload.message)) - if (isWitnessExtraSignerKind(message)) { - return toKnownKind(message.signerKind) - } - } catch { - // ignore - } - - return undefined - } - - async resolveKinds( - wallet: Address.Address, - signers: (Address.Address | { address: Address.Address; imageHash: Hex.Hex })[], - ): Promise { - return Promise.all( - signers.map(async (signer) => { - if (typeof signer === 'string') { - const kind = await this.kindOf(wallet, signer) - return { - address: signer, - kind, - } - } else { - const kind = await this.kindOf(wallet, signer.address, signer.imageHash) - return { - address: signer.address, - imageHash: signer.imageHash, - kind, - } - } - }), - ) - } -} diff --git a/packages/wallet/wdk/src/sequence/transactions.ts b/packages/wallet/wdk/src/sequence/transactions.ts deleted file mode 100644 index 26bf21d34c..0000000000 --- a/packages/wallet/wdk/src/sequence/transactions.ts +++ /dev/null @@ -1,668 +0,0 @@ -import { Envelope, Wallet, Bundler } from '@0xsequence/wallet-core' -import { Relayer } from '@0xsequence/relayer' -import { Constants, Payload } from '@0xsequence/wallet-primitives' -import { Abi, AbiFunction, Address, Hex, Provider, RpcTransport } from 'ox' -import { v7 as uuidv7 } from 'uuid' -import { Shared } from './manager.js' -import { - ERC4337RelayerOption, - isERC4337RelayerOption, - isStandardRelayerOption, - StandardRelayerOption, - Transaction, - TransactionFinal, - TransactionFormed, - TransactionRelayed, - TransactionRequest, -} from './types/transaction-request.js' - -export interface TransactionsInterface { - /** - * Retrieves the full state of a specific transaction by its ID. - * - * This method returns a `Transaction` object, which is a union type representing the - * transaction's current stage in the lifecycle (`requested`, `defined`, `formed`, `relayed`, `final`). - * The properties available on the returned object depend on its `status` property. - * For example, a `defined` transaction will include `relayerOptions`, while a `final` - * transaction will include the final on-chain `opStatus`. - * - * @param transactionId The unique identifier of the transaction to retrieve. - * @returns A promise that resolves to the `Transaction` object. - * @throws An error if the transaction is not found. - * @see {Transaction} for the detailed structure of the returned object and its possible states. - */ - get(transactionId: string): Promise - - /** - * Initiates a new transaction, starting the transaction lifecycle. - * - * This method takes a set of simplified transaction requests, prepares a wallet-specific - * transaction envelope, and stores it with a `requested` status. - * - * @param from The address of the wallet initiating the transaction. - * @param chainId The chain ID on which the transaction will be executed. - * @param txs An array of simplified transaction objects to be batched together. - * @param options Configuration for the request. - * @param options.source A string indicating the origin of the request (e.g., 'dapp-a.com', 'wallet-webapp'). - * @param options.noConfigUpdate If `true`, any pending on-chain wallet configuration updates will be - * skipped for this transaction. This is crucial for actions like recovery or session management - * where the active signer may not have permission to approve the main configuration update. - * Defaults to `false`, meaning updates are included by default. - * @param options.unsafe If `true`, allows transactions that might be risky, such as calls from the - * wallet to itself (which can change its configuration) or delegate calls. Use with caution. Defaults to `false`. - * @param options.space The nonce "space" for the transaction. Transactions in different spaces can be - * executed concurrently. If not provided, it defaults to the current timestamp. - * @returns A promise that resolves to the unique `transactionId` for this new request. - */ - request( - from: Address.Address, - chainId: number, - txs: TransactionRequest[], - options?: { source?: string; noConfigUpdate?: boolean; unsafe?: boolean; space?: bigint }, - ): Promise - - /** - * Finalizes the transaction's parameters and fetches relayer options. - * - * This moves a transaction from the `requested` to the `defined` state. In this step, - * the SDK queries all available relayers (both standard and ERC-4337 bundlers) for - * fee options and execution quotes. These options are then attached to the transaction object. - * - * @param transactionId The ID of the transaction to define. - * @param changes (Optional) An object to override transaction parameters. - * - `nonce`: Override the automatically selected nonce. - * - `space`: Override the nonce space. - * - `calls`: Tweak the `gasLimit` for specific calls within the batch. The array must match the original call length. - * @returns A promise that resolves when the transaction has been defined. - * @throws An error if the transaction is not in the `requested` state. - */ - define( - transactionId: string, - changes?: { nonce?: bigint; space?: bigint; calls?: Pick[] }, - ): Promise - - /** - * Selects a relayer for the transaction and prepares it for signing. - * - * This moves a transaction from `defined` to `formed`. Based on the chosen `relayerOptionId`, - * the transaction payload is finalized. If a standard relayer with a fee is chosen, the fee payment - * is prepended to the transaction calls. If an ERC-4337 bundler is chosen, the entire payload is - * transformed into a UserOperation-compatible format. - * - * This method creates a `SignatureRequest` and returns its ID. The next step is to use this ID - * with the `Signatures` module to collect the required signatures. - * - * @param transactionId The ID of the `defined` transaction. - * @param relayerOptionId The `id` of the desired relayer option from the `relayerOptions` array on the transaction object. - * @returns A promise that resolves to the `signatureId` of the newly created signature request. - * @throws An error if the transaction is not in the `defined` state. - */ - selectRelayer(transactionId: string, relayerOptionId: string): Promise - - /** - * Relays a signed transaction to the network. - * - * This is the final step, submitting the transaction for execution. It requires that the - * associated `SignatureRequest` has collected enough weight to meet the wallet's threshold. - * The transaction's status transitions to `relayed` upon successful submission to the relayer, - * and then asynchronously updates to `final` once it's confirmed or fails on-chain. - * - * The final on-chain status (`opStatus`) can be monitored using `onTransactionUpdate`. - * Possible final statuses are: - * - `confirmed`: The transaction succeeded. Includes the `transactionHash`. - * - `failed`: The transaction was included in a block but reverted. Includes the `transactionHash` and `reason`. - * If a transaction remains in `relayed` status for over 30 minutes, it will be marked as `failed` with a 'timeout' reason. - * - * @param transactionOrSignatureId The ID of the transaction to relay, or the ID of its associated signature request. - * @returns A promise that resolves once the transaction is successfully submitted to the relayer. - * @throws An error if the transaction is not in the `formed` state or if the signature threshold is not met. - */ - relay(transactionOrSignatureId: string): Promise - - /** - * Deletes a transaction from the manager, regardless of its current state. - * - * If the transaction is in the `formed` state, this will also cancel the associated - * signature request, preventing further signing. - * - * @param transactionId The ID of the transaction to delete. - * @returns A promise that resolves when the transaction has been deleted. - */ - delete(transactionId: string): Promise - - /** - * Subscribes to real-time updates for a single transaction. - * - * The callback is invoked whenever the transaction's state changes, such as transitioning - * from `relayed` to `final`, or when its `opStatus` is updated. This is the recommended - * way to monitor the progress of a relayed transaction. - * - * @param transactionId The ID of the transaction to monitor. - * @param cb The callback function to execute with the updated `Transaction` object. - * @param trigger (Optional) If `true`, the callback is immediately invoked with the current state. - * @returns A function that, when called, unsubscribes the listener. - */ - onTransactionUpdate(transactionId: string, cb: (transaction: Transaction) => void, trigger?: boolean): () => void - - /** - * Subscribes to updates for the entire list of transactions managed by this instance. - * - * This is useful for UI components that display a history or list of all transactions, - * ensuring the view stays synchronized as transactions are created, updated, or deleted. - * - * @param cb The callback function to execute with the full, updated list of transactions. - * @param trigger (Optional) If `true`, the callback is immediately invoked with the current list. - * @returns A function that, when called, unsubscribes the listener. - */ - onTransactionsUpdate(cb: (transactions: Transaction[]) => void, trigger?: boolean): () => void -} - -export class Transactions implements TransactionsInterface { - constructor(private readonly shared: Shared) {} - - initialize() { - this.shared.modules.cron.registerJob('update-transaction-status', 1000, async () => { - await this.refreshStatus() - }) - } - - public async refreshStatus(onlyTxId?: string): Promise { - const transactions = await this.list() - - const THIRTY_MINUTES = 30 * 60 * 1000 - const now = Date.now() - - let finalCount = 0 - - for (const tx of transactions) { - if (onlyTxId && tx.id !== onlyTxId) { - continue - } - - if (tx.status === 'relayed') { - let relayer: Relayer.Relayer | Bundler.Bundler | undefined = this.shared.sequence.relayers.find( - (relayer) => relayer.id === tx.relayerId, - ) - if (!relayer) { - const bundler: Bundler.Bundler | undefined = this.shared.sequence.bundlers.find( - (bundler) => bundler.id === tx.relayerId, - ) - if (!bundler) { - console.warn('relayer or bundler not found', tx.id, tx.relayerId) - continue - } - - relayer = bundler - } - - // Check for timeout: if relayedAt is more than 30 minutes ago, fail with timeout - if (typeof tx.relayedAt === 'number' && now - tx.relayedAt > THIRTY_MINUTES) { - const opStatus = { - status: 'failed', - reason: 'timeout', - } - this.shared.databases.transactions.set({ - ...tx, - opStatus, - status: 'final', - } as TransactionFinal) - finalCount++ - continue - } - - const opStatus = await relayer.status(tx.opHash as Hex.Hex, tx.envelope.chainId) - - if (opStatus.status === 'confirmed' || opStatus.status === 'failed') { - this.shared.databases.transactions.set({ - ...tx, - opStatus, - status: 'final', - } as TransactionFinal) - finalCount++ - } else { - this.shared.databases.transactions.set({ - ...tx, - opStatus, - status: 'relayed', - } as TransactionRelayed) - } - } - } - - return finalCount - } - - public async list(): Promise { - return this.shared.databases.transactions.list() - } - - public async get(transactionId: string): Promise { - const tx = await this.shared.databases.transactions.get(transactionId) - if (!tx) { - throw new Error(`Transaction ${transactionId} not found`) - } - - return tx - } - - async request( - from: Address.Address, - chainId: number, - txs: TransactionRequest[], - options?: { - source?: string - noConfigUpdate?: boolean - unsafe?: boolean - space?: bigint - }, - ): Promise { - const network = this.shared.sequence.networks.find((network) => network.chainId === chainId) - if (!network) { - throw new Error(`Network not found for ${chainId}`) - } - - const transport = RpcTransport.fromHttp(network.rpcUrl) - const provider = Provider.from(transport) - const wallet = new Wallet(from, { stateProvider: this.shared.sequence.stateProvider }) - - const calls = txs.map( - (tx): Payload.Call => ({ - to: tx.to, - value: tx.value ?? 0n, - data: tx.data ?? '0x', - gasLimit: tx.gasLimit ?? 0n, // TODO: Add gas estimation - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }), - ) - - const envelope = await wallet.prepareTransaction(provider, calls, { - noConfigUpdate: options?.noConfigUpdate, - unsafe: options?.unsafe, - space: options?.space !== undefined ? options.space : BigInt(Math.floor(Date.now() / 1000)), - }) - - const id = uuidv7() - await this.shared.databases.transactions.set({ - id, - wallet: from, - requests: txs, - envelope, - source: options?.source ?? 'unknown', - status: 'requested', - timestamp: Date.now(), - }) - - return id - } - - async define( - transactionId: string, - changes?: { - nonce?: bigint - space?: bigint - calls?: Pick[] - }, - ): Promise { - const tx = await this.get(transactionId) - if (tx.status !== 'requested') { - throw new Error(`Transaction ${transactionId} is not in the requested state`) - } - - // Modify the envelope with the changes - if (changes?.nonce) { - tx.envelope.payload.nonce = changes.nonce - } - - if (changes?.space) { - tx.envelope.payload.space = changes.space - } - - if (changes?.calls) { - if (changes.calls.length !== tx.envelope.payload.calls.length) { - throw new Error(`Invalid number of calls for transaction ${transactionId}`) - } - - for (let i = 0; i < changes.calls.length; i++) { - tx.envelope.payload.calls[i]!.gasLimit = changes.calls[i]!.gasLimit - } - } - - const wallet = new Wallet(tx.wallet, { stateProvider: this.shared.sequence.stateProvider }) - const network = this.shared.sequence.networks.find((network) => network.chainId === tx.envelope.chainId) - if (!network) { - throw new Error(`Network not found for ${tx.envelope.chainId}`) - } - const provider = Provider.from(RpcTransport.fromHttp(network.rpcUrl)) - - // Get relayer and relayer options - const [allRelayerOptions, allBundlerOptions] = await Promise.all([ - Promise.all( - this.shared.sequence.relayers - // Filter relayers based on the chainId of the transaction - .map(async (relayer): Promise => { - const ifAvailable = await relayer.isAvailable(tx.wallet, tx.envelope.chainId) - if (!ifAvailable) { - return [] - } - - // Determine the to address for the built transaction - const walletStatus = await wallet.getStatus(provider) - const to = walletStatus.isDeployed ? wallet.address : wallet.guest - - const feeOptions = await relayer.feeOptions(tx.wallet, tx.envelope.chainId, to, tx.envelope.payload.calls) - - if (feeOptions.options.length === 0) { - const { name, icon } = relayer instanceof Relayer.EIP6963.EIP6963Relayer ? relayer.info : {} - - return [ - { - kind: 'standard', - id: uuidv7(), - relayerType: relayer.type, - relayerId: relayer.id, - name, - icon, - } as StandardRelayerOption, - ] - } - - return feeOptions.options.map((feeOption: Relayer.FeeOption) => ({ - kind: 'standard', - id: uuidv7(), - feeOption, - relayerType: relayer.type, - relayerId: relayer.id, - quote: feeOptions.quote, - })) - }), - ), - (async () => { - const entrypoint = await wallet.get4337Entrypoint(provider) - if (!entrypoint) { - return [] - } - - return Promise.all( - this.shared.sequence.bundlers.map(async (bundler: Bundler.Bundler): Promise => { - const ifAvailable = await bundler.isAvailable(entrypoint, tx.envelope.chainId) - if (!ifAvailable) { - return [] - } - - try { - const erc4337Op = await wallet.prepare4337Transaction(provider, tx.envelope.payload.calls, { - space: tx.envelope.payload.space, - }) - - const erc4337OpsWithEstimatedLimits = await bundler.estimateLimits(tx.wallet, erc4337Op.payload) - - return erc4337OpsWithEstimatedLimits.map(({ speed, payload }) => ({ - kind: 'erc4337', - id: uuidv7(), - relayerType: 'erc4337', - relayerId: bundler.id, - alternativePayload: payload, - speed, - })) - } catch (e) { - console.error('error estimating limits 4337', e) - return [] - } - }), - ) - })(), - ]) - - await this.shared.databases.transactions.set({ - ...tx, - relayerOptions: [...allRelayerOptions.flat(), ...allBundlerOptions.flat()], - status: 'defined', - }) - } - - async selectRelayer(transactionId: string, relayerOptionId: string): Promise { - const tx = await this.get(transactionId) - if (tx.status !== 'defined') { - throw new Error(`Transaction ${transactionId} is not in the defined state`) - } - - const selection = tx.relayerOptions.find((option) => option.id === relayerOptionId) - if (!selection) { - throw new Error(`Relayer option ${relayerOptionId} not found for transaction ${transactionId}`) - } - - // if we have a fee option on the selected relayer option - if (isStandardRelayerOption(selection)) { - if (selection.feeOption) { - // then we need to prepend the transaction payload with the fee - const { token, to, value, gasLimit } = selection.feeOption - - Address.assert(to) - - if (token.contractAddress === Constants.ZeroAddress) { - tx.envelope.payload.calls.unshift({ - to, - value: BigInt(value), - data: '0x', - gasLimit: BigInt(gasLimit), - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }) - } else { - const [transfer] = Abi.from(['function transfer(address to, uint256 amount) returns (bool)']) - - tx.envelope.payload.calls.unshift({ - to: token.contractAddress as Address.Address, - value: 0n, - data: AbiFunction.encodeData(transfer, [to, BigInt(value)]), - gasLimit: BigInt(gasLimit), - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }) - } - } - } else if (selection.kind === 'erc4337') { - // Modify the envelope into a 4337 envelope - tx.envelope = { - ...tx.envelope, - payload: selection.alternativePayload, - } as Envelope.Envelope - } else { - throw new Error(`Invalid relayer option ${(selection as any).kind}`) - } - - // Pass to the signatures manager - const signatureId = await this.shared.modules.signatures.request(tx.envelope, 'send-transaction', { - origin: tx.source, - }) - - await this.shared.databases.transactions.set({ - ...tx, - relayerOptions: undefined, - relayerOption: selection, - status: 'formed', - signatureId, - } as TransactionFormed) - - return signatureId - } - - async relay(transactionOrSignatureId: string) { - // First, try to get the transaction directly - let tx: Transaction | undefined - try { - tx = await this.get(transactionOrSignatureId) - } catch { - // If not found, it might be a signature ID - const signature = await this.shared.modules.signatures.get(transactionOrSignatureId) - if (!signature) { - throw new Error(`Neither transaction nor signature found with ID ${transactionOrSignatureId}`) - } - - // Find the transaction associated with this signature - const transactions = await this.list() - tx = transactions.find( - (t) => t.status === 'formed' && 'signatureId' in t && t.signatureId === transactionOrSignatureId, - ) - - if (!tx) { - throw new Error(`No transaction found for signature ${transactionOrSignatureId}`) - } - } - - const transactionId = tx.id - - if (tx.status !== 'formed') { - throw new Error(`Transaction ${transactionId} is not in the formed state`) - } - - const signature = await this.shared.modules.signatures.get(tx.signatureId) - if (!signature) { - throw new Error(`Signature ${tx.signatureId} not found for transaction ${transactionId}`) - } - - const network = this.shared.sequence.networks.find((network) => network.chainId === tx.envelope.chainId) - if (!network) { - throw new Error(`Network not found for ${tx.envelope.chainId}`) - } - - const transport = RpcTransport.fromHttp(network.rpcUrl) - const provider = Provider.from(transport) - - const wallet = new Wallet(tx.wallet, { stateProvider: this.shared.sequence.stateProvider }) - - if (!Envelope.isSigned(signature.envelope)) { - throw new Error(`Transaction ${transactionId} is not signed`) - } - - const { weight, threshold } = Envelope.weightOf(signature.envelope) - if (weight < threshold) { - throw new Error(`Transaction ${transactionId} has insufficient weight`) - } - - const relayer = [...this.shared.sequence.relayers, ...this.shared.sequence.bundlers].find( - (relayer) => relayer.id === tx.relayerOption.relayerId, - ) - - if (!relayer) { - throw new Error(`Relayer ${tx.relayerOption.relayerId} not found for transaction ${transactionId}`) - } - - let opHash: string | undefined - - if (isStandardRelayerOption(tx.relayerOption)) { - if (!Relayer.isRelayer(relayer)) { - throw new Error(`Relayer ${tx.relayerOption.relayerId} is not a legacy relayer`) - } - - if (!Payload.isCalls(signature.envelope.payload)) { - throw new Error(`Transaction ${transactionId} with legacy relayer is not a calls payload`) - } - - const transaction = await wallet.buildTransaction(provider, { - ...signature.envelope, - payload: signature.envelope.payload, - }) - - const { opHash: opHashLegacy } = await relayer.relay( - transaction.to, - transaction.data, - tx.envelope.chainId, - tx.relayerOption.quote, - ) - - opHash = opHashLegacy - - await this.shared.databases.transactions.set({ - ...tx, - status: 'relayed', - opHash, - relayedAt: Date.now(), - relayerId: tx.relayerOption.relayerId, - } as TransactionRelayed) - - await this.shared.modules.signatures.complete(signature.id) - } else if (isERC4337RelayerOption(tx.relayerOption)) { - if (!Bundler.isBundler(relayer)) { - throw new Error(`Relayer ${tx.relayerOption.relayerId} is not a bundler`) - } - - if (!Payload.isCalls4337_07(signature.envelope.payload)) { - throw new Error(`Transaction ${transactionId} with bundler is not a calls4337_07 payload`) - } - - const { operation, entrypoint } = await wallet.build4337Transaction(provider, { - ...signature.envelope, - payload: signature.envelope.payload, - }) - - const { opHash: opHashBundler } = await relayer.relay(entrypoint, operation) - opHash = opHashBundler - - await this.shared.databases.transactions.set({ - ...tx, - status: 'relayed', - opHash, - relayedAt: Date.now(), - relayerId: tx.relayerOption.relayerId, - } as TransactionRelayed) - } else { - throw new Error(`Invalid relayer option ${(tx.relayerOption as any).kind}`) - } - - if (!opHash) { - throw new Error(`Relayer ${tx.relayerOption.relayerId} did not return an op hash`) - } - - // Refresh the status of the transaction every second for the next 30 seconds - const intervalId = setInterval(async () => { - const finalCount = await this.refreshStatus(tx.id) - if (finalCount > 0) { - clearInterval(intervalId) - } - }, 1000) - setTimeout(() => clearInterval(intervalId), 30 * 1000) - - if (!opHash) { - throw new Error(`Relayer ${tx.relayerOption.relayerId} did not return an op hash`) - } - } - - onTransactionsUpdate(cb: (transactions: Transaction[]) => void, trigger?: boolean) { - const undo = this.shared.databases.transactions.addListener(() => { - this.list().then((l) => cb(l)) - }) - - if (trigger) { - this.list().then((l) => cb(l)) - } - - return undo - } - - onTransactionUpdate(transactionId: string, cb: (transaction: Transaction) => void, trigger?: boolean) { - const undo = this.shared.databases.transactions.addListener(() => { - this.get(transactionId).then((t) => cb(t)) - }) - - if (trigger) { - this.get(transactionId).then((t) => cb(t)) - } - - return undo - } - - async delete(transactionId: string) { - const tx = await this.get(transactionId) - await this.shared.databases.transactions.del(transactionId) - - // Cancel any signature requests associated with this transaction - if (tx.status === 'formed') { - await this.shared.modules.signatures.cancel(tx.signatureId) - } - } -} diff --git a/packages/wallet/wdk/src/sequence/types/device.ts b/packages/wallet/wdk/src/sequence/types/device.ts deleted file mode 100644 index a7ca130808..0000000000 --- a/packages/wallet/wdk/src/sequence/types/device.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Address } from 'ox' - -/** - * Represents a device key that is authorized to sign for a wallet. - */ -export interface Device { - /** - * The on-chain address of the device key. - */ - address: Address.Address - - /** - * True if this is the key for the current local session. - * This is useful for UI to distinguish the active device from others and to exclude from remote logout if true. - */ - isLocal: boolean -} diff --git a/packages/wallet/wdk/src/sequence/types/index.ts b/packages/wallet/wdk/src/sequence/types/index.ts deleted file mode 100644 index 066e8a071e..0000000000 --- a/packages/wallet/wdk/src/sequence/types/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -export type { Message, MessageRequest, MessageRequested, MessageSigned } from './message-request.js' -export type { QueuedRecoveryPayload } from './recovery.js' -export { Actions } from './signature-request.js' -export type { - Action, - ActionToPayload, - BaseSignatureRequest, - SignatureRequest, - Signer, - SignerActionable, - SignerBase, - SignerReady, - SignerSigned, - SignerUnavailable, -} from './signature-request.js' -export { Kinds } from './signer.js' -export type { Kind, RecoverySigner, SignerWithKind, WitnessExtraSignerKind } from './signer.js' -export type { - BaseRelayerOption, - ERC4337RelayerOption, - StandardRelayerOption, - RelayerOption, - Transaction, - TransactionDefined, - TransactionFormed, - TransactionRelayed, - TransactionRequest, - TransactionRequested, -} from './transaction-request.js' -export type { Wallet } from './wallet.js' -export type { Module } from './module.js' diff --git a/packages/wallet/wdk/src/sequence/types/message-request.ts b/packages/wallet/wdk/src/sequence/types/message-request.ts deleted file mode 100644 index 8c1d9e1cc5..0000000000 --- a/packages/wallet/wdk/src/sequence/types/message-request.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Envelope } from '@0xsequence/wallet-core' -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' - -export type MessageRequest = string | Hex.Hex | Payload.TypedDataToSign - -type MessageBase = { - id: string - wallet: Address.Address - message: MessageRequest - source: string - signatureId: string -} - -export type MessageRequested = MessageBase & { - status: 'requested' - envelope: Envelope.Envelope -} - -export type MessageSigned = MessageBase & { - status: 'signed' - envelope: Envelope.Signed - messageSignature: Hex.Hex -} - -export type Message = MessageRequested | MessageSigned diff --git a/packages/wallet/wdk/src/sequence/types/module.ts b/packages/wallet/wdk/src/sequence/types/module.ts deleted file mode 100644 index 014a97ae69..0000000000 --- a/packages/wallet/wdk/src/sequence/types/module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Config } from '@0xsequence/wallet-primitives' - -export type Module = { - weight: bigint - sapientLeaf: Config.SapientSignerLeaf - guardLeaf?: Config.Topology -} diff --git a/packages/wallet/wdk/src/sequence/types/recovery.ts b/packages/wallet/wdk/src/sequence/types/recovery.ts deleted file mode 100644 index 59b662c586..0000000000 --- a/packages/wallet/wdk/src/sequence/types/recovery.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' - -export type QueuedRecoveryPayload = { - id: string - index: bigint - recoveryModule: Address.Address - wallet: Address.Address - signer: Address.Address - chainId: number - startTimestamp: bigint - endTimestamp: bigint - payloadHash: Hex.Hex - payload?: Payload.Payload -} diff --git a/packages/wallet/wdk/src/sequence/types/sessions.ts b/packages/wallet/wdk/src/sequence/types/sessions.ts deleted file mode 100644 index 1efef2490a..0000000000 --- a/packages/wallet/wdk/src/sequence/types/sessions.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Hex } from 'ox' - -export type AuthorizeImplicitSessionArgs = { - target: string - applicationData?: Hex.Hex -} diff --git a/packages/wallet/wdk/src/sequence/types/signature-request.ts b/packages/wallet/wdk/src/sequence/types/signature-request.ts deleted file mode 100644 index e3cb1b7875..0000000000 --- a/packages/wallet/wdk/src/sequence/types/signature-request.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Envelope } from '@0xsequence/wallet-core' -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { Handler } from '../handlers/handler.js' - -export type ActionToPayload = { - [Actions.Logout]: Payload.ConfigUpdate - [Actions.RemoteLogout]: Payload.ConfigUpdate - [Actions.Login]: Payload.ConfigUpdate - [Actions.SendTransaction]: Payload.Calls | Payload.Calls4337_07 - [Actions.SignMessage]: Payload.Message - [Actions.SessionUpdate]: Payload.ConfigUpdate - [Actions.Recovery]: Payload.Recovery - [Actions.AddRecoverySigner]: Payload.ConfigUpdate - [Actions.RemoveRecoverySigner]: Payload.ConfigUpdate - [Actions.AddLoginSigner]: Payload.ConfigUpdate - [Actions.RemoveLoginSigner]: Payload.ConfigUpdate - [Actions.SessionImplicitAuthorize]: Payload.SessionImplicitAuthorize -} - -export const Actions = { - Logout: 'logout', - RemoteLogout: 'remote-logout', - Login: 'login', - SendTransaction: 'send-transaction', - SignMessage: 'sign-message', - SessionUpdate: 'session-update', - Recovery: 'recovery', - AddRecoverySigner: 'add-recovery-signer', - RemoveRecoverySigner: 'remove-recovery-signer', - AddLoginSigner: 'add-login-signer', - RemoveLoginSigner: 'remove-login-signer', - SessionImplicitAuthorize: 'session-implicit-authorize', -} as const - -export type Action = (typeof Actions)[keyof typeof Actions] - -/** - * Represents the fundamental, stored state of a signature request. - * This is the core object persisted in the database, containing the static details of what needs to be signed. - * - * @template A The specific action type, which determines the payload shape. - */ -export type BaseSignatureRequest = - | { - /** A unique identifier for the signature request (UUID v7). */ - id: string - /** The address of the wallet this request is for. */ - wallet: Address.Address - /** A string indicating the origin of the request (e.g., a dapp URL or 'wallet-webapp'). */ - origin: string - /** The ISO 8601 timestamp of when the request was created. */ - createdAt: string - - /** The specific type of action being requested (e.g., 'send-transaction', 'login'). */ - action: A - /** - * The Sequence wallet envelope containing the payload to be signed, the wallet configuration, - * and the list of collected signatures. - */ - envelope: Envelope.Signed - /** The current status of the request. 'pending' means it is active and awaiting signatures. */ - status: 'pending' - } - | { - /** A unique identifier for the signature request (UUID v7). */ - id: string - /** The address of the wallet this request is for. */ - wallet: Address.Address - /** A string indicating the origin of the request (e.g., a dapp URL or 'wallet-webapp'). */ - origin: string - /** The ISO 8601 timestamp of when the request was created. */ - createdAt: string - - /** The specific type of action being requested (e.g., 'send-transaction', 'login'). */ - action: A - /** - * The Sequence wallet envelope containing the payload to be signed, the wallet configuration, - * and the list of collected signatures. - */ - envelope: Envelope.Signed - /** The terminal status of the request. It is no longer active. */ - status: 'cancelled' | 'completed' - /** - * A Unix timestamp (in milliseconds) indicating when this terminal request can be safely - * removed from the database by the pruning job. - */ - scheduledPruning: number - } - -/** - * The most basic representation of a signer required for a `SignatureRequest`. - */ -export type SignerBase = { - /** The address of the signer. */ - address: Address.Address - /** - * For sapient signers (e.g., passkeys, recovery modules), this is the hash of the - * configuration tree that defines the signer's behavior, acting as a unique identifier. - */ - imageHash?: Hex.Hex -} - -/** - * Represents a signer who has already provided their signature for the request. - * The UI can show this signer as "completed". - */ -export type SignerSigned = SignerBase & { - /** The handler associated with this signer's kind. */ - handler?: Handler - /** The status of this signer, always 'signed'. */ - status: 'signed' -} - -/** - * Represents a signer that cannot currently provide a signature. - * The UI can use the `reason` to inform the user why this option is disabled. - */ -export type SignerUnavailable = SignerBase & { - /** The handler associated with this signer's kind, if one could be determined. */ - handler?: Handler - /** A machine-readable string explaining why the signer is unavailable (e.g., 'not-local-key', 'ui-not-registered'). */ - reason: string - /** The status of this signer, always 'unavailable'. */ - status: 'unavailable' -} - -/** - * Represents a signer that is immediately available to sign without any further user interaction. - * This is typical for local device keys. The UI can present this as a simple "Sign" button. - */ -export type SignerReady = SignerBase & { - /** The handler that will perform the signing. */ - handler: Handler - /** The status of this signer, always 'ready'. */ - status: 'ready' - /** A function to call to trigger the signing process. Returns `true` on success. */ - handle: () => Promise -} - -/** - * Represents a signer that requires user interaction to provide a signature. - * The UI should use the `message` to prompt the user for the appropriate action (e.g., enter OTP, use passkey). - */ -export type SignerActionable = SignerBase & { - /** The handler that will manage the user interaction and signing flow. */ - handler: Handler - /** The status of this signer, always 'actionable'. */ - status: 'actionable' - /** A message key for the UI, indicating the required action (e.g., 'enter-mnemonic', 'request-interaction-with-passkey'). */ - message: string - /** A function that initiates the user interaction flow. Returns `true` when the user successfully completes the action. */ - handle: () => Promise -} - -/** - * A union type representing all possible states of a signer for a given signature request. - * An array of these objects is used to build a dynamic signing UI. - */ -export type Signer = SignerSigned | SignerUnavailable | SignerReady | SignerActionable - -/** - * The "hydrated" signature request object, providing a complete, real-time view of the request's state. - * It combines the static `BaseSignatureRequest` with dynamic information about the required signers. - * This is the primary object used for building interactive signing UIs. - */ -export type SignatureRequest = BaseSignatureRequest & { - /** The total weight of the signatures that have been collected so far. */ - weight: bigint - /** The total weight required from signers to fulfill the request. */ - threshold: bigint - /** An array containing the real-time status of every signer required for this request. */ - signers: Signer[] -} diff --git a/packages/wallet/wdk/src/sequence/types/signer.ts b/packages/wallet/wdk/src/sequence/types/signer.ts deleted file mode 100644 index eb46db52fd..0000000000 --- a/packages/wallet/wdk/src/sequence/types/signer.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Address, Hex } from 'ox' - -export const Kinds = { - LocalDevice: 'local-device', - LoginPasskey: 'login-passkey', - LoginMnemonic: 'login-mnemonic', // Todo: do not name it login-mnemonic, just mnemonic - LoginEmailOtp: 'login-email-otp', - LoginGoogle: 'login-google', - LoginApple: 'login-apple', - Recovery: 'recovery-extension', - Guard: 'guard-extension', - Unknown: 'unknown', -} as const - -export type Kind = (typeof Kinds)[keyof typeof Kinds] | `custom-${string}` - -export type WitnessExtraSignerKind = { - signerKind: string -} - -export type SignerWithKind = { - address: Address.Address - kind?: Kind - imageHash?: Hex.Hex -} - -export type RecoverySigner = { - kind: Kind - isRecovery: true - address: Address.Address - minTimestamp: bigint - requiredDeltaTime: bigint -} diff --git a/packages/wallet/wdk/src/sequence/types/transaction-request.ts b/packages/wallet/wdk/src/sequence/types/transaction-request.ts deleted file mode 100644 index 51160a0499..0000000000 --- a/packages/wallet/wdk/src/sequence/types/transaction-request.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Envelope } from '@0xsequence/wallet-core' -import { Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex } from 'ox' -import { Relayer } from '@0xsequence/relayer' - -export type TransactionRequest = { - to: Address.Address - value?: bigint - data?: Hex.Hex - gasLimit?: bigint -} - -export type BaseRelayerOption = { - id: string - relayerType: string - relayerId: string - speed?: 'slow' | 'standard' | 'fast' -} - -export type StandardRelayerOption = BaseRelayerOption & { - kind: 'standard' - feeOption?: Relayer.FeeOption - quote?: Relayer.FeeQuote - name?: string - icon?: string -} - -export type ERC4337RelayerOption = BaseRelayerOption & { - kind: 'erc4337' - alternativePayload: Payload.Calls4337_07 -} - -export type RelayerOption = StandardRelayerOption | ERC4337RelayerOption - -export function isStandardRelayerOption(relayerOption: RelayerOption): relayerOption is StandardRelayerOption { - return relayerOption.kind === 'standard' -} - -export function isERC4337RelayerOption(relayerOption: RelayerOption): relayerOption is ERC4337RelayerOption { - return relayerOption.kind === 'erc4337' -} - -type TransactionBase = { - id: string - wallet: Address.Address - requests: TransactionRequest[] - source: string - envelope: Envelope.Envelope - timestamp: number -} - -export type TransactionRequested = TransactionBase & { - status: 'requested' -} - -export type TransactionDefined = TransactionBase & { - status: 'defined' - relayerOptions: RelayerOption[] -} - -export type TransactionFormed = TransactionBase & { - relayerOption: RelayerOption - status: 'formed' - signatureId: string -} - -export type TransactionRelayed = TransactionBase & { - status: 'relayed' - opHash: string - relayedAt: number - relayerId: string - opStatus?: Relayer.OperationStatus -} - -export type TransactionFinal = TransactionBase & { - status: 'final' - opHash: string - relayedAt: number - relayerId: string - opStatus: Relayer.OperationStatus -} - -export type Transaction = - | TransactionRequested - | TransactionDefined - | TransactionFormed - | TransactionRelayed - | TransactionFinal diff --git a/packages/wallet/wdk/src/sequence/types/wallet.ts b/packages/wallet/wdk/src/sequence/types/wallet.ts deleted file mode 100644 index c1e12a990c..0000000000 --- a/packages/wallet/wdk/src/sequence/types/wallet.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Address } from 'ox' - -/** - * Represents the local state of a managed wallet session within the SDK. - * This object contains information about the current session, not just the on-chain state. - */ -export interface Wallet { - /** - * The unique, on-chain address of the wallet. - * @property - */ - address: Address.Address - - /** - * The current status of the wallet's session in the manager. - * - `ready`: The wallet is fully logged in and available for signing and sending transactions. - * - `logging-in`: A login process has been initiated but is not yet complete. The wallet is not yet usable. - * - `logging-out`: A hard logout process has been initiated but is not yet complete. The wallet is being removed. - * @property - */ - status: 'ready' | 'logging-in' | 'logging-out' - - /** - * The ISO 8601 timestamp of when the current session was established. - * @property - */ - loginDate: string - - /** - * The address of the temporary, session-specific key for this device. - * This key is added to the wallet's on-chain configuration upon login and is used for - * most signing operations, avoiding the need to use the primary login credential repeatedly. - * @property - */ - device: Address.Address - - /** - * A string identifier for the authentication method used for this session. - * Examples: 'login-mnemonic', 'login-passkey', 'login-google'. - * @property - */ - loginType: string - - /** - * Indicates whether the wallet's configuration includes a security guard module (e.g., for 2FA). - * This is a reflection of the on-chain configuration at the time of login. - * @property - */ - useGuard: boolean - - /** - * The email address associated with the login, if available (e.g., from an email OTP or social login). - * This is optional and used primarily for display purposes in the UI. - * @property - */ - loginEmail?: string -} - -/** - * Provides contextual information to a `WalletSelectionUiHandler` about how it was invoked. - * This helps the UI adapt its presentation (e.g., full-page vs. modal). - */ -export type WalletSelectionContext = { - /** - * `true` if the wallet selection was triggered as part of an OAuth redirect flow. - * @property - */ - isRedirect: boolean - - /** - * If `isRedirect` is true, this is the original URL the user intended to visit before the - * authentication redirect, allowing the app to return them there after completion. - * @property - */ - target?: string - - /** - * The kind of authentication method that initiated the flow (e.g., 'google-pkce'). - * @property - */ - signupKind?: string -} - -/** - * The set of options passed to a `WalletSelectionUiHandler` when a user attempts to sign up - * with a credential that is already associated with one or more existing wallets. - */ -export type WalletSelectionOptions = { - /** - * An array of wallet addresses that are already configured to use the provided credential (`signerAddress`). - * The UI should present these as login options. - * @property - */ - existingWallets: Address.Address[] - - /** - * The address of the signer/credential that triggered this selection flow (e.g., a passkey's public key address). - * @property - */ - signerAddress: Address.Address - - /** - * Additional context about how the selection handler was invoked. - * @property - */ - context: WalletSelectionContext -} - -/** - * Defines the signature for a function that handles the UI for wallet selection. - * - * When a user attempts to sign up, the SDK may discover that their credential (e.g., passkey, social account) - * is already a signer for existing wallets. This handler is then called to let the user decide how to proceed. - * - * @param options - The `WalletSelectionOptions` containing the list of existing wallets and context. - * @returns A promise that resolves with one of the following: - * - The string `'create-new'` if the user chose to create a new wallet for this login method. - * - The string `'abort-signup'` if the user chose to abort the sign-up process (no wallet is created; the client may call `login` to log in to an existing wallet). - */ -export type WalletSelectionUiHandler = (options: WalletSelectionOptions) => Promise<'create-new' | 'abort-signup'> diff --git a/packages/wallet/wdk/src/sequence/wallets.ts b/packages/wallet/wdk/src/sequence/wallets.ts deleted file mode 100644 index 5746f7a832..0000000000 --- a/packages/wallet/wdk/src/sequence/wallets.ts +++ /dev/null @@ -1,1784 +0,0 @@ -import { Wallet as CoreWallet, Envelope, Signers, State } from '@0xsequence/wallet-core' -import { Config, Constants, Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider, RpcTransport } from 'ox' -import { AuthCommitment } from '../dbs/auth-commitments.js' -import { AuthCodeHandler } from './handlers/authcode.js' -import { IdTokenHandler } from './handlers/idtoken.js' -import { MnemonicHandler } from './handlers/mnemonic.js' -import { OtpHandler } from './handlers/otp.js' -import { Shared } from './manager.js' -import { Device } from './types/device.js' -import { Action, Actions, Module } from './types/index.js' -import { Kinds, SignerWithKind, WitnessExtraSignerKind } from './types/signer.js' -import { Wallet, WalletSelectionUiHandler } from './types/wallet.js' -import { PasskeysHandler } from './handlers/passkeys.js' -import type { PasskeySigner } from './passkeys-provider.js' - -function getSignupHandlerKey(kind: SignupArgs['kind'] | StartSignUpWithRedirectArgs['kind'] | AuthCommitment['kind']) { - if (kind === 'google-pkce') { - return Kinds.LoginGoogle - } - if (kind.startsWith('custom-')) { - return kind - } - return 'login-' + kind -} - -function getSignerKindForSignup(kind: SignupArgs['kind'] | AuthCommitment['kind']) { - if (kind === 'google-id-token' || kind === 'google-pkce') { - return Kinds.LoginGoogle - } - if (kind === 'apple-id-token' || kind === 'apple') { - return Kinds.LoginApple - } - if (kind.startsWith('custom-')) { - return kind - } - return ('login-' + kind) as string -} - -function getIdTokenSignupHandler( - shared: Shared, - kind: typeof Kinds.LoginGoogle | typeof Kinds.LoginApple | `custom-${string}`, -): IdTokenHandler { - const handler = shared.handlers.get(kind) - if (!handler) { - throw new Error('handler-not-registered') - } - if (!(handler instanceof IdTokenHandler)) { - throw new Error('handler-does-not-support-id-token') - } - return handler -} - -export type StartSignUpWithRedirectArgs = { - kind: 'google-pkce' | 'apple' | `custom-${string}` - target: string - metadata: { [key: string]: string } -} - -export type StartAddLoginSignerWithRedirectArgs = { - wallet: Address.Address - kind: 'google-pkce' | 'apple' | `custom-${string}` - target: string -} - -export type SignupStatus = - | { type: 'login-signer-created'; address: Address.Address } - | { type: 'device-signer-created'; address: Address.Address } - | { type: 'wallet-created'; address: Address.Address } - | { type: 'signup-completed' } - | { type: 'signup-aborted' } - -export type CommonSignupArgs = { - use4337?: boolean - noGuard?: boolean - noSessionManager?: boolean - noRecovery?: boolean - onStatusChange?: (status: SignupStatus) => void -} - -export type PasskeySignupArgs = CommonSignupArgs & { - kind: 'passkey' - name?: string -} - -export type MnemonicSignupArgs = CommonSignupArgs & { - kind: 'mnemonic' - mnemonic: string -} - -export type EmailOtpSignupArgs = CommonSignupArgs & { - kind: 'email-otp' - email: string -} - -export type IdTokenSignupArgs = CommonSignupArgs & { - kind: 'google-id-token' | 'apple-id-token' | `custom-${string}` - idToken: string -} - -export type CompleteRedirectArgs = CommonSignupArgs & { - state: string - code: string -} - -export type AuthCodeSignupArgs = CommonSignupArgs & { - kind: 'google-pkce' | 'apple' | `custom-${string}` - commitment: AuthCommitment - code: string - target: string - isRedirect: boolean -} - -export type SignupArgs = - | PasskeySignupArgs - | MnemonicSignupArgs - | EmailOtpSignupArgs - | IdTokenSignupArgs - | AuthCodeSignupArgs - -export type AddLoginSignerArgs = { - wallet: Address.Address -} & ( - | { kind: 'mnemonic'; mnemonic: string } - | { kind: 'email-otp'; email: string } - | { kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`; idToken: string } -) - -export type RemoveLoginSignerArgs = { - wallet: Address.Address - signerAddress: Address.Address -} - -export type LoginToWalletArgs = { - wallet: Address.Address -} - -export type LoginToMnemonicArgs = { - kind: 'mnemonic' - mnemonic: string - selectWallet: (wallets: Address.Address[]) => Promise -} - -export type LoginToPasskeyArgs = { - kind: 'passkey' - credentialId?: string - selectWallet: (wallets: Address.Address[]) => Promise -} - -export type LoginArgs = LoginToWalletArgs | LoginToMnemonicArgs | LoginToPasskeyArgs - -export interface WalletsInterface { - /** - * Checks if a wallet is currently managed and logged in within this manager instance. - * - * This method queries the local database to see if there is an active session for the given wallet address. - * It's important to note that a `false` return value does not mean the wallet doesn't exist on-chain; - * it simply means this specific browser/device does not have a logged-in session for it. - * - * @param wallet The address of the wallet to check. - * @returns A promise that resolves to `true` if the wallet is managed, `false` otherwise. - */ - has(wallet: Address.Address): Promise - - /** - * Retrieves the details of a managed wallet. - * - * This method returns the stored `Wallet` object, which contains information about the session, - * such as its status (`ready`, `logging-in`, `logging-out`), the device address used for this session, - * the login method (`mnemonic`, `passkey`, etc.), and the login date. - * - * @param walletAddress The address of the wallet to retrieve. - * @returns A promise that resolves to the `Wallet` object if found, or `undefined` if the wallet is not managed. - * @see {Wallet} for details on the returned object structure. - */ - get(walletAddress: Address.Address): Promise - - /** - * Lists all wallets that are currently managed and logged in by this manager instance. - * - * @returns A promise that resolves to an array of `Wallet` objects. - */ - list(): Promise - - /** - * Lists all device keys currently authorized in the wallet's on-chain configuration. - * - * This method inspects the wallet's configuration to find all signers that - * have been identified as 'local-device' keys. It also indicates which of - * these keys corresponds to the current, active session. - * - * @param wallet The address of the wallet to query. - * @returns A promise that resolves to an array of `Device` objects. - */ - listDevices(wallet: Address.Address): Promise - - /** - * Registers a UI handler for wallet selection. - * - * Some authentication methods (like emails or social logins) can be associated with multiple wallets. - * When a user attempts to sign up with a credential that already has wallets, this handler is invoked - * to prompt the user to either select an existing wallet to log into or confirm the creation of a new one. - * - * If no handler is registered, the system will default to creating a new wallet. - * Only one handler can be registered per manager instance. - * - * @param handler A function that receives `WalletSelectionOptions` and prompts the user for a decision. - * It should return the address of the selected wallet, or `undefined` to proceed with new wallet creation. - * @returns A function to unregister the provided handler. - */ - registerWalletSelector(handler: WalletSelectionUiHandler): () => void - - /** - * Unregisters the currently active wallet selection UI handler. - * - * @param handler (Optional) If provided, it will only unregister if the given handler is the one currently registered. - * This prevents accidentally unregistering a handler set by another part of the application. - */ - unregisterWalletSelector(handler?: WalletSelectionUiHandler): void - - /** - * Subscribes to updates for the list of managed wallets. - * - * The provided callback function is invoked whenever a wallet is added (login), removed (logout), - * or has its status updated (e.g., from 'logging-in' to 'ready'). - * - * @param cb The callback function to execute with the updated list of wallets. - * @param trigger (Optional) If `true`, the callback will be immediately invoked with the current list of wallets upon registration. - * @returns A function to unsubscribe the listener. - */ - onWalletsUpdate(cb: (wallets: Wallet[]) => void, trigger?: boolean): () => void - - /** - * Creates and configures a new Sequence wallet. - * - * This method manages the full sign-up process, including generating a login signer, creating a device key, - * building the wallet's on-chain configuration, deploying the wallet, and storing the session locally. - * - * If a wallet selection UI handler is registered, it will be invoked if the provided credential is already associated - * with one or more existing wallets. The handler can return: - * - `'create-new'`: The sign-up process continues and a new wallet is created. The method resolves to the new wallet address. - * - `'abort-signup'`: The sign-up process is cancelled and the method returns `undefined`. To log in to an existing wallet, - * the client must call the `login` method separately with the desired wallet address. - * If no handler is registered, a new wallet is always created. - * - * @param args The sign-up arguments, specifying the method and options. - * - `kind: 'mnemonic'`: Uses a mnemonic phrase as the login credential. - * - `kind: 'passkey'`: Prompts the user to create a WebAuthn passkey. - * - `kind: 'email-otp'`: Initiates an OTP flow to the user's email. - * - `kind: 'google-id-token' | 'apple-id-token'`: Completes an OIDC ID token flow when the provider is configured with `authMethod: 'id-token'`. - * - `kind: 'google-pkce' | 'apple'`: Completes an OAuth redirect flow. - * Common options like `noGuard` or `noRecovery` can customize the wallet's security features. - * @returns A promise that resolves to the address of the newly created wallet, or `undefined` if the sign-up was aborted. - * @see {SignupArgs} - */ - signUp(args: SignupArgs): Promise - - /** - * Initiates a sign-up or login process that involves an OAuth redirect. - * - * This is the first step for social logins (e.g., Google, Apple). It generates the necessary - * challenges and state, stores them locally, and returns a URL. Your application should - * redirect the user to this URL to continue the authentication process with the third-party provider. - * - * @param args Arguments specifying the provider (`kind`) and the `target` URL for the provider to redirect back to. - * @returns A promise that resolves to the full OAuth URL to which the user should be redirected. - * @see {completeRedirect} for the second step of this flow. - */ - startSignUpWithRedirect(args: StartSignUpWithRedirectArgs): Promise - - /** - * Completes an OAuth redirect flow after the user returns to the application. - * - * After the user authenticates with the third-party provider and is redirected back, your application - * must call this method with the `state` and `code` parameters from the URL query string. - * This method verifies the state, exchanges the code for a token, and completes the sign-up or login process. - * - * @param args The arguments containing the `state` and `code` from the redirect, along with original sign-up options. - * @returns A promise that resolves to target path that should be redirected to. - */ - completeRedirect(args: CompleteRedirectArgs): Promise - - /** - * Initiates the login process for an existing wallet by adding the current device as a new signer. - * - * This method is for adding a new device/session to a wallet that has already been created. It generates a - * configuration update transaction to add the new device key to the wallet's on-chain topology. - * This configuration change requires a signature from an existing authorized signer. - * - * The `args` can be one of: - * - `LoginToWalletArgs`: Login to a known wallet address. - * - `LoginToMnemonicArgs` / `LoginToPasskeyArgs`: "Discover" wallets associated with a credential, - * prompt the user to select one via the `selectWallet` callback, and then log in. - * - * @param args The login arguments. - * @returns A promise that resolves to a `requestId`. This ID represents the signature request for the - * configuration update, which must be signed by an existing key to authorize the new device. - * @see {completeLogin} - */ - login(args: LoginArgs): Promise - - /** - * Completes the login process after the configuration update has been signed. - * - * After `login` is called and the resulting signature request is fulfilled, this method should be called - * with the `requestId`. It submits the signed configuration update to the key tracker, finalizing the - * addition of the new device. The wallet's local status is then set to 'ready'. - * - * @param requestId The ID of the completed signature request returned by `login`. - * @returns A promise that resolves when the login process is fully complete and the wallet is ready for use. - */ - completeLogin(requestId: string): Promise - - /** - * Adds a new login signer to an existing wallet, enabling account federation. - * - * This allows a user to link a new login method (e.g., Google, email OTP, mnemonic) to a wallet - * that was originally created with a different credential. After federation, the wallet can be - * discovered and accessed via any of its linked login methods. - * - * @param args The arguments specifying the wallet and the new login credential to add. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {completeAddLoginSigner} - */ - addLoginSigner(args: AddLoginSignerArgs): Promise - - /** - * Completes the add-login-signer process after the configuration update has been signed. - * - * @param requestId The ID of the completed signature request returned by `addLoginSigner`. - * @returns A promise that resolves when the configuration update has been submitted. - */ - completeAddLoginSigner(requestId: string): Promise - - /** - * Initiates an add-login-signer process that involves an OAuth redirect. - * - * This is the first step for adding a social login signer (e.g., Google, Apple) to an existing wallet - * via a redirect-based OAuth flow. It validates the wallet, generates the necessary challenges and state, - * stores them locally, and returns a URL. Your application should redirect the user to this URL. - * - * After the redirect callback, call `completeRedirect` with the returned state and code. This will - * create a pending `AddLoginSigner` signature request internally. The caller can then discover - * the pending request through the signatures module and call `completeAddLoginSigner` with the requestId - * once it has been signed. - * - * @param args Arguments specifying the wallet, provider (`kind`), and the `target` URL for the redirect callback. - * @returns A promise that resolves to the full OAuth URL to which the user should be redirected. - * @see {completeRedirect} for the second step of this flow. - * @see {completeAddLoginSigner} to finalize after signing. - */ - startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs): Promise - - /** - * Removes a login signer from an existing wallet, enabling account defederation. - * - * This allows a user to unlink a login method from a wallet. A safety guard ensures - * at least one login signer always remains. - * - * @param args The arguments specifying the wallet and the signer address to remove. - * @returns A promise that resolves to a `requestId` for the configuration update signature request. - * @see {completeRemoveLoginSigner} - */ - removeLoginSigner(args: RemoveLoginSignerArgs): Promise - - /** - * Completes the remove-login-signer process after the configuration update has been signed. - * - * @param requestId The ID of the completed signature request returned by `removeLoginSigner`. - * @returns A promise that resolves when the configuration update has been submitted. - */ - completeRemoveLoginSigner(requestId: string): Promise - - /** - * Logs out from a given wallet, ending the current session. - * - * This method has two modes of operation: - * 1. **Hard Logout (default):** Initiates a key tracker update to remove the current device's key - * from the wallet's configuration. This is the most secure option as it revokes the key's access - * entirely. This returns a `requestId` that must be signed and completed via `completeLogout`. - * 2. **Soft Logout (`skipRemoveDevice: true`):** Immediately deletes the session and device key from local - * storage only. This is faster as it requires no transaction, but the device key remains authorized. - * This is suitable for clearing a session on a trusted device without revoking the key itself. - * - * @param wallet The address of the wallet to log out from. - * @param options (Optional) Configuration for the logout process. - * @returns If `skipRemoveDevice` is `true`, returns `Promise`. Otherwise, returns a `Promise` - * containing the `requestId` for the on-chain logout transaction. - */ - logout( - wallet: Address.Address, - options?: T, - ): Promise - - /** - * Initiates a remote logout process for a given wallet. - * - * This method is used to log out a device from a wallet that is not the local device. - * - * @param wallet The address of the wallet to log out from. - * @param deviceAddress The address of the device to log out. - * @returns A promise that resolves to a `requestId` for the on-chain logout transaction. - */ - remoteLogout(wallet: Address.Address, deviceAddress: Address.Address): Promise - - /** - * Completes the "hard logout" process. - * - * If `logout` was called without `skipRemoveDevice: true`, the resulting configuration update must be signed. - * Once signed, this method takes the `requestId`, broadcasts the transaction to the network, and upon completion, - * removes all local data associated with the wallet and device. - * - * @param requestId The ID of the completed signature request returned by `logout`. - * @param options (Optional) Advanced options for completing the logout. - * @returns A promise that resolves when the on-chain update is submitted and local storage is cleared. - */ - completeLogout(requestId: string, options?: { skipValidateSave?: boolean }): Promise - - /** - * Completes a generic configuration update after it has been signed. - * - * This method takes a requestId for any action that results in a configuration - * update (e.g., from `login`, `logout`, `remoteLogout`, `addSigner`, etc.), - * validates it, and saves the new configuration to the state provider. The - * update will be bundled with the next on-chain transaction. - * - * @param requestId The ID of the completed signature request. - * @returns A promise that resolves when the update has been processed. - */ - completeConfigurationUpdate(requestId: string): Promise - - /** - * Retrieves the full, resolved configuration of a wallet. - * - * This method provides a detailed view of the wallet's structure, including lists of login signers, - * device signers and a guard signer with their "kind" (e.g., 'local-device', 'login-passkey') resolved. - * Additionally, each module with a guard signer will have its guard signer resolved in the `moduleGuards` map, - * where the key is the module address and the value is the guard signer. - * It also includes the raw, low-level configuration topology. - * - * @param wallet The address of the wallet. - * @returns A promise that resolves to an object containing the resolved `devices`, `login` signers, and the `raw` configuration. - */ - getConfiguration(wallet: Address.Address): Promise<{ - devices: SignerWithKind[] - login: SignerWithKind[] - walletGuard?: SignerWithKind - moduleGuards: Map<`0x${string}`, SignerWithKind> - raw: any - }> - - /** - * Fetches the current nonce of a wallet for a specific transaction space. - * - * Sequence wallets use a 2D nonce system (`space`, `nonce`) to prevent replay attacks and allow - * for concurrent transactions. This method reads the current nonce for a given space directly from the blockchain. - * - * @param chainId The chain ID of the network to query. - * @param address The address of the wallet. - * @param space A unique identifier for a transaction category or flow, typically a large random number. - * @returns A promise that resolves to the `bigint` nonce for the given space. - */ - getNonce(chainId: number, address: Address.Address, space: bigint): Promise - - /** - * Checks if the wallet's on-chain configuration is up to date for a given chain. - * - * This method returns `true` if, on the specified chain, there are no pending configuration updates - * in the state tracker that have not yet been applied to the wallet. In other words, it verifies - * that the wallet's on-chain image hash matches the latest configuration image hash. - * - * @param wallet The address of the wallet to check. - * @param chainId The chain ID of the network to check against. - * @returns A promise that resolves to `true` if the wallet is up to date on the given chain, or `false` otherwise. - */ - isUpdatedOnchain(wallet: Address.Address, chainId: number): Promise -} - -export function isLoginToWalletArgs(args: LoginArgs): args is LoginToWalletArgs { - return 'wallet' in args -} - -export function isLoginToMnemonicArgs(args: LoginArgs): args is LoginToMnemonicArgs { - return 'kind' in args && args.kind === 'mnemonic' -} - -export function isLoginToPasskeyArgs(args: LoginArgs): args is LoginToPasskeyArgs { - return 'kind' in args && args.kind === 'passkey' -} - -export function isAuthCodeArgs(args: SignupArgs): args is AuthCodeSignupArgs { - return 'code' in args && 'commitment' in args -} - -export function isIdTokenArgs(args: SignupArgs): args is IdTokenSignupArgs { - return 'idToken' in args -} - -function addLoginSignerToSignupArgs(args: AddLoginSignerArgs): SignupArgs { - switch (args.kind) { - case 'mnemonic': - return { kind: 'mnemonic', mnemonic: args.mnemonic } - case 'email-otp': - return { kind: 'email-otp', email: args.email } - default: { - // google-id-token, apple-id-token, custom-* - const _args = args as { kind: string; idToken: string } - return { kind: _args.kind as IdTokenSignupArgs['kind'], idToken: _args.idToken } - } - } -} - -function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology { - const loginMemberWeight = 1n - - if (members.length === 0) { - // We need to maintain the general structure of the tree, so we can't have an empty node here - // instead, we add a dummy signer with weight 0 - return { - type: 'signer', - address: Constants.ZeroAddress, - weight: 0n, - } as Config.SignerLeaf - } - - if (members.length === 1) { - if (members[0]!.imageHash) { - return { - type: 'sapient-signer', - address: members[0]!.address, - imageHash: members[0]!.imageHash, - weight: loginMemberWeight, - } as Config.SapientSignerLeaf - } else { - return { - type: 'signer', - address: members[0]!.address, - weight: loginMemberWeight, - } as Config.SignerLeaf - } - } - - return { - type: 'nested', - weight: loginMemberWeight, - threshold: 1n, - tree: Config.flatLeavesToTopology( - members.map((member) => - member.imageHash - ? { - type: 'sapient-signer', - address: member.address, - imageHash: member.imageHash, - weight: 1n, - } - : { - type: 'signer', - address: member.address, - weight: 1n, - }, - ), - ), - } as Config.NestedLeaf -} - -// function buildCappedTreeFromTopology(weight: bigint, topology: Config.Topology): Config.Topology { -// // We may optimize this for some topology types -// // but it is not worth it, because the topology -// // that we will use for prod won't be optimizable -// return { -// type: 'nested', -// weight: weight, -// threshold: weight, -// tree: topology, -// } -// } - -function toConfig( - checkpoint: bigint, - loginTopology: Config.Topology, - devicesTopology: Config.Topology, - modules: Module[], - guardTopology?: Config.Topology, -): Config.Config { - if (!guardTopology) { - return { - checkpoint: checkpoint, - threshold: 1n, - topology: [[loginTopology, devicesTopology], toModulesTopology(modules)], - } - } else { - return { - checkpoint: checkpoint, - threshold: 2n, - topology: [[[loginTopology, devicesTopology], guardTopology], toModulesTopology(modules)], - } - } -} - -function toModulesTopology(modules: Module[]): Config.Topology { - // We always include a modules topology, even if there are no modules - // in that case we just add a signer with address 0 and no weight - if (modules.length === 0) { - return { - type: 'signer', - address: Constants.ZeroAddress, - weight: 0n, - } as Config.SignerLeaf - } - - const leaves = modules.map((module) => { - if (module.guardLeaf) { - return { - type: 'nested', - weight: module.weight, - threshold: module.sapientLeaf.weight + Config.getWeight(module.guardLeaf, () => true).maxWeight, - tree: [module.sapientLeaf, module.guardLeaf], - } as Config.NestedLeaf - } else { - return module.sapientLeaf - } - }) - - return Config.flatLeavesToTopology(leaves) -} - -function fromModulesTopology(topology: Config.Topology): Module[] { - let modules: Module[] = [] - - if (Config.isNode(topology)) { - modules = [...fromModulesTopology(topology[0]), ...fromModulesTopology(topology[1])] - } else if (Config.isSapientSignerLeaf(topology)) { - modules.push({ - sapientLeaf: topology, - weight: topology.weight, - }) - } else if ( - Config.isNestedLeaf(topology) && - Config.isNode(topology.tree) && - Config.isSapientSignerLeaf(topology.tree[0]) - ) { - modules.push({ - sapientLeaf: topology.tree[0], - weight: topology.weight, - guardLeaf: topology.tree[1], - }) - } else if (Config.isSignerLeaf(topology)) { - // Ignore non-sapient signers, as they are not modules - return [] - } else { - throw new Error('unknown-modules-topology-format') - } - - return modules -} - -function fromConfig(config: Config.Config): { - loginTopology: Config.Topology - devicesTopology: Config.Topology - modules: Module[] - guardTopology?: Config.Topology -} { - if (config.threshold === 1n) { - if (Config.isNode(config.topology) && Config.isNode(config.topology[0])) { - return { - loginTopology: config.topology[0][0], - devicesTopology: config.topology[0][1], - modules: fromModulesTopology(config.topology[1]), - } - } else { - throw new Error('unknown-config-format') - } - } else if (config.threshold === 2n) { - if ( - Config.isNode(config.topology) && - Config.isNode(config.topology[0]) && - Config.isNode(config.topology[0][0]) && - Config.isTopology(config.topology[0][1]) - ) { - return { - loginTopology: config.topology[0][0][0], - devicesTopology: config.topology[0][0][1], - guardTopology: config.topology[0][1], - modules: fromModulesTopology(config.topology[1]), - } - } else { - throw new Error('unknown-config-format') - } - } - - throw new Error('unknown-config-format') -} - -export class Wallets implements WalletsInterface { - private walletSelectionUiHandler: WalletSelectionUiHandler | null = null - - private pendingMnemonicOrPasskeyLogin?: typeof Kinds.LoginMnemonic | typeof Kinds.LoginPasskey - - constructor(private readonly shared: Shared) {} - - public async has(wallet: Address.Address): Promise { - return this.get(wallet).then((r) => r !== undefined) - } - - public async get(walletAddress: Address.Address): Promise { - // Fetch the checksummed version first, if it does not exist, try the lowercase version - const wallet = await this.shared.databases.manager.get(Address.checksum(walletAddress)) - if (wallet) { - return wallet - } - - return this.shared.databases.manager.get(walletAddress.toLowerCase() as `0x${string}`) - } - - public async list(): Promise { - return this.shared.databases.manager.list() - } - - public async listDevices(wallet: Address.Address): Promise { - const walletEntry = await this.get(wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - - const localDeviceAddress = walletEntry.device - - const { devices: deviceSigners } = await this.getConfiguration(wallet) - - return deviceSigners.map((signer) => ({ - address: signer.address, - isLocal: Address.isEqual(signer.address, localDeviceAddress), - })) - } - - public registerWalletSelector(handler: WalletSelectionUiHandler) { - if (this.walletSelectionUiHandler) { - throw new Error('wallet-selector-already-registered') - } - this.walletSelectionUiHandler = handler - return () => { - this.unregisterWalletSelector(handler) - } - } - - public unregisterWalletSelector(handler?: WalletSelectionUiHandler) { - if (handler && this.walletSelectionUiHandler !== handler) { - throw new Error('wallet-selector-not-registered') - } - this.walletSelectionUiHandler = null - } - - public onWalletsUpdate(cb: (wallets: Wallet[]) => void, trigger?: boolean) { - const undo = this.shared.databases.manager.addListener(() => { - this.list().then((wallets) => { - cb(wallets) - }) - }) - - if (trigger) { - this.list().then((wallets) => { - cb(wallets) - }) - } - - return undo - } - - private async prepareSignUp(args: SignupArgs): Promise<{ - signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable - extra: WitnessExtraSignerKind - loginEmail?: string - }> { - switch (args.kind) { - case 'passkey': { - const passkeySigner = await this.shared.passkeyProvider.create(this.shared.sequence.extensions, { - stateProvider: this.shared.sequence.stateProvider, - credentialName: args.name, - }) - this.shared.modules.logger.log('Created new passkey signer:', passkeySigner.address) - - return { - signer: passkeySigner, - extra: { - signerKind: Kinds.LoginPasskey, - }, - } - } - - case 'mnemonic': { - const mnemonicSigner = MnemonicHandler.toSigner(args.mnemonic) - if (!mnemonicSigner) { - throw new Error('invalid-mnemonic') - } - - this.shared.modules.logger.log('Created new mnemonic signer:', mnemonicSigner.address) - - return { - signer: mnemonicSigner, - extra: { - signerKind: Kinds.LoginMnemonic, - }, - } - } - - case 'email-otp': { - const handler = this.shared.handlers.get(Kinds.LoginEmailOtp) as OtpHandler - if (!handler) { - throw new Error('email-otp-handler-not-registered') - } - - const { signer: otpSigner, email: returnedEmail } = await handler.getSigner(args.email) - this.shared.modules.logger.log('Created new email otp signer:', otpSigner.address, 'Email:', returnedEmail) - - return { - signer: otpSigner, - extra: { - signerKind: Kinds.LoginEmailOtp, - }, - loginEmail: returnedEmail, - } - } - - case 'google-id-token': - case 'apple-id-token': { - const handler = getIdTokenSignupHandler( - this.shared, - args.kind === 'google-id-token' ? Kinds.LoginGoogle : Kinds.LoginApple, - ) - const [signer, metadata] = await handler.completeAuth(args.idToken) - const loginEmail = metadata.email - this.shared.modules.logger.log('Created new id token signer:', signer.address) - - return { - signer, - extra: { - signerKind: getSignerKindForSignup(args.kind), - }, - loginEmail, - } - } - - case 'google-pkce': - case 'apple': { - const handler = this.shared.handlers.get(getSignupHandlerKey(args.kind)) as AuthCodeHandler - if (!handler) { - throw new Error('handler-not-registered') - } - - const [signer, metadata] = await handler.completeAuth(args.commitment, args.code) - const loginEmail = metadata.email - this.shared.modules.logger.log('Created new auth code pkce signer:', signer.address) - - return { - signer, - extra: { - signerKind: getSignerKindForSignup(args.kind), - }, - loginEmail, - } - } - } - - if (args.kind.startsWith('custom-')) { - if (isIdTokenArgs(args)) { - const handler = getIdTokenSignupHandler(this.shared, args.kind) - const [signer, metadata] = await handler.completeAuth(args.idToken) - return { - signer, - extra: { - signerKind: args.kind, - }, - loginEmail: metadata.email, - } - } - - const handler = this.shared.handlers.get(args.kind) as AuthCodeHandler - if (!handler) { - throw new Error('handler-not-registered') - } - - const [signer, metadata] = await handler.completeAuth(args.commitment, args.code) - return { - signer, - extra: { - signerKind: args.kind, - }, - loginEmail: metadata.email, - } - } - - throw new Error('invalid-signup-kind') - } - - async startSignUpWithRedirect(args: StartSignUpWithRedirectArgs) { - const kind = getSignupHandlerKey(args.kind) - const handler = this.shared.handlers.get(kind) - if (!handler) { - throw new Error('handler-not-registered') - } - if (!(handler instanceof AuthCodeHandler)) { - throw new Error('handler-does-not-support-redirect') - } - return handler.commitAuth(args.target, { type: 'auth' }) - } - - async startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs) { - const walletEntry = await this.get(args.wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - if (walletEntry.status !== 'ready') { - throw new Error('wallet-not-ready') - } - - const kind = getSignupHandlerKey(args.kind) - const handler = this.shared.handlers.get(kind) - if (!handler) { - throw new Error('handler-not-registered') - } - if (!(handler instanceof AuthCodeHandler)) { - throw new Error('handler-does-not-support-redirect') - } - return handler.commitAuth(args.target, { type: 'add-signer', wallet: args.wallet }) - } - - async completeRedirect(args: CompleteRedirectArgs): Promise { - const commitment = await this.shared.databases.authCommitments.get(args.state) - if (!commitment) { - throw new Error('invalid-state') - } - - switch (commitment.type) { - case 'add-signer': { - const handlerKind = getSignupHandlerKey(commitment.kind) - const handler = this.shared.handlers.get(handlerKind) - if (!handler) { - throw new Error('handler-not-registered') - } - if (!(handler instanceof AuthCodeHandler)) { - throw new Error('handler-does-not-support-redirect') - } - - const walletAddress = commitment.wallet as Address.Address - const walletEntry = await this.get(walletAddress) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - if (walletEntry.status !== 'ready') { - throw new Error('wallet-not-ready') - } - - const [signer] = await handler.completeAuth(commitment, args.code) - const signerKind = getSignerKindForSignup(commitment.kind) - - await this.addLoginSignerFromPrepared(walletAddress, { - signer, - extra: { signerKind }, - }) - break - } - - case 'auth': { - await this.signUp({ - kind: commitment.kind, - commitment, - code: args.code, - noGuard: args.noGuard, - target: commitment.target, - isRedirect: true, - use4337: args.use4337, - }) - break - } - - case 'reauth': { - const handlerKind = getSignupHandlerKey(commitment.kind) - const handler = this.shared.handlers.get(handlerKind) - if (!handler) { - throw new Error('handler-not-registered') - } - if (!(handler instanceof AuthCodeHandler)) { - throw new Error('handler-does-not-support-redirect') - } - - await handler.completeAuth(commitment, args.code) - break - } - } - - if (!commitment.target) { - throw new Error('invalid-state') - } - - return commitment.target - } - - async signUp(args: SignupArgs): Promise { - const loginSigner = await this.prepareSignUp(args) - - args.onStatusChange?.({ type: 'login-signer-created', address: await loginSigner.signer.address }) - - // If there is an existing wallet callback, we check if any wallet already exist for this login signer - if (this.walletSelectionUiHandler) { - const existingWallets = await State.getWalletsFor(this.shared.sequence.stateProvider, loginSigner.signer) - - if (existingWallets.length > 0) { - for (const wallet of existingWallets) { - const preliminaryEntry: Wallet = { - address: wallet.wallet, - status: 'logging-in', - loginEmail: loginSigner.loginEmail, - loginType: loginSigner.extra.signerKind, - loginDate: new Date().toISOString(), - device: '' as `0x${string}`, - useGuard: false, - } - await this.shared.databases.manager.set(preliminaryEntry) - } - - const result = await this.walletSelectionUiHandler({ - existingWallets: existingWallets.map((w) => w.wallet), - signerAddress: await loginSigner.signer.address, - context: isAuthCodeArgs(args) ? { isRedirect: args.isRedirect, target: args.target } : { isRedirect: false }, - }) - - if (result === 'abort-signup') { - for (const wallet of existingWallets) { - const finalEntry = await this.shared.databases.manager.get(wallet.wallet) - if (finalEntry && !finalEntry.device) { - await this.shared.databases.manager.del(wallet.wallet) - } - } - - args.onStatusChange?.({ type: 'signup-aborted' }) - - // Abort the signup process - return undefined - } - - if (result === 'create-new') { - for (const wallet of existingWallets) { - await this.shared.databases.manager.del(wallet.wallet) - } - // Continue with the signup process - } else { - throw new Error('invalid-result-from-wallet-selector') - } - } - } else { - console.warn('No wallet selector registered, creating a new wallet') - } - - // Create the first session - const device = await this.shared.modules.devices.create() - - args.onStatusChange?.({ type: 'device-signer-created', address: device.address }) - - if (!args.noGuard && !this.shared.sequence.defaultGuardTopology) { - throw new Error('guard is required for signup') - } - - // Build the login tree - const loginSignerAddress = await loginSigner.signer.address - const loginTopology = buildCappedTree([ - { - address: loginSignerAddress, - imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined, - }, - ]) - const devicesTopology = buildCappedTree([{ address: device.address }]) - const walletGuardTopology = args.noGuard ? undefined : this.shared.modules.guards.topology('wallet') - const sessionsGuardTopology = args.noGuard ? undefined : this.shared.modules.guards.topology('sessions') - - // Add modules - const modules: Module[] = [] - - if (!args.noSessionManager) { - const identitySigners = [device.address] - if (!Signers.isSapientSigner(loginSigner.signer)) { - // Add non sapient login signer to the identity signers - identitySigners.unshift(loginSignerAddress) - } - await this.shared.modules.sessions.initSessionModule(modules, identitySigners, sessionsGuardTopology) - } - - if (!args.noRecovery) { - await this.shared.modules.recovery.initRecoveryModule(modules, device.address) - } - - // Create initial configuration - const initialConfiguration = toConfig(0n, loginTopology, devicesTopology, modules, walletGuardTopology) - console.log('initialConfiguration', initialConfiguration) - - // Create wallet - const context = args.use4337 ? this.shared.sequence.context4337 : this.shared.sequence.context - const wallet = await CoreWallet.fromConfiguration(initialConfiguration, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - context, - }) - - args.onStatusChange?.({ type: 'wallet-created', address: wallet.address }) - - this.shared.modules.logger.log('Created new sequence wallet:', wallet.address) - - // Sign witness using device signer - await this.shared.modules.devices.witness(device.address, wallet.address) - - // Sign witness using the passkey signer - await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet.address, loginSigner.extra) - - // Save entry in the manager db - const newWalletEntry = { - address: wallet.address, - status: 'ready' as const, - loginDate: new Date().toISOString(), - device: device.address, - loginType: loginSigner.extra.signerKind, - useGuard: !args.noGuard, - loginEmail: loginSigner.loginEmail, - } - - try { - await this.shared.databases.manager.set(newWalletEntry) - } catch (error) { - console.error('[Wallets/signUp] Error saving new wallet entry:', error, 'Entry was:', newWalletEntry) - // Re-throw the error if you want the operation to fail loudly, or handle it - throw error - } - - // Store passkey credential ID mapping if this is a passkey signup - if (args.kind === 'passkey' && this.isPasskeySigner(loginSigner.signer)) { - try { - await this.shared.databases.passkeyCredentials.saveCredential( - loginSigner.signer.credentialId, - loginSigner.signer.publicKey, - wallet.address, - ) - this.shared.modules.logger.log('Stored passkey credential mapping for wallet:', wallet.address) - } catch (error) { - console.error('[Wallets/signUp] Error saving passkey mapping:', error) - // Don't throw the error as this is not critical to the signup process - } - } - - args.onStatusChange?.({ type: 'signup-completed' }) - - return wallet.address - } - - public async getConfigurationParts(address: Address.Address) { - const wallet = new CoreWallet(address, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const status = await wallet.getStatus() - return fromConfig(status.configuration) - } - - public async requestConfigurationUpdate( - address: Address.Address, - changes: Partial>, - action: Action, - origin?: string, - ) { - const wallet = new CoreWallet(address, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const status = await wallet.getStatus() - const { loginTopology, devicesTopology, modules, guardTopology } = fromConfig(status.configuration) - - const nextLoginTopology = changes.loginTopology ?? loginTopology - const nextDevicesTopology = changes.devicesTopology ?? devicesTopology - const nextModules = changes.modules ?? modules - const nextGuardTopology = changes.guardTopology ?? guardTopology - - const envelope = await wallet.prepareUpdate( - toConfig( - status.configuration.checkpoint + 1n, - nextLoginTopology, - nextDevicesTopology, - nextModules, - nextGuardTopology, - ), - ) - - const requestId = await this.shared.modules.signatures.request(envelope, action, { - origin, - }) - - return requestId - } - - public async completeConfigurationUpdate(requestId: string) { - const request = await this.shared.modules.signatures.get(requestId) - if (!Payload.isConfigUpdate(request.envelope.payload)) { - throw new Error('invalid-request-payload') - } - - if (!Envelope.reachedThreshold(request.envelope)) { - throw new Error('insufficient-weight') - } - - const wallet = new CoreWallet(request.wallet, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - await wallet.submitUpdate(request.envelope as Envelope.Signed) - await this.shared.modules.signatures.complete(requestId) - } - - async login(args: LoginArgs): Promise { - if (isLoginToWalletArgs(args)) { - try { - const existingWallet = await this.get(args.wallet) - - if (existingWallet?.status === 'ready') { - throw new Error('wallet-already-logged-in') - } - - const device = await this.shared.modules.devices.create() - const { devicesTopology, modules, guardTopology } = await this.getConfigurationParts(args.wallet) - - // Witness the wallet - await this.shared.modules.devices.witness(device.address, args.wallet) - - // Add device to devices topology - const prevDevices = Config.getSigners(devicesTopology) - if (prevDevices.sapientSigners.length > 0) { - throw new Error('found-sapient-signer-in-devices-topology') - } - - if (!prevDevices.isComplete) { - throw new Error('devices-topology-incomplete') - } - - const nextDevicesTopology = buildCappedTree([ - ...prevDevices.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })), - ...prevDevices.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })), - { address: device.address }, - ]) - - if (this.shared.modules.recovery.hasRecoveryModule(modules)) { - await this.shared.modules.recovery.addRecoverySignerToModules(modules, device.address) - } - - if (this.shared.modules.sessions.hasSessionModule(modules)) { - await this.shared.modules.sessions.addIdentitySignerToModules(modules, device.address) - } - - const walletEntryToUpdate: Wallet = { - ...(existingWallet as Wallet), - address: args.wallet, - status: 'logging-in' as const, - loginDate: new Date().toISOString(), - device: device.address, - loginType: existingWallet?.loginType || this.pendingMnemonicOrPasskeyLogin || 'wallet', - loginEmail: existingWallet?.loginEmail, - useGuard: guardTopology !== undefined, - } - - await this.shared.databases.manager.set(walletEntryToUpdate) - - const requestId = await this.requestConfigurationUpdate( - args.wallet, - { - devicesTopology: nextDevicesTopology, - modules, - }, - 'login', - 'wallet-webapp', - ) - - this.shared.modules.signatures.onCancel(requestId, async (request) => { - this.shared.modules.logger.log('Login cancelled', request) - await this.shared.databases.manager.del(args.wallet) - }) - - return requestId - } finally { - this.pendingMnemonicOrPasskeyLogin = undefined - } - } - - if (isLoginToMnemonicArgs(args)) { - const mnemonicSigner = MnemonicHandler.toSigner(args.mnemonic) - if (!mnemonicSigner) { - throw new Error('invalid-mnemonic') - } - - const wallets = await State.getWalletsFor(this.shared.sequence.stateProvider, mnemonicSigner) - if (wallets.length === 0) { - throw new Error('no-wallets-found') - } - - const wallet = await args.selectWallet(wallets.map((w) => w.wallet)) - if (!wallets.some((w) => Address.isEqual(w.wallet, wallet))) { - throw new Error('wallet-not-found') - } - - // Ready the signer on the handler so it can be used to complete the login configuration update - const mnemonicHandler = this.shared.handlers.get(Kinds.LoginMnemonic) as MnemonicHandler - mnemonicHandler.addReadySigner(mnemonicSigner) - this.pendingMnemonicOrPasskeyLogin = Kinds.LoginMnemonic - - return this.login({ wallet }) - } - - if (isLoginToPasskeyArgs(args)) { - let passkeySigner: PasskeySigner - - if (args.credentialId) { - // Application-controlled login: use the provided credentialId - this.shared.modules.logger.log('Using provided credentialId for passkey login:', args.credentialId) - - const credential = await this.shared.databases.passkeyCredentials.getByCredentialId(args.credentialId) - if (!credential) { - throw new Error('credential-not-found') - } - - // Create passkey signer from stored credential - passkeySigner = this.shared.passkeyProvider.fromCredential({ - credentialId: credential.credentialId, - publicKey: credential.publicKey, - extensions: this.shared.sequence.extensions, - embedMetadata: false, - metadata: { credentialId: credential.credentialId }, - }) - } else { - // Default discovery behavior: use WebAuthn discovery - this.shared.modules.logger.log('No credentialId provided, using discovery method') - - const foundPasskeySigner = await this.shared.passkeyProvider.find( - this.shared.sequence.stateProvider, - this.shared.sequence.extensions, - ) - if (!foundPasskeySigner) { - throw new Error('no-passkey-found') - } - passkeySigner = foundPasskeySigner - } - - const wallets = await State.getWalletsFor(this.shared.sequence.stateProvider, passkeySigner) - if (wallets.length === 0) { - throw new Error('no-wallets-found') - } - - const wallet = await args.selectWallet(wallets.map((w) => w.wallet)) - if (!wallets.some((w) => Address.isEqual(w.wallet, wallet))) { - throw new Error('wallet-not-found') - } - - // Store discovered credential - try { - const existingCredential = await this.shared.databases.passkeyCredentials.getByCredentialId( - passkeySigner.credentialId, - ) - - if (!existingCredential) { - await this.shared.databases.passkeyCredentials.saveCredential( - passkeySigner.credentialId, - passkeySigner.publicKey, - wallet, - ) - } else { - await this.shared.databases.passkeyCredentials.updateCredential(passkeySigner.credentialId, { - lastLoginAt: new Date().toISOString(), - walletAddress: wallet, - }) - } - } catch (error) { - // Don't fail login if credential storage fails - this.shared.modules.logger.log('Failed to store discovered passkey credential:', error) - } - - // Store the passkey signer for later use during signing - const passkeysHandler = this.shared.handlers.get(Kinds.LoginPasskey) as PasskeysHandler - passkeysHandler.addReadySigner(passkeySigner) - - this.pendingMnemonicOrPasskeyLogin = Kinds.LoginPasskey - - return this.login({ wallet }) - } - - throw new Error('invalid-login-args') - } - - private isPasskeySigner(signer: unknown): signer is PasskeySigner { - const guard = this.shared.passkeyProvider.isSigner - if (guard) { - return guard(signer) - } - return ( - typeof signer === 'object' && - signer !== null && - 'credentialId' in signer && - 'publicKey' in signer && - 'imageHash' in signer - ) - } - - async completeLogin(requestId: string) { - const request = await this.shared.modules.signatures.get(requestId) - - const walletEntry = await this.shared.databases.manager.get(request.wallet) - if (!walletEntry) { - throw new Error('login-for-wallet-not-found') - } - - await this.completeConfigurationUpdate(requestId) - - await this.shared.databases.manager.set({ - ...walletEntry, - status: 'ready', - loginDate: new Date().toISOString(), - }) - } - - async addLoginSigner(args: AddLoginSignerArgs): Promise { - const walletEntry = await this.get(args.wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - if (walletEntry.status !== 'ready') { - throw new Error('wallet-not-ready') - } - - const signupArgs = addLoginSignerToSignupArgs(args) - const loginSigner = await this.prepareSignUp(signupArgs) - return this.addLoginSignerFromPrepared(args.wallet, loginSigner) - } - - async completeAddLoginSigner(requestId: string): Promise { - const request = await this.shared.modules.signatures.get(requestId) - if (request.action !== Actions.AddLoginSigner) { - throw new Error('invalid-request-action') - } - await this.completeConfigurationUpdate(requestId) - } - - async removeLoginSigner(args: RemoveLoginSignerArgs): Promise { - const walletEntry = await this.get(args.wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - if (walletEntry.status !== 'ready') { - throw new Error('wallet-not-ready') - } - - const { loginTopology, modules } = await this.getConfigurationParts(args.wallet) - - const existingSigners = Config.getSigners(loginTopology) - const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] - - if (!allExistingAddresses.some((addr) => Address.isEqual(addr, args.signerAddress))) { - throw new Error('signer-not-found') - } - - const remainingMembers = [ - ...existingSigners.signers - .filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, args.signerAddress)) - .map((x) => ({ address: x })), - ...existingSigners.sapientSigners - .filter((x) => !Address.isEqual(x.address, args.signerAddress)) - .map((x) => ({ address: x.address, imageHash: x.imageHash })), - ] - - if (remainingMembers.length < 1) { - throw new Error('cannot-remove-last-login-signer') - } - - const nextLoginTopology = buildCappedTree(remainingMembers) - - if (this.shared.modules.sessions.hasSessionModule(modules)) { - await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, args.signerAddress) - } - - if (this.shared.modules.recovery.hasRecoveryModule(modules)) { - await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, args.signerAddress) - } - - const requestId = await this.requestConfigurationUpdate( - args.wallet, - { loginTopology: nextLoginTopology, modules }, - Actions.RemoveLoginSigner, - 'wallet-webapp', - ) - - return requestId - } - - async completeRemoveLoginSigner(requestId: string): Promise { - const request = await this.shared.modules.signatures.get(requestId) - if (request.action !== Actions.RemoveLoginSigner) { - throw new Error('invalid-request-action') - } - await this.completeConfigurationUpdate(requestId) - } - - async logout( - wallet: Address.Address, - options?: T, - ): Promise { - const walletEntry = await this.shared.databases.manager.get(wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - - if (options?.skipRemoveDevice) { - await Promise.all([ - this.shared.databases.manager.del(wallet), - this.shared.modules.devices.remove(walletEntry.device), - ]) - return undefined as any - } - - // Prevent starting logout if already logging out or not ready - if (walletEntry.status !== 'ready') { - console.warn(`Logout called on wallet ${wallet} with status ${walletEntry.status}. Aborting.`) - throw new Error(`Wallet is not in 'ready' state for logout (current: ${walletEntry.status})`) - } - - const device = await this.shared.modules.devices.get(walletEntry.device) - if (!device) { - throw new Error('device-not-found') - } - - const requestId = await this._prepareDeviceRemovalUpdate(wallet, device.address, 'logout') - - await this.shared.databases.manager.set({ ...walletEntry, status: 'logging-out' }) - - return requestId as any - } - - public async remoteLogout(wallet: Address.Address, deviceAddress: Address.Address): Promise { - const walletEntry = await this.get(wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - - if (Address.isEqual(walletEntry.device, deviceAddress)) { - throw new Error('cannot-remote-logout-from-local-device') - } - - const requestId = await this._prepareDeviceRemovalUpdate(wallet, deviceAddress, 'remote-logout') - - return requestId - } - - async completeLogout(requestId: string, _options?: { skipValidateSave?: boolean }) { - const request = await this.shared.modules.signatures.get(requestId) - const walletEntry = await this.shared.databases.manager.get(request.wallet) - if (!walletEntry) { - throw new Error('wallet-not-found') - } - - // Wallet entry should ideally be 'logging-out' here, but we proceed regardless - if (walletEntry.status !== 'logging-out') { - this.shared.modules.logger.log( - `Warning: Wallet ${request.wallet} status was ${walletEntry.status} during completeLogout.`, - ) - } - - await this.completeConfigurationUpdate(requestId) - await this.shared.databases.manager.del(request.wallet) - await this.shared.modules.devices.remove(walletEntry.device) - } - - async getConfiguration(wallet: Address.Address) { - const walletObject = new CoreWallet(wallet, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const status = await walletObject.getStatus() - const raw = fromConfig(status.configuration) - - const deviceSigners = Config.getSigners(raw.devicesTopology) - const loginSigners = Config.getSigners(raw.loginTopology) - - const walletGuardSigners = raw.guardTopology ? Config.getSigners(raw.guardTopology) : undefined - - const moduleGuards = ( - await Promise.all( - raw.modules - .filter((m) => m.guardLeaf) - .map((m) => ({ moduleAddress: m.sapientLeaf.address, guardSigners: Config.getSigners(m.guardLeaf!).signers })) - .filter(({ guardSigners }) => guardSigners && guardSigners.length > 0) - .map(async ({ moduleAddress, guardSigners }) => ({ - moduleAddress, - guardSigners: await this.shared.modules.signers.resolveKinds(wallet, guardSigners), - })), - ) - ) - .filter(({ guardSigners }) => guardSigners && guardSigners.length > 0) - .map(({ moduleAddress, guardSigners }) => [moduleAddress, guardSigners[0]]) as [Address.Address, SignerWithKind][] - - return { - devices: await this.shared.modules.signers.resolveKinds(wallet, [ - ...deviceSigners.signers, - ...deviceSigners.sapientSigners, - ]), - login: await this.shared.modules.signers.resolveKinds(wallet, [ - ...loginSigners.signers, - ...loginSigners.sapientSigners, - ]), - walletGuard: - walletGuardSigners && walletGuardSigners.signers.length > 0 - ? (await this.shared.modules.signers.resolveKinds(wallet, walletGuardSigners.signers))[0] - : undefined, - moduleGuards: new Map<`0x${string}`, SignerWithKind>(moduleGuards), - raw, - } - } - - async getNonce(chainId: number, address: Address.Address, space: bigint) { - const wallet = new CoreWallet(address, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const network = this.shared.sequence.networks.find((n) => n.chainId === chainId) - if (!network) { - throw new Error('network-not-found') - } - - const provider = Provider.from(RpcTransport.fromHttp(network.rpcUrl)) - return wallet.getNonce(provider, space) - } - - async getOnchainConfiguration(wallet: Address.Address, chainId: number) { - const walletObject = new CoreWallet(wallet, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const network = this.shared.sequence.networks.find((n) => n.chainId === chainId) - if (!network) { - throw new Error('network-not-found') - } - - const provider = Provider.from(RpcTransport.fromHttp(network.rpcUrl)) - const status = await walletObject.getStatus(provider) - - const onchainConfiguration = await this.shared.sequence.stateProvider.getConfiguration(status.onChainImageHash) - if (!onchainConfiguration) { - throw new Error('onchain-configuration-not-found') - } - - const raw = fromConfig(status.configuration) - - const deviceSigners = Config.getSigners(raw.devicesTopology) - const loginSigners = Config.getSigners(raw.loginTopology) - - const guardSigners = raw.guardTopology ? Config.getSigners(raw.guardTopology) : undefined - - return { - devices: await this.shared.modules.signers.resolveKinds(wallet, [ - ...deviceSigners.signers, - ...deviceSigners.sapientSigners, - ]), - login: await this.shared.modules.signers.resolveKinds(wallet, [ - ...loginSigners.signers, - ...loginSigners.sapientSigners, - ]), - guard: guardSigners - ? await this.shared.modules.signers.resolveKinds(wallet, [ - ...guardSigners.signers, - ...guardSigners.sapientSigners, - ]) - : [], - raw, - } - } - - async isUpdatedOnchain(wallet: Address.Address, chainId: number) { - const walletObject = new CoreWallet(wallet, { - stateProvider: this.shared.sequence.stateProvider, - guest: this.shared.sequence.guest, - }) - - const network = this.shared.sequence.networks.find((n) => n.chainId === chainId) - if (!network) { - throw new Error('network-not-found') - } - - const provider = Provider.from(RpcTransport.fromHttp(network.rpcUrl)) - const onchainStatus = await walletObject.getStatus(provider) - return onchainStatus.imageHash === onchainStatus.onChainImageHash - } - - private async _prepareDeviceRemovalUpdate( - wallet: Address.Address, - deviceToRemove: Address.Address, - action: 'logout' | 'remote-logout', - ): Promise { - const { devicesTopology, modules } = await this.getConfigurationParts(wallet) - - // The result of this entire inner block is a clean, simple list of the remaining devices, ready to be rebuilt. - const nextDevicesTopology = buildCappedTree([ - ...Config.getSigners(devicesTopology) - .signers.filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, deviceToRemove)) - .map((x) => ({ address: x })), - ...Config.getSigners(devicesTopology).sapientSigners, - ]) - - // Remove the device from the recovery module's topology as well. - if (this.shared.modules.recovery.hasRecoveryModule(modules)) { - await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, deviceToRemove) - } - - // Remove the device from the session module's topology as well. - if (this.shared.modules.sessions.hasSessionModule(modules)) { - await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, deviceToRemove) - } - - // Request the configuration update. - const requestId = await this.requestConfigurationUpdate( - wallet, - { - devicesTopology: nextDevicesTopology, - modules, - }, - action, - 'wallet-webapp', - ) - - return requestId - } - - private async addLoginSignerFromPrepared( - wallet: Address.Address, - loginSigner: { - signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable - extra: WitnessExtraSignerKind - }, - ): Promise { - const newSignerAddress = await loginSigner.signer.address - - const { loginTopology, modules } = await this.getConfigurationParts(wallet) - - // Check for duplicate signer - const existingSigners = Config.getSigners(loginTopology) - const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] - if (allExistingAddresses.some((addr) => Address.isEqual(addr, newSignerAddress))) { - throw new Error('signer-already-exists') - } - - // Build new login topology with the additional signer - const existingMembers = [ - ...existingSigners.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })), - ...existingSigners.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })), - ] - const newMember = { - address: newSignerAddress, - imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined, - } - const nextLoginTopology = buildCappedTree([...existingMembers, newMember]) - - // Add non-sapient login signer to sessions module identity signers - if (!Signers.isSapientSigner(loginSigner.signer) && this.shared.modules.sessions.hasSessionModule(modules)) { - await this.shared.modules.sessions.addIdentitySignerToModules(modules, newSignerAddress) - } - - // Add to recovery module if present - if (this.shared.modules.recovery.hasRecoveryModule(modules)) { - await this.shared.modules.recovery.addRecoverySignerToModules(modules, newSignerAddress) - } - - // Witness so the wallet becomes discoverable via the new credential - await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet, loginSigner.extra) - - return this.requestConfigurationUpdate( - wallet, - { loginTopology: nextLoginTopology, modules }, - Actions.AddLoginSigner, - 'wallet-webapp', - ) - } -} diff --git a/packages/wallet/wdk/test/authcode-pkce.test.ts b/packages/wallet/wdk/test/authcode-pkce.test.ts deleted file mode 100644 index d9b46bd87b..0000000000 --- a/packages/wallet/wdk/test/authcode-pkce.test.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import * as Identity from '@0xsequence/identity-instrument' -import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce.js' -import { Signatures } from '../src/sequence/signatures.js' -import * as Db from '../src/dbs/index.js' -import { IdentitySigner } from '../src/identity/signer.js' - -describe('AuthCodePkceHandler', () => { - let handler: AuthCodePkceHandler - let mockNitroInstrument: Identity.IdentityInstrument - let mockSignatures: Signatures - let mockCommitments: Db.AuthCommitments - let mockAuthKeys: Db.AuthKeys - let mockIdentitySigner: IdentitySigner - - beforeEach(() => { - vi.clearAllMocks() - - // Mock IdentityInstrument - mockNitroInstrument = { - commitVerifier: vi.fn(), - completeAuth: vi.fn(), - } as unknown as Identity.IdentityInstrument - - // Mock Signatures - mockSignatures = { - addSignature: vi.fn(), - } as unknown as Signatures - - // Mock AuthCommitments database - mockCommitments = { - set: vi.fn(), - get: vi.fn(), - del: vi.fn(), - list: vi.fn(), - } as unknown as Db.AuthCommitments - - // Mock AuthKeys database - mockAuthKeys = { - set: vi.fn(), - get: vi.fn(), - del: vi.fn(), - delBySigner: vi.fn(), - getBySigner: vi.fn(), - addListener: vi.fn(), - } as unknown as Db.AuthKeys - - // Mock IdentitySigner - mockIdentitySigner = { - address: '0x1234567890123456789012345678901234567890', - sign: vi.fn(), - } as unknown as IdentitySigner - - // Create handler instance - handler = new AuthCodePkceHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - 'test-google-client-id', - mockNitroInstrument, - mockSignatures, - mockCommitments, - mockAuthKeys, - ) - - // Set redirect URI for tests - handler.setRedirectUri('https://example.com/auth/callback') - - // Mock inherited methods - vi.spyOn(handler as any, 'nitroCommitVerifier').mockImplementation(async () => { - return { - verifier: 'mock-verifier-code', - loginHint: 'user@example.com', - challenge: 'mock-challenge-hash', - } - }) - - vi.spyOn(handler as any, 'nitroCompleteAuth').mockImplementation(async () => { - return { - signer: mockIdentitySigner, - email: 'user@example.com', - } - }) - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - describe('commitAuth', () => { - it('Should create Google PKCE auth commitment and return OAuth URL', async () => { - const target = 'https://example.com/success' - - const result = await handler.commitAuth(target, { type: 'auth' }) - - // Verify nitroCommitVerifier was called with correct challenge - expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( - expect.objectContaining({ - issuer: 'https://accounts.google.com', - audience: 'test-google-client-id', - }), - ) - - // Verify commitment was saved - expect(mockCommitments.set).toHaveBeenCalledWith({ - id: expect.any(String), - kind: 'google-pkce', - verifier: 'mock-verifier-code', - challenge: 'mock-challenge-hash', - target, - metadata: {}, - type: 'auth', - }) - - // Verify OAuth URL is constructed correctly - expect(result).toMatch(/^https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?/) - expect(result).toContain('code_challenge=mock-challenge-hash') - expect(result).toContain('code_challenge_method=S256') - expect(result).toContain('client_id=test-google-client-id') - expect(result).toContain('redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback') - expect(result).toContain('login_hint=user%40example.com') - expect(result).toContain('response_type=code') - expect(result).toContain('scope=openid+profile+email') // + is valid URL encoding for spaces - expect(result).toContain('state=') - }) - - it('Should use provided state instead of generating random one', async () => { - const target = 'https://example.com/success' - const customState = 'custom-state-123' - - const result = await handler.commitAuth(target, { - type: 'reauth', - state: customState, - signer: '0x1234567890123456789012345678901234567890', - }) - - // Verify commitment was saved with custom state - expect(mockCommitments.set).toHaveBeenCalledWith({ - id: customState, - kind: 'google-pkce', - verifier: 'mock-verifier-code', - challenge: 'mock-challenge-hash', - target, - metadata: {}, - type: 'reauth', - signer: '0x1234567890123456789012345678901234567890', - }) - - // Verify URL contains custom state - expect(result).toContain(`state=${customState}`) - }) - - it('Should include signer in challenge when provided', async () => { - const target = 'https://example.com/success' - const signer = '0x9876543210987654321098765432109876543210' - - await handler.commitAuth(target, { type: 'reauth', state: 'test-state', signer }) - - // Verify nitroCommitVerifier was called with signer in challenge - expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( - expect.objectContaining({ - signer: { address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }, - }), - ) - }) - - it('Should generate random state when not provided', async () => { - const target = 'https://example.com/success' - - const result = await handler.commitAuth(target, { type: 'auth' }) - - // Verify that a state parameter is present and looks like a hex string - expect(result).toMatch(/state=0x[a-f0-9]+/) - expect(mockCommitments.set).toHaveBeenCalledWith( - expect.objectContaining({ - id: expect.stringMatching(/^0x[a-f0-9]+$/), - }), - ) - }) - - it('Should handle different signup and login scenarios', async () => { - const target = 'https://example.com/success' - - // Test signup - await handler.commitAuth(target, { type: 'auth' }) - expect(mockCommitments.set).toHaveBeenLastCalledWith( - expect.objectContaining({ - type: 'auth', - }), - ) - - // Test login - await handler.commitAuth(target, { - type: 'reauth', - state: 'test-state', - signer: '0x1234567890123456789012345678901234567890', - }) - expect(mockCommitments.set).toHaveBeenLastCalledWith( - expect.objectContaining({ - type: 'reauth', - }), - ) - }) - - it('Should handle errors from nitroCommitVerifier', async () => { - vi.spyOn(handler as any, 'nitroCommitVerifier').mockRejectedValue(new Error('Nitro service unavailable')) - - await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( - 'Nitro service unavailable', - ) - }) - - it('Should handle database errors during commitment storage', async () => { - vi.mocked(mockCommitments.set).mockRejectedValue(new Error('Database write failed')) - - await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( - 'Database write failed', - ) - }) - }) - - describe('completeAuth', () => { - let mockCommitment: Db.AuthCommitment - - beforeEach(() => { - mockCommitment = { - id: 'test-commitment-123', - kind: 'google-pkce', - verifier: 'test-verifier-code', - challenge: 'test-challenge-hash', - target: 'https://example.com/success', - metadata: { scope: 'openid profile email' }, - type: 'auth', - } - }) - - it('Should complete auth and return signer with metadata', async () => { - const authCode = 'auth-code-from-google' - - const result = await handler.completeAuth(mockCommitment, authCode) - - // Verify nitroCompleteAuth was called with correct challenge - expect(handler['nitroCompleteAuth']).toHaveBeenCalledWith( - expect.objectContaining({ - verifier: 'test-verifier-code', - authCode: authCode, - }), - ) - - // Verify commitment was deleted - expect(mockCommitments.del).toHaveBeenCalledWith(mockCommitment.id) - - // Verify return value - expect(result).toEqual([ - mockIdentitySigner, - { - scope: 'openid profile email', - email: 'user@example.com', - }, - ]) - }) - - it('Should merge commitment metadata with email from auth response', async () => { - mockCommitment.metadata = { - customField: 'customValue', - scope: 'openid profile email', - } - - const result = await handler.completeAuth(mockCommitment, 'auth-code') - - expect(result[1]).toEqual({ - customField: 'customValue', - scope: 'openid profile email', - email: 'user@example.com', - }) - }) - - it('Should throw error when verifier is missing from commitment', async () => { - const invalidCommitment = { - ...mockCommitment, - verifier: undefined, - } - - await expect(handler.completeAuth(invalidCommitment, 'auth-code')).rejects.toThrow( - 'Missing verifier in commitment', - ) - - // Verify nitroCompleteAuth was not called - expect(handler['nitroCompleteAuth']).not.toHaveBeenCalled() - }) - - it('Should handle errors from nitroCompleteAuth', async () => { - vi.spyOn(handler as any, 'nitroCompleteAuth').mockRejectedValue(new Error('Invalid auth code')) - - await expect(handler.completeAuth(mockCommitment, 'invalid-code')).rejects.toThrow('Invalid auth code') - - // Verify commitment was not deleted on error - expect(mockCommitments.del).not.toHaveBeenCalled() - }) - - it('Should handle database errors during commitment deletion', async () => { - vi.mocked(mockCommitments.del).mockRejectedValue(new Error('Database delete failed')) - - // nitroCompleteAuth should succeed, but del should fail - await expect(handler.completeAuth(mockCommitment, 'auth-code')).rejects.toThrow('Database delete failed') - }) - - it('Should work with empty metadata', async () => { - mockCommitment.metadata = {} - - const result = await handler.completeAuth(mockCommitment, 'auth-code') - - expect(result[1]).toEqual({ - email: 'user@example.com', - }) - }) - - it('Should preserve all existing metadata fields', async () => { - mockCommitment.metadata = { - sessionId: 'session-123', - returnUrl: '/dashboard', - userAgent: 'Chrome/123', - } - - const result = await handler.completeAuth(mockCommitment, 'auth-code') - - expect(result[1]).toEqual({ - sessionId: 'session-123', - returnUrl: '/dashboard', - userAgent: 'Chrome/123', - email: 'user@example.com', - }) - }) - }) - - describe('Integration and Edge Cases', () => { - it('Should have correct kind property', () => { - expect(handler.kind).toBe('login-google') - }) - - it('Should handle redirect URI configuration', () => { - const newRedirectUri = 'https://newdomain.com/callback' - handler.setRedirectUri(newRedirectUri) - - return handler.commitAuth('https://example.com/success', { type: 'auth' }).then((result) => { - expect(result).toContain(`redirect_uri=${encodeURIComponent(newRedirectUri)}`) - }) - }) - - it('Should work with different issuer and audience configurations', () => { - const customHandler = new AuthCodePkceHandler( - 'custom-provider', - 'https://custom-issuer.com', - 'https://custom-issuer.com/o/oauth2/v2/auth', - 'custom-client-id', - mockNitroInstrument, - mockSignatures, - mockCommitments, - mockAuthKeys, - ) - - expect(customHandler['issuer']).toBe('https://custom-issuer.com') - expect(customHandler['audience']).toBe('custom-client-id') - expect(customHandler.signupKind).toBe('custom-provider') - }) - }) -}) diff --git a/packages/wallet/wdk/test/authcode.test.ts b/packages/wallet/wdk/test/authcode.test.ts deleted file mode 100644 index f673f8d42e..0000000000 --- a/packages/wallet/wdk/test/authcode.test.ts +++ /dev/null @@ -1,731 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { IdentityInstrument, IdentityType, KeyType, AuthCodeChallenge } from '@0xsequence/identity-instrument' -import { AuthCodeHandler } from '../src/sequence/handlers/authcode.js' -import { Signatures } from '../src/sequence/signatures.js' -import * as Db from '../src/dbs/index.js' -import { IdentitySigner } from '../src/identity/signer.js' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' - -// Mock the global crypto API -const mockCryptoSubtle = { - sign: vi.fn(), - generateKey: vi.fn(), - exportKey: vi.fn(), -} - -Object.defineProperty(global, 'window', { - value: { - crypto: { - subtle: mockCryptoSubtle, - }, - location: { - pathname: '/test-path', - href: '', - }, - }, - writable: true, -}) - -// Mock URLSearchParams -class MockURLSearchParams { - private params: Record = {} - - constructor(params?: Record) { - if (params) { - this.params = { ...params } - } - } - - toString() { - return Object.entries(this.params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&') - } -} - -// Override global URLSearchParams for testing -globalThis.URLSearchParams = MockURLSearchParams as any - -// Mock dependencies with proper vi.fn() types -const mockCommitVerifier = vi.fn() -const mockCompleteAuth = vi.fn() -const mockAddSignature = vi.fn() -const mockAuthCommitmentsSet = vi.fn() -const mockAuthCommitmentsGet = vi.fn() -const mockAuthCommitmentsDel = vi.fn() -const mockGetBySigner = vi.fn() -const mockDelBySigner = vi.fn() -const mockAuthKeysSet = vi.fn() -const mockAddListener = vi.fn() - -const mockIdentityInstrument = { - commitVerifier: mockCommitVerifier, - completeAuth: mockCompleteAuth, -} as unknown as IdentityInstrument - -const mockSignatures = { - addSignature: mockAddSignature, -} as unknown as Signatures - -const mockAuthCommitments = { - set: mockAuthCommitmentsSet, - get: mockAuthCommitmentsGet, - del: mockAuthCommitmentsDel, -} as unknown as Db.AuthCommitments - -const mockAuthKeys = { - getBySigner: mockGetBySigner, - delBySigner: mockDelBySigner, - set: mockAuthKeysSet, - addListener: mockAddListener, -} as unknown as Db.AuthKeys - -describe('AuthCodeHandler', () => { - let authCodeHandler: AuthCodeHandler - let testWallet: Address.Address - let testCommitment: Db.AuthCommitment - let testRequest: BaseSignatureRequest - - beforeEach(() => { - vi.clearAllMocks() - - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - - // Create mock CryptoKey - const mockCryptoKey = { - algorithm: { name: 'ECDSA', namedCurve: 'P-256' }, - extractable: false, - type: 'private', - usages: ['sign'], - } as CryptoKey - - mockCryptoSubtle.generateKey.mockResolvedValue({ - publicKey: {} as CryptoKey, - privateKey: mockCryptoKey, - }) - - mockCryptoSubtle.exportKey.mockResolvedValue(new ArrayBuffer(64)) - - testCommitment = { - id: 'test-state-123', - kind: 'google-pkce', - metadata: {}, - target: '/test-target', - type: 'reauth', - signer: testWallet, - } - - testRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: Payload.fromMessage(Hex.fromString('Test message')), - }, - } as BaseSignatureRequest - - authCodeHandler = new AuthCodeHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - // === CONSTRUCTOR AND PROPERTIES === - - describe('Constructor', () => { - it('Should create AuthCodeHandler with Google PKCE configuration', () => { - const handler = new AuthCodeHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - 'google-client-id', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - expect(handler.signupKind).toBe('google-pkce') - expect(handler.issuer).toBe('https://accounts.google.com') - expect(handler.audience).toBe('google-client-id') - expect(handler.identityType).toBe(IdentityType.OIDC) - }) - - it('Should create AuthCodeHandler with Apple configuration', () => { - const handler = new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - 'apple-client-id', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - expect(handler.signupKind).toBe('apple') - expect(handler.issuer).toBe('https://appleid.apple.com') - expect(handler.audience).toBe('apple-client-id') - }) - - it('Should initialize with empty redirect URI', () => { - expect(authCodeHandler['redirectUri']).toBe('') - }) - }) - - // === KIND GETTER === - - describe('kind getter', () => { - it('Should return login-google for Google PKCE handler', () => { - const googleHandler = new AuthCodeHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - expect(googleHandler.kind).toBe('login-google') - }) - - it('Should return login-apple for Apple handler', () => { - const appleHandler = new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - expect(appleHandler.kind).toBe('login-apple') - }) - }) - - // === REDIRECT URI MANAGEMENT === - - describe('setRedirectUri()', () => { - it('Should set redirect URI', () => { - const testUri = 'https://example.com/callback' - - authCodeHandler.setRedirectUri(testUri) - - expect(authCodeHandler['redirectUri']).toBe(testUri) - }) - - it('Should update redirect URI when called multiple times', () => { - authCodeHandler.setRedirectUri('https://first.com/callback') - authCodeHandler.setRedirectUri('https://second.com/callback') - - expect(authCodeHandler['redirectUri']).toBe('https://second.com/callback') - }) - }) - - // === COMMIT AUTH FLOW === - - describe('commitAuth()', () => { - beforeEach(() => { - authCodeHandler.setRedirectUri('https://example.com/callback') - }) - - it('Should create auth commitment and return OAuth URL', async () => { - const target = '/test-target' - - const result = await authCodeHandler.commitAuth(target, { type: 'auth' }) - - // Verify commitment was saved - expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce() - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - - expect(commitmentCall.kind).toBe('google-pkce') - expect(commitmentCall.target).toBe(target) - expect(commitmentCall.metadata).toEqual({}) - expect(commitmentCall.type).toBe('auth') - expect(commitmentCall.id).toBeDefined() - expect(typeof commitmentCall.id).toBe('string') - - // Verify OAuth URL structure - expect(result).toContain('https://accounts.google.com/o/oauth2/v2/auth?') - expect(result).toContain('client_id=test-audience') - expect(result).toContain('redirect_uri=https%3A%2F%2Fexample.com%2Fcallback') // Fix URL encoding - expect(result).toContain('response_type=code') - expect(result).toContain('scope=openid') - expect(result).toContain(`state=${commitmentCall.id}`) - }) - - it('Should use provided state parameter', async () => { - const customState = 'custom-state-123' - - const result = await authCodeHandler.commitAuth('/target', { - type: 'reauth', - state: customState, - signer: testWallet, - }) - - // Verify commitment uses custom state - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(commitmentCall.id).toBe(customState) - expect(result).toContain(`state=${customState}`) - }) - - it('Should generate random state when not provided', async () => { - await authCodeHandler.commitAuth('/target', { type: 'auth' }) - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(commitmentCall.id).toBeDefined() - expect(typeof commitmentCall.id).toBe('string') - expect(commitmentCall.id.startsWith('0x')).toBe(true) - expect(commitmentCall.id.length).toBe(66) // 0x + 64 hex chars - }) - - it('Should handle Apple OAuth URL', async () => { - const appleHandler = new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - 'apple-client-id', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - appleHandler.setRedirectUri('https://example.com/callback') - - const result = await appleHandler.commitAuth('/target', { type: 'auth' }) - - expect(result).toContain('https://appleid.apple.com/auth/authorize?') - expect(result).toContain('client_id=apple-client-id') - const resultUrl = new URL(result) - expect(resultUrl.searchParams.has('scope')).toBe(false) - }) - - it('Should create commitment without signer', async () => { - await authCodeHandler.commitAuth('/target', { type: 'auth' }) - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(commitmentCall.signer).toBeUndefined() - expect(commitmentCall.type).toBe('auth') - }) - }) - - // === COMPLETE AUTH FLOW === - - describe('completeAuth()', () => { - it('Should complete auth flow with code and return signer', async () => { - const authCode = 'test-auth-code-123' - // const mockSigner = {} as IdentitySigner - const mockEmail = 'test@example.com' - - mockCommitVerifier.mockResolvedValueOnce(undefined) - mockCompleteAuth.mockResolvedValueOnce({ - signer: { address: testWallet }, - identity: { email: mockEmail }, - }) - - // Mock getAuthKey to return a key for the commitVerifier and completeAuth calls - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - const [signer, metadata] = await authCodeHandler.completeAuth(testCommitment, authCode) - - // Verify commitVerifier was called - expect(mockCommitVerifier).toHaveBeenCalledOnce() - const commitVerifierCall = mockCommitVerifier.mock.calls[0]! - expect(commitVerifierCall[1]).toBeInstanceOf(AuthCodeChallenge) - - // Verify completeAuth was called - expect(mockCompleteAuth).toHaveBeenCalledOnce() - const completeAuthCall = mockCompleteAuth.mock.calls[0]! - expect(completeAuthCall[1]).toBeInstanceOf(AuthCodeChallenge) - - // Verify results - expect(signer).toBeInstanceOf(IdentitySigner) - expect(metadata.email).toBe(mockEmail) - }) - - it('Should complete auth flow with existing signer', async () => { - const authCode = 'test-auth-code-123' - const commitmentWithSigner = { ...testCommitment, signer: testWallet } - - mockCommitVerifier.mockResolvedValueOnce(undefined) - mockCompleteAuth.mockResolvedValueOnce({ - signer: { address: testWallet }, - identity: { email: 'test@example.com' }, - }) - - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - const [signer, metadata] = await authCodeHandler.completeAuth(commitmentWithSigner, authCode) - - expect(signer).toBeDefined() - expect(metadata.email).toBe('test@example.com') - }) - - it('Should handle commitVerifier failure', async () => { - const authCode = 'test-auth-code-123' - - mockGetBySigner.mockResolvedValue(null) - - // The actual error comes from trying to access commitment.signer - await expect(authCodeHandler.completeAuth(testCommitment, authCode)).rejects.toThrow( - 'Cannot read properties of undefined', - ) - }) - - it('Should handle completeAuth failure', async () => { - const authCode = 'test-auth-code-123' - - mockCommitVerifier.mockResolvedValueOnce(undefined) - mockCompleteAuth.mockRejectedValueOnce(new Error('OAuth verification failed')) - - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - await expect(authCodeHandler.completeAuth(testCommitment, authCode)).rejects.toThrow('OAuth verification failed') - }) - }) - - // === STATUS METHOD === - - describe('status()', () => { - it('Should return ready status when auth key signer exists', async () => { - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: testWallet, - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - - const result = await authCodeHandler.status(testWallet, undefined, testRequest) - - expect(result.status).toBe('ready') - expect(result.address).toBe(testWallet) - expect(result.handler).toBe(authCodeHandler) - expect(typeof (result as any).handle).toBe('function') - }) - - it('Should execute signing when handle is called on ready status', async () => { - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: testWallet, - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - - const result = await authCodeHandler.status(testWallet, undefined, testRequest) - - // Mock the signer's sign method - const mockSignature = { - type: 'hash' as const, - r: 0x1234567890abcdefn, - s: 0xfedcba0987654321n, - yParity: 0, - } - - // We need to mock the IdentitySigner's sign method - vi.spyOn(IdentitySigner.prototype, 'sign').mockResolvedValueOnce(mockSignature) - - const handleResult = await (result as any).handle() - - expect(handleResult).toBe(true) - expect(mockAddSignature).toHaveBeenCalledOnce() - expect(mockAddSignature).toHaveBeenCalledWith(testRequest.id, { - address: testWallet, - signature: mockSignature, - }) - }) - - it('Should return actionable status when no auth key signer exists', async () => { - mockGetBySigner.mockResolvedValueOnce(null) - - const result = await authCodeHandler.status(testWallet, undefined, testRequest) - - expect(result.status).toBe('actionable') - expect(result.address).toBe(testWallet) - expect(result.handler).toBe(authCodeHandler) - expect((result as any).message).toBe('request-redirect') - expect(typeof (result as any).handle).toBe('function') - }) - - it('Should redirect to OAuth when handle is called on actionable status', async () => { - authCodeHandler.setRedirectUri('https://example.com/callback') - mockGetBySigner.mockResolvedValueOnce(null) - - const result = await authCodeHandler.status(testWallet, undefined, testRequest) - - const handleResult = await (result as any).handle() - - expect(handleResult).toBe(true) - expect(window.location.href).toContain('https://accounts.google.com/o/oauth2/v2/auth') - expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce() - - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(commitmentCall.target).toBe(window.location.pathname) - expect(commitmentCall.type).toBe('reauth') - expect(commitmentCall.signer).toBe(testWallet) - }) - }) - - // === OAUTH URL PROPERTY === - - describe('oauthUrl', () => { - it('Should return Google OAuth URL for Google issuer', () => { - const googleHandler = new AuthCodeHandler( - 'google-pkce', - 'https://accounts.google.com', - 'https://accounts.google.com/o/oauth2/v2/auth', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - const url = googleHandler['oauthUrl'] - expect(url).toBe('https://accounts.google.com/o/oauth2/v2/auth') - }) - - it('Should return Apple OAuth URL for Apple issuer', () => { - const appleHandler = new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - 'https://appleid.apple.com/auth/authorize', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - const url = appleHandler['oauthUrl'] - expect(url).toBe('https://appleid.apple.com/auth/authorize') - }) - }) - - // === INHERITED METHODS FROM IDENTITYHANDLER === - - describe('Inherited IdentityHandler methods', () => { - it('Should provide onStatusChange listener', () => { - const mockUnsubscribe = vi.fn() - mockAddListener.mockReturnValueOnce(mockUnsubscribe) - - const callback = vi.fn() - const unsubscribe = authCodeHandler.onStatusChange(callback) - - expect(mockAddListener).toHaveBeenCalledWith(callback) - expect(unsubscribe).toBe(mockUnsubscribe) - }) - - it('Should handle nitroCommitVerifier with auth key cleanup', async () => { - const mockChallenge = {} as any - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCommitVerifier.mockResolvedValueOnce('result') - - const result = await authCodeHandler['nitroCommitVerifier'](mockChallenge) - - expect(mockDelBySigner).toHaveBeenCalledWith('') - expect(mockCommitVerifier).toHaveBeenCalledWith( - expect.objectContaining({ - address: mockAuthKey.address, - keyType: KeyType.WebCrypto_Secp256r1, - signer: mockAuthKey.identitySigner, - }), - mockChallenge, - ) - expect(result).toBe('result') - }) - - it('Should handle nitroCompleteAuth with auth key management', async () => { - const mockChallenge = {} as any - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - const mockIdentityResult = { - signer: { address: testWallet }, - identity: { email: 'test@example.com' }, - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCompleteAuth.mockResolvedValueOnce(mockIdentityResult) - - const result = await authCodeHandler['nitroCompleteAuth'](mockChallenge) - - expect(mockCompleteAuth).toHaveBeenCalledWith( - expect.objectContaining({ - address: mockAuthKey.address, - }), - mockChallenge, - ) - - // Verify auth key cleanup and updates - expect(mockDelBySigner).toHaveBeenCalledWith('') - expect(mockDelBySigner).toHaveBeenCalledWith(testWallet) - expect(mockAuthKeysSet).toHaveBeenCalledWith( - expect.objectContaining({ - identitySigner: testWallet, - }), - ) - - expect(result.signer).toBeInstanceOf(IdentitySigner) - expect(result.email).toBe('test@example.com') - }) - }) - - // === ERROR HANDLING === - - describe('Error Handling', () => { - it('Should handle missing auth key in commitVerifier', async () => { - const mockChallenge = {} as any - mockGetBySigner.mockResolvedValueOnce(null) - - // Make crypto operations fail to prevent auto-generation of auth key - mockCryptoSubtle.generateKey.mockRejectedValueOnce(new Error('Crypto not available')) - - await expect(authCodeHandler['nitroCommitVerifier'](mockChallenge)).rejects.toThrow('Crypto not available') - }) - - it('Should handle missing auth key in completeAuth', async () => { - const mockChallenge = {} as any - mockGetBySigner.mockResolvedValueOnce(null) - - // Make crypto operations fail to prevent auto-generation of auth key - mockCryptoSubtle.generateKey.mockRejectedValueOnce(new Error('Crypto not available')) - - await expect(authCodeHandler['nitroCompleteAuth'](mockChallenge)).rejects.toThrow('Crypto not available') - }) - - it('Should handle identity instrument failures in commitVerifier', async () => { - const mockChallenge = {} as any - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCommitVerifier.mockRejectedValueOnce(new Error('Identity service error')) - - await expect(authCodeHandler['nitroCommitVerifier'](mockChallenge)).rejects.toThrow('Identity service error') - }) - - it('Should handle auth commitments database errors', async () => { - mockAuthCommitmentsSet.mockRejectedValueOnce(new Error('Database error')) - - await expect(authCodeHandler.commitAuth('/target', { type: 'auth' })).rejects.toThrow('Database error') - }) - - it('Should handle auth keys database errors', async () => { - mockGetBySigner.mockRejectedValueOnce(new Error('Database error')) - - await expect(authCodeHandler.status(testWallet, undefined, testRequest)).rejects.toThrow('Database error') - }) - }) - - // === INTEGRATION TESTS === - - describe('Integration Tests', () => { - it('Should handle complete OAuth flow from commitment to completion', async () => { - authCodeHandler.setRedirectUri('https://example.com/callback') - - // Step 1: Commit auth - const commitUrl = await authCodeHandler.commitAuth('/test-target', { - type: 'reauth', - state: 'test-state', - signer: testWallet, - }) - - expect(commitUrl).toContain('state=test-state') - expect(mockAuthCommitmentsSet).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'test-state', - kind: 'google-pkce', - target: '/test-target', - type: 'reauth', - signer: testWallet, - }), - ) - - // Step 2: Complete auth - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValue(mockAuthKey) - mockCommitVerifier.mockResolvedValueOnce(undefined) - mockCompleteAuth.mockResolvedValueOnce({ - signer: { address: testWallet }, - identity: { email: 'test@example.com' }, - }) - - const [signer, metadata] = await authCodeHandler.completeAuth(testCommitment, 'auth-code-123') - - expect(signer).toBeInstanceOf(IdentitySigner) - expect(metadata.email).toBe('test@example.com') - }) - - it('Should handle signup vs login flows correctly', async () => { - authCodeHandler.setRedirectUri('https://example.com/callback') - - // Test signup flow - await authCodeHandler.commitAuth('/signup-target', { type: 'auth', state: 'signup-state' }) - - const signupCall = mockAuthCommitmentsSet.mock.calls[0]![0]! - expect(signupCall.type).toBe('auth') - expect(signupCall.target).toBe('/signup-target') - - // Test login flow - await authCodeHandler.commitAuth('/login-target', { type: 'reauth', state: 'login-state', signer: testWallet }) - - const loginCall = mockAuthCommitmentsSet.mock.calls[1]![0]! - expect(loginCall.type).toBe('reauth') - expect(loginCall.target).toBe('/login-target') - }) - }) -}) diff --git a/packages/wallet/wdk/test/constants.ts b/packages/wallet/wdk/test/constants.ts deleted file mode 100644 index 855884c8b2..0000000000 --- a/packages/wallet/wdk/test/constants.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { config as dotenvConfig } from 'dotenv' -import { Abi, Address, Provider, RpcTransport } from 'ox' -import { Manager, ManagerOptions, ManagerOptionsDefaults } from '../src/sequence/index.js' -import { mockEthereum } from './setup.js' -import { Signers as CoreSigners, State, Bundler } from '@0xsequence/wallet-core' -import { Relayer } from '@0xsequence/relayer' -import * as Db from '../src/dbs/index.js' -import { Network } from '@0xsequence/wallet-primitives' - -// eslint-disable-next-line turbo/no-undeclared-env-vars -const envFile = process.env.CI ? '.env.test' : '.env.test.local' -dotenvConfig({ path: envFile }) - -export const EMITTER_ADDRESS: Address.Address = '0xb7bE532959236170064cf099e1a3395aEf228F44' -export const EMITTER_ABI = Abi.from(['function explicitEmit()', 'function implicitEmit()']) - -// Environment variables -// eslint-disable-next-line turbo/no-undeclared-env-vars -export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' - -let testIdCounter = 0 - -export function newManager(options?: ManagerOptions, noEthereumMock?: boolean, tag?: string) { - if (!noEthereumMock) { - mockEthereum() - } - - testIdCounter++ - const dbSuffix = tag ? `_${tag}_testrun_${testIdCounter}` : `_testrun_${testIdCounter}` - - // Ensure options and its identity sub-object exist for easier merging - const effectiveOptions = { - ...options, - identity: { ...ManagerOptionsDefaults.identity, ...options?.identity }, - } - - return new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore()), - networks: [ - { - name: 'Arbitrum (local fork)', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io/' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - // Override DBs with unique names if not provided in options, - // otherwise, use the provided DB instance. - // This assumes options?.someDb is either undefined or a fully constructed DB instance. - encryptedPksDb: effectiveOptions.encryptedPksDb || new CoreSigners.Pk.Encrypted.EncryptedPksDb('pk-db' + dbSuffix), - managerDb: effectiveOptions.managerDb || new Db.Wallets('sequence-manager' + dbSuffix), - messagesDb: effectiveOptions.messagesDb || new Db.Messages('sequence-messages' + dbSuffix), - transactionsDb: effectiveOptions.transactionsDb || new Db.Transactions('sequence-transactions' + dbSuffix), - signaturesDb: effectiveOptions.signaturesDb || new Db.Signatures('sequence-signature-requests' + dbSuffix), - authCommitmentsDb: - effectiveOptions.authCommitmentsDb || new Db.AuthCommitments('sequence-auth-commitments' + dbSuffix), - authKeysDb: effectiveOptions.authKeysDb || new Db.AuthKeys('sequence-auth-keys' + dbSuffix), - recoveryDb: effectiveOptions.recoveryDb || new Db.Recovery('sequence-recovery' + dbSuffix), - ...effectiveOptions, - }) -} - -export function newRemoteManager( - remoteManagerOptions: { - network: { - relayerPk: string - bundlerUrl: string - rpcUrl: string - chainId: number - } - tag?: string - }, - options?: ManagerOptions, -) { - testIdCounter++ - const dbSuffix = remoteManagerOptions?.tag - ? `_${remoteManagerOptions.tag}_testrun_${testIdCounter}` - : `_testrun_${testIdCounter}` - - const relayers: Relayer.Relayer[] = [] - const bundlers: Bundler.Bundler[] = [] - - if (remoteManagerOptions.network.relayerPk) { - const provider = Provider.from(RpcTransport.fromHttp(remoteManagerOptions.network.rpcUrl)) - relayers.push(new Relayer.PkRelayer(remoteManagerOptions.network.relayerPk as `0x${string}`, provider)) - } - - if (remoteManagerOptions.network.bundlerUrl) { - bundlers.push( - new Bundler.Bundlers.PimlicoBundler( - remoteManagerOptions.network.bundlerUrl, - Provider.from(RpcTransport.fromHttp(remoteManagerOptions.network.rpcUrl)), - ), - ) - } - - // Ensure options and its identity sub-object exist for easier merging - const effectiveOptions = { - relayers, - bundlers, - ...options, - identity: { ...ManagerOptionsDefaults.identity, ...options?.identity }, - } - - return new Manager({ - networks: [ - { - name: 'Remote Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: remoteManagerOptions.network.rpcUrl, - chainId: remoteManagerOptions.network.chainId, - blockExplorer: { url: 'https://undefined/' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - // Override DBs with unique names if not provided in options, - // otherwise, use the provided DB instance. - // This assumes options?.someDb is either undefined or a fully constructed DB instance. - encryptedPksDb: effectiveOptions.encryptedPksDb || new CoreSigners.Pk.Encrypted.EncryptedPksDb('pk-db' + dbSuffix), - managerDb: effectiveOptions.managerDb || new Db.Wallets('sequence-manager' + dbSuffix), - messagesDb: effectiveOptions.messagesDb || new Db.Messages('sequence-messages' + dbSuffix), - transactionsDb: effectiveOptions.transactionsDb || new Db.Transactions('sequence-transactions' + dbSuffix), - signaturesDb: effectiveOptions.signaturesDb || new Db.Signatures('sequence-signature-requests' + dbSuffix), - authCommitmentsDb: - effectiveOptions.authCommitmentsDb || new Db.AuthCommitments('sequence-auth-commitments' + dbSuffix), - authKeysDb: effectiveOptions.authKeysDb || new Db.AuthKeys('sequence-auth-keys' + dbSuffix), - recoveryDb: effectiveOptions.recoveryDb || new Db.Recovery('sequence-recovery' + dbSuffix), - ...effectiveOptions, - }) -} diff --git a/packages/wallet/wdk/test/guard.test.ts b/packages/wallet/wdk/test/guard.test.ts deleted file mode 100644 index ffb117c3fc..0000000000 --- a/packages/wallet/wdk/test/guard.test.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Manager } from '../src/sequence/index.js' -import { GuardHandler } from '../src/sequence/handlers/guard.js' -import { Address, Bytes, Hex, TypedData } from 'ox' -import { Config, Constants, Network, Payload } from '@0xsequence/wallet-primitives' -import { Kinds } from '../src/sequence/types/signer.js' -import { newManager } from './constants.js' -import { Guards } from '../src/sequence/guards.js' - -// Mock fetch globally for guard API calls -const mockFetch = vi.fn() -global.fetch = mockFetch - -describe('GuardHandler', () => { - let manager: Manager - let guards: Guards - let testWallet: Address.Address - let testPayload: Payload.Payload - let _testMessageDigest: Bytes.Bytes - let _testMessage: Hex.Hex - - beforeEach(async () => { - vi.clearAllMocks() - manager = newManager(undefined, undefined, `guard_test_${Date.now()}`) - - // Access guard instance through manager modules - guards = (manager as any).shared.modules.guards - - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - testPayload = Payload.fromMessage(Hex.fromString('Test message')) - _testMessage = TypedData.encode(Payload.toTyped(testWallet, Network.ChainId.ARBITRUM, testPayload)) - _testMessageDigest = Payload.hash(testWallet, Network.ChainId.ARBITRUM, testPayload) - }) - - afterEach(async () => { - await manager.stop() - vi.resetAllMocks() - }) - - // === GUARD HANDLER INTEGRATION === - - describe('GuardHandler Integration', () => { - const previousSignature = { - type: 'hash', - address: '0x1234567890123456789012345678901234567890' as Address.Address, - signature: { - type: 'hash', - r: 1n, - s: 2n, - yParity: 0, - }, - } - - it('Should create guard handler with correct kind', () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - expect(guardHandler.kind).toBe(Kinds.Guard) // Use the actual constant - }) - - it('Should return unavailable status if no UI is registered', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [previousSignature], - }, - } - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - expect(status.status).toBe('unavailable') - expect((status as any).reason).toBe('guard-ui-not-registered') - }) - - it('Should return unavailable status if no signatures present', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [], - }, - } - - guardHandler.registerUI(vi.fn()) - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - - expect(status.status).toBe('unavailable') - expect((status as any).reason).toBe('must-not-sign-first') - }) - - it('Should return ready status for guard signer', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [previousSignature], - }, - } - - guardHandler.registerUI(vi.fn()) - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - - expect(status.status).toBe('ready') - expect(status.address).toBe(guards.getByRole('wallet').address) - expect(status.handler).toBe(guardHandler) - expect(typeof (status as any).handle).toBe('function') - }) - - it('Should handle signature through guard handler', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - const mockSignature = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - sig: mockSignature, - }), - text: async () => - JSON.stringify({ - sig: mockSignature, - }), - ok: true, - }) - - guardHandler.registerUI(vi.fn()) - - // Mock the addSignature method - const mockAddSignature = vi.fn() - signatures.addSignature = mockAddSignature - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [previousSignature], - }, - } - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - const result = await (status as any).handle() - - expect(result).toBe(true) - expect(mockAddSignature).toHaveBeenCalledOnce() - - const [requestId, signatureData] = mockAddSignature.mock.calls[0]! - expect(requestId).toBe('test-request-id') - expect(signatureData.address).toBe(guards.getByRole('wallet').address) - expect(signatureData.signature).toBeDefined() - }) - - it('Should handle guard service errors in handler', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - mockFetch.mockRejectedValueOnce(new Error('Service error')) - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [previousSignature], - }, - } - - guardHandler.registerUI(vi.fn()) - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - - await expect((status as any).handle()).rejects.toThrow('Error signing with guard') - }) - - it('Should handle 2FA', async () => { - const signatures = (manager as any).shared.modules.signatures - const guardHandler = new GuardHandler(signatures, guards) - - const mock2FAError = { - code: 6600, - } - const mockSignature = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockFetch - .mockResolvedValueOnce({ - json: async () => mock2FAError, - text: async () => JSON.stringify(mock2FAError), - ok: false, - }) - .mockResolvedValueOnce({ - json: async () => ({ - sig: mockSignature, - }), - text: async () => - JSON.stringify({ - sig: mockSignature, - }), - ok: true, - }) - - // Mock the addSignature method - const mockAddSignature = vi.fn() - signatures.addSignature = mockAddSignature - - const mockCallback = vi.fn().mockImplementation(async (request, codeType, respond) => { - expect(codeType).toBe('TOTP') - await respond('123456') - }) - - guardHandler.registerUI(mockCallback) - - const mockRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: testPayload, - signatures: [previousSignature], - }, - } - - const status = await guardHandler.status(guards.getByRole('wallet').address, undefined, mockRequest as any) - const result = await (status as any).handle() - - expect(result).toBe(true) - expect(mockCallback).toHaveBeenCalledOnce() - expect(mockAddSignature).toHaveBeenCalledOnce() - - const [requestId, signatureData] = mockAddSignature.mock.calls[0]! - expect(requestId).toBe('test-request-id') - expect(signatureData.address).toBe(guards.getByRole('wallet').address) - expect(signatureData.signature).toBeDefined() - }) - }) - - // === CONFIGURATION TESTING === - - describe('Guard Configuration', () => { - it('Should use custom guard URL from manager options', async () => { - const customGuardUrl = 'https://test-guard.example.com' - - const customManager = newManager( - { - guardUrl: customGuardUrl, - }, - undefined, - `guard_url_${Date.now()}`, - ) - - const customGuard = (customManager as any).shared.modules.guards as Guards - - mockFetch.mockResolvedValueOnce({ - json: async () => ({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - text: async () => - JSON.stringify({ - sig: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b', - }), - ok: true, - }) - - await customGuard.getByRole('wallet').signEnvelope({ - payload: { - type: 'config-update', - imageHash: '0x123456789012345678901234567890123456789012345678901234567890123' as Hex.Hex, - }, - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - configuration: { - threshold: 1n, - checkpoint: 0n, - topology: { - type: 'signer', - address: '0x1234567890123456789012345678901234567890' as Address.Address, - weight: 1n, - }, - }, - signatures: [], - }) - - expect(mockFetch.mock.calls[0]![0]).toContain(customGuardUrl) - - await customManager.stop() - }) - - it('Should use default guard configuration when not specified', () => { - // The guard should be created with default URL and address from ManagerOptionsDefaults - expect(guards).toBeDefined() - - // Verify the shared configuration contains the defaults - const sharedConfig = (manager as any).shared.sequence - expect(sharedConfig.guardUrl).toBeDefined() - expect(sharedConfig.guardAddresses).toBeDefined() - }) - }) - - describe('Guard Topology', () => { - it('should replace the placeholder guard address', () => { - const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet - const defaultTopology = (manager as any).shared.sequence.defaultGuardTopology - - const topology = guards.topology('wallet') - - expect(topology).toBeDefined() - expect(Config.findSignerLeaf(topology!, guardAddress)).toBeDefined() - expect(Config.findSignerLeaf(topology!, Constants.PlaceholderAddress as Address.Address)).toBeUndefined() - expect(Config.findSignerLeaf(defaultTopology, guardAddress)).toBeUndefined() - expect(Config.hashConfiguration(topology!)).not.toEqual(Config.hashConfiguration(defaultTopology)) - }) - - it('should throw when the placeholder is missing in the default topology', async () => { - const customManager = newManager( - { - defaultGuardTopology: { - type: 'signer', - address: '0x0000000000000000000000000000000000000001', - weight: 1n, - }, - }, - undefined, - `guard_topology_${Date.now()}`, - ) - - const customGuards = (customManager as any).shared.modules.guards as Guards - - try { - expect(() => customGuards.topology('wallet')).toThrow('Guard address replacement failed for role wallet') - } finally { - await customManager.stop() - } - }) - - it('should return undefined when no guard address is set for a role', async () => { - const defaultWalletGuard = (manager as any).shared.sequence.guardAddresses.wallet - const customManager = newManager( - { - guardAddresses: { - wallet: defaultWalletGuard, - } as any, - }, - undefined, - `guard_missing_${Date.now()}`, - ) - - const customGuards = (customManager as any).shared.modules.guards as Guards - - expect(customGuards.topology('sessions')).toBeUndefined() - - await customManager.stop() - }) - }) -}) diff --git a/packages/wallet/wdk/test/identity-auth-dbs.test.ts b/packages/wallet/wdk/test/identity-auth-dbs.test.ts deleted file mode 100644 index bba408ef36..0000000000 --- a/packages/wallet/wdk/test/identity-auth-dbs.test.ts +++ /dev/null @@ -1,549 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Manager } from '../src/sequence/index.js' -import * as Db from '../src/dbs/index.js' -import { LOCAL_RPC_URL } from './constants.js' -import { State } from '@0xsequence/wallet-core' -import { Network } from '@0xsequence/wallet-primitives' - -describe('Identity Authentication Databases', () => { - let manager: Manager | undefined - let authCommitmentsDb: Db.AuthCommitments - let authKeysDb: Db.AuthKeys - - beforeEach(() => { - vi.clearAllMocks() - - // Create isolated database instances with unique names - const testId = `auth_dbs_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` - authCommitmentsDb = new Db.AuthCommitments(`test-auth-commitments-${testId}`) - authKeysDb = new Db.AuthKeys(`test-auth-keys-${testId}`) - }) - - afterEach(async () => { - await manager?.stop() - }) - - // === AUTH COMMITMENTS DATABASE TESTS === - - describe('AuthCommitments Database', () => { - it('Should create and manage Google PKCE commitments', async () => { - const commitment: Db.AuthCommitment = { - id: 'test-state-123', - kind: 'google-pkce', - metadata: { scope: 'openid profile email' }, - verifier: 'test-verifier-code', - challenge: 'test-challenge-hash', - target: 'test-target-url', - type: 'reauth', - signer: '0x1234567890123456789012345678901234567890', - } - - // Test setting a commitment - const id = await authCommitmentsDb.set(commitment) - expect(id).toBe(commitment.id) - - // Test getting the commitment - const retrieved = await authCommitmentsDb.get(commitment.id) - expect(retrieved).toEqual(commitment) - - // Test listing commitments - const list = await authCommitmentsDb.list() - expect(list).toHaveLength(1) - expect(list[0]).toEqual(commitment) - - // Test deleting the commitment - await authCommitmentsDb.del(commitment.id) - const deletedCommitment = await authCommitmentsDb.get(commitment.id) - expect(deletedCommitment).toBeUndefined() - }) - - it('Should create and manage Apple commitments', async () => { - const appleCommitment: Db.AuthCommitment = { - id: 'apple-state-456', - kind: 'apple', - metadata: { - response_type: 'code id_token', - response_mode: 'form_post', - }, - target: 'apple-redirect-url', - type: 'auth', - } - - await authCommitmentsDb.set(appleCommitment) - const retrieved = await authCommitmentsDb.get(appleCommitment.id) - - expect(retrieved).toBeDefined() - expect(retrieved!.kind).toBe('apple') - expect(retrieved!.type).toBe('auth') - expect(retrieved!.metadata.response_type).toBe('code id_token') - }) - - it('Should handle multiple commitments and proper cleanup', async () => { - const commitments: Db.AuthCommitment[] = [ - { - id: 'commit-1', - kind: 'google-pkce', - metadata: {}, - target: 'target-1', - type: 'auth', - }, - { - id: 'commit-2', - kind: 'apple', - metadata: {}, - target: 'target-2', - type: 'reauth', - signer: '0x1234567890123456789012345678901234567890', - }, - { - id: 'commit-3', - kind: 'google-pkce', - metadata: {}, - target: 'target-3', - type: 'auth', - }, - ] - - // Add all commitments - for (const commitment of commitments) { - await authCommitmentsDb.set(commitment) - } - - // Verify all are present - const list = await authCommitmentsDb.list() - expect(list.length).toBe(3) - - // Test selective deletion - await authCommitmentsDb.del('commit-2') - const updatedList = await authCommitmentsDb.list() - expect(updatedList.length).toBe(2) - expect(updatedList.find((c) => c.id === 'commit-2')).toBeUndefined() - }) - - it('Should handle database initialization and migration', async () => { - // This test ensures the database creation code is triggered - const freshDb = new Db.AuthCommitments(`fresh-db-${Date.now()}`) - - // Add a commitment to trigger database initialization - const testCommitment: Db.AuthCommitment = { - id: 'init-test', - kind: 'google-pkce', - metadata: {}, - target: 'init-target', - type: 'auth', - } - - await freshDb.set(testCommitment) - const retrieved = await freshDb.get(testCommitment.id) - expect(retrieved).toEqual(testCommitment) - }) - }) - - // === AUTH KEYS DATABASE TESTS === - - describe('AuthKeys Database', () => { - let mockCryptoKey: CryptoKey - - beforeEach(() => { - // Mock CryptoKey - mockCryptoKey = { - algorithm: { name: 'ECDSA', namedCurve: 'P-256' }, - extractable: false, - type: 'private', - usages: ['sign'], - } as CryptoKey - }) - - it('Should create and manage auth keys with expiration', async () => { - const authKey: Db.AuthKey = { - address: '0xAbCdEf1234567890123456789012345678901234', - privateKey: mockCryptoKey, - identitySigner: '0x9876543210987654321098765432109876543210', - expiresAt: new Date(Date.now() + 3600000), // 1 hour from now - } - - // Test setting an auth key (should normalize addresses) - const address = await authKeysDb.set(authKey) - expect(address).toBe(authKey.address.toLowerCase()) - - // Test getting the auth key - const retrieved = await authKeysDb.get(address) - if (!retrieved) { - throw new Error('Retrieved auth key should not be undefined') - } - expect(retrieved.address).toBe(authKey.address.toLowerCase()) - expect(retrieved.identitySigner).toBe(authKey.identitySigner.toLowerCase()) - expect(retrieved.privateKey).toEqual(mockCryptoKey) - }) - - it('Should handle getBySigner with fallback mechanisms', async () => { - const authKey: Db.AuthKey = { - address: '0x1111111111111111111111111111111111111111', - privateKey: mockCryptoKey, - identitySigner: '0x2222222222222222222222222222222222222222', - expiresAt: new Date(Date.now() + 3600000), - } - - await authKeysDb.set(authKey) - - // Test normal getBySigner - const retrieved = await authKeysDb.getBySigner(authKey.identitySigner) - expect(retrieved?.address).toBe(authKey.address.toLowerCase()) - - // Test with different casing - const retrievedMixed = await authKeysDb.getBySigner(authKey.identitySigner.toUpperCase()) - expect(retrievedMixed?.address).toBe(authKey.address.toLowerCase()) - }) - - it('Should handle getBySigner retry mechanism', async () => { - const signer = '0x3333333333333333333333333333333333333333' - - // First call should return undefined, then retry - const result = await authKeysDb.getBySigner(signer) - expect(result).toBeUndefined() - }) - - it('Should handle delBySigner operations', async () => { - const authKey: Db.AuthKey = { - address: '0x4444444444444444444444444444444444444444', - privateKey: mockCryptoKey, - identitySigner: '0x5555555555555555555555555555555555555555', - expiresAt: new Date(Date.now() + 3600000), - } - - await authKeysDb.set(authKey) - - // Verify it exists - const beforeDelete = await authKeysDb.getBySigner(authKey.identitySigner) - expect(beforeDelete).toBeDefined() - - // Delete by signer - await authKeysDb.delBySigner(authKey.identitySigner) - - // Verify it's gone - const afterDelete = await authKeysDb.getBySigner(authKey.identitySigner) - expect(afterDelete).toBeUndefined() - }) - - it('Should handle delBySigner with non-existent signer', async () => { - // Should not throw when deleting non-existent signer - await expect(authKeysDb.delBySigner('0x9999999999999999999999999999999999999999')).resolves.not.toThrow() - }) - - it('Should handle expired auth keys and automatic cleanup', async () => { - const expiredAuthKey: Db.AuthKey = { - address: '0x6666666666666666666666666666666666666666', - privateKey: mockCryptoKey, - identitySigner: '0x7777777777777777777777777777777777777777', - expiresAt: new Date(Date.now() - 1000), // Already expired - } - - // Setting an expired key should trigger immediate deletion - await authKeysDb.set(expiredAuthKey) - - // It should be automatically deleted - await new Promise((resolve) => setTimeout(resolve, 10)) - const retrieved = await authKeysDb.getBySigner(expiredAuthKey.identitySigner) - expect(retrieved).toBeUndefined() - }) - - it('Should schedule and clear expiration timers', async () => { - const shortLivedKey: Db.AuthKey = { - address: '0x8888888888888888888888888888888888888888', - privateKey: mockCryptoKey, - identitySigner: '0x9999999999999999999999999999999999999999', - expiresAt: new Date(Date.now() + 100), // Expires in 100ms - } - - await authKeysDb.set(shortLivedKey) - - // Should exist initially - const initial = await authKeysDb.getBySigner(shortLivedKey.identitySigner) - expect(initial).toBeDefined() - - // Wait for expiration - await new Promise((resolve) => setTimeout(resolve, 200)) - - // Should be automatically deleted - const afterExpiration = await authKeysDb.getBySigner(shortLivedKey.identitySigner) - expect(afterExpiration).toBeUndefined() - }) - - it('Should handle database initialization and indexing', async () => { - // Test database initialization with indexes - const freshAuthKeysDb = new Db.AuthKeys(`fresh-auth-keys-${Date.now()}`) - - const testKey: Db.AuthKey = { - address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', - privateKey: mockCryptoKey, - identitySigner: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', - expiresAt: new Date(Date.now() + 3600000), - } - - await freshAuthKeysDb.set(testKey) - - // Test index-based lookup - const retrieved = await freshAuthKeysDb.getBySigner(testKey.identitySigner) - expect(retrieved?.address).toBe(testKey.address.toLowerCase()) - }) - - it('Should handle handleOpenDB for existing auth keys', async () => { - // Add multiple keys before calling handleOpenDB - const keys: Db.AuthKey[] = [ - { - address: '0xcccccccccccccccccccccccccccccccccccccccc', - privateKey: mockCryptoKey, - identitySigner: '0xdddddddddddddddddddddddddddddddddddddddd', - expiresAt: new Date(Date.now() + 3600000), - }, - { - address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - privateKey: mockCryptoKey, - identitySigner: '0xffffffffffffffffffffffffffffffffffffffff', - expiresAt: new Date(Date.now() + 7200000), - }, - ] - - for (const key of keys) { - await authKeysDb.set(key) - } - - // Test handleOpenDB (this would normally be called on database initialization) - await authKeysDb.handleOpenDB() - - // All keys should still be accessible - for (const key of keys) { - const retrieved = await authKeysDb.getBySigner(key.identitySigner) - expect(retrieved).toBeDefined() - } - }) - }) - - // === INTEGRATION TESTS WITH MANAGER === - - describe('Integration with Manager (Google/Email enabled)', () => { - it('Should use auth databases when Google authentication is enabled', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-google-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - google: { - enabled: true, - clientId: 'test-google-client-id', - }, - }, - }) - - // Verify that Google is registered under the canonical signer kind while - // still using the PKCE flow by default. - const handlers = (manager as any).shared.handlers - expect(handlers.has('login-google')).toBe(true) - expect(handlers.has('login-google-pkce')).toBe(false) - }) - - it('Should register the Google ID token handler when configured explicitly', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-google-idtoken-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - google: { - enabled: true, - clientId: 'test-google-client-id', - authMethod: 'id-token', - }, - }, - }) - - const handlers = (manager as any).shared.handlers - expect(handlers.has('login-google-id-token')).toBe(false) - expect(handlers.has('login-google')).toBe(true) - expect(handlers.has('login-google-pkce')).toBe(false) - }) - - it('Should register the Apple ID token handler when configured explicitly', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-apple-idtoken-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - apple: { - enabled: true, - clientId: 'test-apple-client-id', - authMethod: 'id-token', - }, - }, - }) - - const handlers = (manager as any).shared.handlers - expect(handlers.has('login-apple-id-token')).toBe(false) - expect(handlers.has('login-apple')).toBe(true) - }) - - it('Should use auth databases when email authentication is enabled', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-email-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - email: { - enabled: true, - }, - }, - }) - - // Verify that email OTP handler is registered and uses our auth keys database - const handlers = (manager as any).shared.handlers - expect(handlers.has('login-email-otp')).toBe(true) - }) - - it('Should use auth databases when Apple authentication is enabled', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-apple-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - apple: { - enabled: true, - clientId: 'com.example.test', - }, - }, - }) - - // Verify that Apple handler is registered and uses our databases - const handlers = (manager as any).shared.handlers - expect(handlers.has('login-apple')).toBe(true) - }) - - it('Should register custom ID token providers without enabling redirect flow for them', async () => { - manager = new Manager({ - stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-custom-idtoken-${Date.now()}`)), - networks: [ - { - name: 'Test Network', - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - chainId: Network.ChainId.ARBITRUM, - blockExplorer: { url: 'https://arbiscan.io' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - relayers: [], - authCommitmentsDb, - authKeysDb, - identity: { - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, - customProviders: [ - { - kind: 'custom-google-native', - authMethod: 'id-token', - issuer: 'https://accounts.google.com', - clientId: 'test-google-client-id', - }, - ], - }, - }) - - const handlers = (manager as any).shared.handlers - expect(handlers.has('custom-google-native')).toBe(true) - await expect( - manager.wallets.startSignUpWithRedirect({ - kind: 'custom-google-native', - target: '/home', - metadata: {}, - }), - ).rejects.toThrow('handler-does-not-support-redirect') - }) - }) -}) diff --git a/packages/wallet/wdk/test/identity-signer.test.ts b/packages/wallet/wdk/test/identity-signer.test.ts deleted file mode 100644 index 526177aa8e..0000000000 --- a/packages/wallet/wdk/test/identity-signer.test.ts +++ /dev/null @@ -1,527 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { IdentityInstrument, KeyType } from '@0xsequence/identity-instrument' -import { State } from '@0xsequence/wallet-core' -import { IdentitySigner, toIdentityAuthKey } from '../src/identity/signer.js' -import { AuthKey } from '../src/dbs/auth-keys.js' - -// Mock the global crypto API -const mockCryptoSubtle = { - sign: vi.fn(), - generateKey: vi.fn(), - exportKey: vi.fn(), -} - -Object.defineProperty(globalThis, 'window', { - value: { - crypto: { - subtle: mockCryptoSubtle, - }, - }, - writable: true, -}) - -// Mock IdentityInstrument -const mockIdentityInstrument = { - sign: vi.fn(), -} as unknown as IdentityInstrument - -describe('Identity Signer', () => { - let testAuthKey: AuthKey - let testWallet: Address.Address - let mockStateWriter: State.Writer - let mockSignFn: Mock - - beforeEach(() => { - vi.clearAllMocks() - - // Create a proper mock function for the sign method - mockSignFn = vi.fn() - mockIdentityInstrument.sign = mockSignFn - - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - - // Create mock CryptoKey - const mockCryptoKey = { - algorithm: { name: 'ECDSA', namedCurve: 'P-256' }, - extractable: false, - type: 'private', - usages: ['sign'], - } as CryptoKey - - testAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: mockCryptoKey, - identitySigner: '0x1234567890123456789012345678901234567890', // Use exact format from working tests - expiresAt: new Date(Date.now() + 3600000), // 1 hour from now - } - - mockStateWriter = { - saveWitnesses: vi.fn(), - } as unknown as State.Writer - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - // === UTILITY FUNCTION TESTS === - - describe('toIdentityAuthKey()', () => { - it('Should convert AuthKey to Identity.AuthKey format', () => { - const result = toIdentityAuthKey(testAuthKey) - - expect(result.address).toBe(testAuthKey.address) - expect(result.keyType).toBe(KeyType.WebCrypto_Secp256r1) - expect(result.signer).toBe(testAuthKey.identitySigner) - expect(typeof result.sign).toBe('function') - }) - - it('Should create working sign function that uses Web Crypto API', async () => { - const mockSignature = new ArrayBuffer(64) - const mockDigest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - - mockCryptoSubtle.sign.mockResolvedValueOnce(mockSignature) - - const identityAuthKey = toIdentityAuthKey(testAuthKey) - const result = await identityAuthKey.sign(mockDigest) - - expect(mockCryptoSubtle.sign).toHaveBeenCalledOnce() - expect(mockCryptoSubtle.sign).toHaveBeenCalledWith( - { - name: 'ECDSA', - hash: 'SHA-256', - }, - testAuthKey.privateKey, - mockDigest, - ) - - expect(result).toBeDefined() - expect(typeof result).toBe('string') - expect(result.startsWith('0x')).toBe(true) - }) - - it('Should handle Web Crypto API errors in sign function', async () => { - const mockDigest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - - mockCryptoSubtle.sign.mockRejectedValueOnce(new Error('Crypto operation failed')) - - const identityAuthKey = toIdentityAuthKey(testAuthKey) - - await expect(identityAuthKey.sign(mockDigest)).rejects.toThrow('Crypto operation failed') - }) - }) - - // === IDENTITY SIGNER CLASS TESTS === - - describe('IdentitySigner', () => { - let identitySigner: IdentitySigner - - beforeEach(() => { - identitySigner = new IdentitySigner(mockIdentityInstrument, testAuthKey) - }) - - describe('Constructor', () => { - it('Should create IdentitySigner with correct properties', () => { - expect(identitySigner.identityInstrument).toBe(mockIdentityInstrument) - expect(identitySigner.authKey).toBe(testAuthKey) - }) - }) - - describe('address getter', () => { - it('Should return checksummed address from authKey.identitySigner', () => { - const result = identitySigner.address - - expect(result).toBe(Address.checksum(testAuthKey.identitySigner)) - expect(Address.validate(result)).toBe(true) - }) - - it('Should throw error when identitySigner is invalid', () => { - const invalidAuthKey = { - ...testAuthKey, - identitySigner: 'invalid-address', - } - const invalidSigner = new IdentitySigner(mockIdentityInstrument, invalidAuthKey) - - expect(() => invalidSigner.address).toThrow('No signer address found') - }) - - it('Should handle empty identitySigner', () => { - const emptyAuthKey = { - ...testAuthKey, - identitySigner: '', - } - const emptySigner = new IdentitySigner(mockIdentityInstrument, emptyAuthKey) - - expect(() => emptySigner.address).toThrow('No signer address found') - }) - }) - - describe('sign()', () => { - it('Should sign payload and return signature', async () => { - const testPayload = Payload.fromMessage(Hex.fromString('Test message')) - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - const result = await identitySigner.sign(testWallet, Network.ChainId.ARBITRUM, testPayload) - - expect(result).toBeDefined() - expect(result.type).toBe('hash') - // For hash type signatures, the structure includes r, s, yParity - if (result.type === 'hash') { - expect(result.r).toBeDefined() - expect(result.s).toBeDefined() - expect(result.yParity).toBeDefined() - } - - // Verify that identityInstrument.sign was called with correct parameters - expect(mockSignFn).toHaveBeenCalledOnce() - const [authKeyArg, digestArg] = mockSignFn.mock.calls[0]! - expect(authKeyArg.address).toBe(testAuthKey.address) - expect(authKeyArg.signer).toBe(testAuthKey.identitySigner) - expect(digestArg).toBeDefined() - }) - - it('Should handle different chainIds correctly', async () => { - const testPayload = Payload.fromMessage(Hex.fromString('Mainnet message')) - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - await identitySigner.sign(testWallet, Network.ChainId.MAINNET, testPayload) - - expect(mockSignFn).toHaveBeenCalledOnce() - // The digest should be different for different chainIds - const [, digestArg] = mockSignFn.mock.calls[0]! - expect(digestArg).toBeDefined() - }) - - it('Should handle transaction payloads', async () => { - const transactionPayload = Payload.fromCall(1n, 0n, [ - { - to: '0x1234567890123456789012345678901234567890' as Address.Address, - value: 1000000000000000000n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - const result = await identitySigner.sign(testWallet, Network.ChainId.ARBITRUM, transactionPayload) - - expect(result).toBeDefined() - expect(result.type).toBe('hash') - expect(mockSignFn).toHaveBeenCalledOnce() - }) - - it('Should handle identity instrument signing errors', async () => { - const testPayload = Payload.fromMessage(Hex.fromString('Error message')) - - mockSignFn.mockRejectedValueOnce(new Error('Identity service unavailable')) - - await expect(identitySigner.sign(testWallet, Network.ChainId.ARBITRUM, testPayload)).rejects.toThrow( - 'Identity service unavailable', - ) - }) - }) - - describe('signDigest()', () => { - it('Should sign raw digest directly', async () => { - const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - const result = await identitySigner.signDigest(digest) - - expect(result).toBeDefined() - expect(result.type).toBe('hash') - // For hash type signatures, check properties conditionally - if (result.type === 'hash') { - expect(result.r).toBeDefined() - expect(result.s).toBeDefined() - expect(result.yParity).toBeDefined() - } - - expect(mockSignFn).toHaveBeenCalledOnce() - const [authKeyArg, digestArg] = mockSignFn.mock.calls[0]! - expect(authKeyArg.address).toBe(testAuthKey.address) - expect(digestArg).toBe(digest) - }) - - it('Should handle different digest lengths', async () => { - const shortDigest = Hex.toBytes('0x1234') - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - const result = await identitySigner.signDigest(shortDigest) - - expect(result).toBeDefined() - expect(result.type).toBe('hash') - expect(mockSignFn).toHaveBeenCalledWith( - expect.objectContaining({ - address: testAuthKey.address, - }), - shortDigest, - ) - }) - - it('Should handle empty digest', async () => { - const emptyDigest = new Uint8Array(0) - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - const result = await identitySigner.signDigest(emptyDigest) - - expect(result).toBeDefined() - expect(result.type).toBe('hash') - }) - - it('Should handle malformed signature from identity instrument', async () => { - const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - - mockSignFn.mockResolvedValueOnce('invalid-signature' as any) - - await expect(identitySigner.signDigest(digest)).rejects.toThrow() // Should throw when Signature.fromHex fails - }) - }) - - describe('witness()', () => { - it('Should create and save witness signature', async () => { - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - await identitySigner.witness(mockStateWriter, testWallet) - - // Verify signature was created (sign called) - expect(mockSignFn).toHaveBeenCalledOnce() - - // Verify witness was saved - expect(mockSaveWitnesses).toHaveBeenCalledOnce() - const [wallet, chainId, payload, witness] = mockSaveWitnesses.mock.calls[0]! - - expect(wallet).toBe(testWallet) - expect(chainId).toBe(0) // Witness signatures use chainId 0 - expect(payload.type).toBe('message') - expect(witness.type).toBe('unrecovered-signer') - expect(witness.weight).toBe(1n) - expect(witness.signature).toBeDefined() - }) - - it('Should create consent payload with correct structure', async () => { - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - await identitySigner.witness(mockStateWriter, testWallet) - - // Extract the payload that was signed - const [, , payload] = mockSaveWitnesses.mock.calls[0]! - - // Parse the message content to verify consent structure - const messageHex = payload.message - const messageString = Hex.toString(messageHex) - const consentData = JSON.parse(messageString) - - expect(consentData.action).toBe('consent-to-be-part-of-wallet') - expect(consentData.wallet).toBe(testWallet) - expect(consentData.signer).toBe(identitySigner.address) - expect(consentData.timestamp).toBeDefined() - expect(typeof consentData.timestamp).toBe('number') - }) - - it('Should include extra data in consent payload', async () => { - const extraData = { - userAgent: 'test-browser', - sessionId: 'session-123', - } - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - - await identitySigner.witness(mockStateWriter, testWallet, extraData) - - // Extract and verify extra data was included - const [, , payload] = mockSaveWitnesses.mock.calls[0]! - const messageString = Hex.toString(payload.message) - const consentData = JSON.parse(messageString) - - expect(consentData.userAgent).toBe(extraData.userAgent) - expect(consentData.sessionId).toBe(extraData.sessionId) - }) - - it('Should handle witness creation failure', async () => { - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - mockSignFn.mockRejectedValueOnce(new Error('Identity signing failed')) - - await expect(identitySigner.witness(mockStateWriter, testWallet)).rejects.toThrow('Identity signing failed') - - // Verify saveWitnesses was not called due to error - expect(mockSaveWitnesses).not.toHaveBeenCalled() - }) - - it('Should handle state writer saveWitnesses failure', async () => { - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - mockSignFn.mockResolvedValueOnce(mockSignatureHex) - mockSaveWitnesses.mockRejectedValueOnce(new Error('State write failed')) - - await expect(identitySigner.witness(mockStateWriter, testWallet)).rejects.toThrow('State write failed') - - // Verify sign was called but saveWitnesses failed - expect(mockSignFn).toHaveBeenCalledOnce() - expect(mockSaveWitnesses).toHaveBeenCalledOnce() - }) - }) - - // === INTEGRATION TESTS === - - describe('Integration Tests', () => { - it('Should work with real-world payload and witness flow', async () => { - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - const mockSaveWitnesses = vi.fn() - mockStateWriter.saveWitnesses = mockSaveWitnesses - - // Mock both sign operations (for payload and witness) - mockSignFn - .mockResolvedValueOnce(mockSignatureHex) // For initial payload signing - .mockResolvedValueOnce(mockSignatureHex) // For witness creation - - // First, sign a regular payload - const payload = Payload.fromMessage(Hex.fromString('User authentication request')) - const payloadSignature = await identitySigner.sign(testWallet, Network.ChainId.MAINNET, payload) - - expect(payloadSignature.type).toBe('hash') - - // Then create a witness - await identitySigner.witness(mockStateWriter, testWallet, { - signatureId: 'sig-123', - purpose: 'authentication', - }) - - // Verify both operations completed - expect(mockSignFn).toHaveBeenCalledTimes(2) - expect(mockSaveWitnesses).toHaveBeenCalledOnce() - - // Verify witness payload includes extra context - const [, , witnessPayload] = mockSaveWitnesses.mock.calls[0]! - const witnessMessage = JSON.parse(Hex.toString(witnessPayload.message)) - expect(witnessMessage.signatureId).toBe('sig-123') - expect(witnessMessage.purpose).toBe('authentication') - }) - - it('Should handle complex payload types correctly', async () => { - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValue(mockSignatureHex) - - // Test with different payload types - const messagePayload = Payload.fromMessage(Hex.fromString('Hello World')) - const transactionPayload = Payload.fromCall(1n, 0n, [ - { - to: testWallet, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]) - - const messageResult = await identitySigner.sign(testWallet, Network.ChainId.ARBITRUM, messagePayload) - const transactionResult = await identitySigner.sign(testWallet, Network.ChainId.ARBITRUM, transactionPayload) - - expect(messageResult.type).toBe('hash') - expect(transactionResult.type).toBe('hash') - expect(mockSignFn).toHaveBeenCalledTimes(2) - - // Verify different payloads produce different hashes - const [, messageDigest] = mockSignFn.mock.calls[0]! - const [, transactionDigest] = mockSignFn.mock.calls[1]! - expect(messageDigest).not.toEqual(transactionDigest) - }) - }) - - // === ERROR HANDLING AND EDGE CASES === - - describe('Error Handling', () => { - it('Should handle corrupted AuthKey data gracefully', () => { - const corruptedAuthKey = { - ...testAuthKey, - address: null, - } as any - - // This should not throw during construction - const corruptedSigner = new IdentitySigner(mockIdentityInstrument, corruptedAuthKey) - expect(corruptedSigner).toBeDefined() - }) - - it('Should handle network failures in identity instrument', async () => { - const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - - mockSignFn.mockRejectedValueOnce(new Error('Network timeout')) - - await expect(identitySigner.signDigest(digest)).rejects.toThrow('Network timeout') - }) - - it('Should handle malformed hex signatures', async () => { - const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - - mockSignFn.mockResolvedValueOnce('not-a-hex-string' as any) - - await expect(identitySigner.signDigest(digest)).rejects.toThrow() - }) - - it('Should handle edge case wallet addresses', async () => { - const zeroWallet = '0x0000000000000000000000000000000000000000' as Address.Address - const maxWallet = '0xffffffffffffffffffffffffffffffffffffffff' as Address.Address - const mockSignatureHex = - '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b' - - mockSignFn.mockResolvedValue(mockSignatureHex) - - const payload = Payload.fromMessage(Hex.fromString('Edge case test')) - - // Should work with edge case addresses - const zeroResult = await identitySigner.sign(zeroWallet, Network.ChainId.MAINNET, payload) - const maxResult = await identitySigner.sign(maxWallet, Network.ChainId.MAINNET, payload) - - expect(zeroResult.type).toBe('hash') - expect(maxResult.type).toBe('hash') - }) - }) - }) -}) diff --git a/packages/wallet/wdk/test/idtoken.test.ts b/packages/wallet/wdk/test/idtoken.test.ts deleted file mode 100644 index 7badcb89a3..0000000000 --- a/packages/wallet/wdk/test/idtoken.test.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { IdentityInstrument, IdentityType } from '@0xsequence/identity-instrument' -import { IdTokenHandler, PromptIdTokenHandler } from '../src/sequence/handlers/idtoken.js' -import { Signatures } from '../src/sequence/signatures.js' -import * as Db from '../src/dbs/index.js' -import { IdentitySigner } from '../src/identity/signer.js' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' -import { Kinds } from '../src/sequence/types/signer.js' - -describe('IdTokenHandler', () => { - let idTokenHandler: IdTokenHandler - let mockNitroInstrument: IdentityInstrument - let mockSignatures: Signatures - let mockAuthKeys: Db.AuthKeys - let mockIdentitySigner: IdentitySigner - let testWallet: Address.Address - let testRequest: BaseSignatureRequest - let mockPromptIdToken: Mock - - beforeEach(() => { - vi.clearAllMocks() - - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - - mockNitroInstrument = { - commitVerifier: vi.fn(), - completeAuth: vi.fn(), - } as unknown as IdentityInstrument - - mockSignatures = { - addSignature: vi.fn(), - } as unknown as Signatures - - mockAuthKeys = { - set: vi.fn(), - get: vi.fn(), - del: vi.fn(), - delBySigner: vi.fn(), - getBySigner: vi.fn(), - addListener: vi.fn(), - } as unknown as Db.AuthKeys - - mockIdentitySigner = { - address: testWallet, - sign: vi.fn(), - } as unknown as IdentitySigner - - testRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: Payload.fromMessage(Hex.fromString('Test message')), - }, - } as BaseSignatureRequest - - mockPromptIdToken = vi.fn() - - idTokenHandler = new IdTokenHandler( - 'google-id-token', - 'https://accounts.google.com', - 'test-google-client-id', - mockNitroInstrument, - mockSignatures, - mockAuthKeys, - ) - - vi.spyOn(idTokenHandler as any, 'nitroCommitVerifier').mockResolvedValue({ - verifier: 'unused-verifier', - loginHint: '', - challenge: '', - }) - - vi.spyOn(idTokenHandler as any, 'nitroCompleteAuth').mockResolvedValue({ - signer: mockIdentitySigner, - email: 'user@example.com', - }) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - describe('Constructor', () => { - it('Should create IdTokenHandler with correct properties', () => { - const handler = new IdTokenHandler( - 'google-id-token', - 'https://accounts.google.com', - 'test-google-client-id', - mockNitroInstrument, - mockSignatures, - mockAuthKeys, - ) - - expect(handler.signupKind).toBe('google-id-token') - expect(handler.issuer).toBe('https://accounts.google.com') - expect(handler.audience).toBe('test-google-client-id') - expect(handler.identityType).toBe(IdentityType.OIDC) - expect(handler.kind).toBe(Kinds.LoginGoogle) - }) - - it('Should normalize apple-id-token handlers to login-apple', () => { - const handler = new IdTokenHandler( - 'apple-id-token', - 'https://appleid.apple.com', - 'test-apple-client-id', - mockNitroInstrument, - mockSignatures, - mockAuthKeys, - ) - - expect(handler.signupKind).toBe('apple-id-token') - expect(handler.issuer).toBe('https://appleid.apple.com') - expect(handler.audience).toBe('test-apple-client-id') - expect(handler.kind).toBe(Kinds.LoginApple) - }) - - it('Should initialize without a registered UI callback', () => { - expect(idTokenHandler['onPromptIdToken']).toBeUndefined() - }) - }) - - describe('UI Registration', () => { - it('Should register ID token UI callback', () => { - const unregister = idTokenHandler.registerUI(mockPromptIdToken) - - expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) - expect(typeof unregister).toBe('function') - }) - - it('Should unregister UI callback when returned function is called', () => { - const unregister = idTokenHandler.registerUI(mockPromptIdToken) - expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) - - unregister() - - expect(idTokenHandler['onPromptIdToken']).toBeUndefined() - }) - - it('Should unregister UI callback directly', () => { - idTokenHandler.registerUI(mockPromptIdToken) - expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) - - idTokenHandler.unregisterUI() - - expect(idTokenHandler['onPromptIdToken']).toBeUndefined() - }) - - it('Should allow multiple registrations by overwriting the previous callback', () => { - const secondCallback = vi.fn() - - idTokenHandler.registerUI(mockPromptIdToken) - expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) - - idTokenHandler.registerUI(secondCallback) - - expect(idTokenHandler['onPromptIdToken']).toBe(secondCallback) - }) - }) - - describe('completeAuth()', () => { - it('Should complete auth using an OIDC ID token challenge', async () => { - const idToken = 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.' - - const [signer, metadata] = await idTokenHandler.completeAuth(idToken) - - expect(idTokenHandler['nitroCommitVerifier']).toHaveBeenCalledWith( - expect.objectContaining({ - issuer: 'https://accounts.google.com', - audience: 'test-google-client-id', - idToken, - }), - ) - expect(idTokenHandler['nitroCompleteAuth']).toHaveBeenCalledWith( - expect.objectContaining({ - issuer: 'https://accounts.google.com', - audience: 'test-google-client-id', - idToken, - }), - ) - expect(signer).toBe(mockIdentitySigner) - expect(metadata).toEqual({ email: 'user@example.com' }) - }) - }) - - describe('getSigner()', () => { - it('Should throw when UI is not registered', async () => { - await expect(idTokenHandler.getSigner()).rejects.toThrow('id-token-handler-ui-not-registered') - }) - - it('Should acquire a signer by prompting for a fresh ID token', async () => { - const idToken = 'header.payload.signature' - const completeAuthSpy = vi - .spyOn(idTokenHandler, 'completeAuth') - .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) - - mockPromptIdToken.mockImplementation(async (kind, respond) => { - expect(kind).toBe('google-id-token') - await respond(idToken) - }) - - idTokenHandler.registerUI(mockPromptIdToken) - - const result = await idTokenHandler.getSigner() - - expect(result).toEqual({ - signer: mockIdentitySigner, - email: 'user@example.com', - }) - expect(completeAuthSpy).toHaveBeenCalledWith(idToken) - expect(mockPromptIdToken).toHaveBeenCalledWith('google-id-token', expect.any(Function)) - }) - - it('Should surface authentication failures from completeAuth', async () => { - const error = new Error('Authentication failed') - vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(error) - - mockPromptIdToken.mockImplementation(async (_kind, respond) => { - await respond('header.payload.signature') - }) - - idTokenHandler.registerUI(mockPromptIdToken) - - await expect(idTokenHandler.getSigner()).rejects.toThrow('Authentication failed') - }) - - it('Should surface UI callback errors', async () => { - mockPromptIdToken.mockRejectedValue(new Error('UI callback failed')) - idTokenHandler.registerUI(mockPromptIdToken) - - await expect(idTokenHandler.getSigner()).rejects.toThrow('UI callback failed') - }) - }) - - describe('status()', () => { - it('Should return ready status when an auth key signer is available', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - - expect(status.status).toBe('ready') - expect(status.handler).toBe(idTokenHandler) - }) - - it('Should sign the request when ready handle is invoked', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) - const signSpy = vi.spyOn(idTokenHandler as any, 'sign').mockResolvedValue(undefined) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - const handled = await (status as any).handle() - - expect(handled).toBe(true) - expect(signSpy).toHaveBeenCalledWith(mockIdentitySigner, testRequest) - }) - - it('Should return unavailable when no auth key signer exists and UI is not registered', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - - expect(status).toMatchObject({ - address: testWallet, - handler: idTokenHandler, - status: 'unavailable', - reason: 'ui-not-registered', - }) - }) - - it('Should return actionable when no auth key signer exists and UI is registered', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) - idTokenHandler.registerUI(mockPromptIdToken) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - - expect(status.status).toBe('actionable') - expect(status.address).toBe(testWallet) - expect(status.handler).toBe(idTokenHandler) - expect((status as any).message).toBe('request-id-token') - expect(typeof (status as any).handle).toBe('function') - }) - - it('Should reacquire the signer when actionable handle is invoked', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) - const completeAuthSpy = vi - .spyOn(idTokenHandler, 'completeAuth') - .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) - - mockPromptIdToken.mockImplementation(async (_kind, respond) => { - await respond('header.payload.signature') - }) - - idTokenHandler.registerUI(mockPromptIdToken) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - const handled = await (status as any).handle() - - expect(handled).toBe(true) - expect(completeAuthSpy).toHaveBeenCalledWith('header.payload.signature') - }) - - it('Should return false when actionable handle authentication fails', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) - vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(new Error('Authentication failed')) - - mockPromptIdToken.mockImplementation(async (_kind, respond) => { - await respond('header.payload.signature') - }) - - idTokenHandler.registerUI(mockPromptIdToken) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - const handled = await (status as any).handle() - - expect(handled).toBe(false) - }) - - it('Should return false when actionable handle authenticates the wrong signer', async () => { - vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) - const wrongSigner = '0x9999999999999999999999999999999999999999' as Address.Address - vi.spyOn(idTokenHandler, 'completeAuth').mockResolvedValue([ - { - ...mockIdentitySigner, - address: wrongSigner, - } as unknown as IdentitySigner, - { email: 'other-user@example.com' }, - ]) - - mockPromptIdToken.mockImplementation(async (_kind, respond) => { - await respond('header.payload.signature') - }) - - idTokenHandler.registerUI(mockPromptIdToken) - - const status = await idTokenHandler.status(testWallet, undefined, testRequest) - const handled = await (status as any).handle() - - expect(handled).toBe(false) - expect(mockAuthKeys.delBySigner).toHaveBeenCalledWith(wrongSigner) - }) - }) -}) diff --git a/packages/wallet/wdk/test/messages.test.ts b/packages/wallet/wdk/test/messages.test.ts deleted file mode 100644 index 32d68ffe5e..0000000000 --- a/packages/wallet/wdk/test/messages.test.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { Manager, SignerActionable } from '../src/sequence/index.js' -import { Mnemonic } from 'ox' -import { newManager } from './constants.js' -import { Network } from '@0xsequence/wallet-primitives' - -describe('Messages', () => { - let manager: Manager - - beforeEach(() => { - manager = newManager() - }) - - afterEach(async () => { - await manager.stop() - }) - - // === BASIC MESSAGE MANAGEMENT === - - it('Should start with empty message list', async () => { - const messages = await manager.messages.list() - expect(messages).toEqual([]) - }) - - it('Should create a basic message request', async () => { - // Create a wallet first - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const testMessage = 'Hello, World!' - - // Create message request - const signatureId = await manager.messages.request(wallet!, testMessage) - expect(signatureId).toBeDefined() - expect(typeof signatureId).toBe('string') - - // Verify message appears in list - const messages = await manager.messages.list() - expect(messages.length).toBe(1) - const message = messages[0]! - expect(message.wallet).toBe(wallet) - expect(message.message).toBe(testMessage) - expect(message.status).toBe('requested') - expect(message.signatureId).toBe(signatureId) - expect(message.source).toBe('unknown') - expect(message.id).toBeDefined() - }) - - it('Should create message request with custom source', async () => { - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Custom source message' - const customSource = 'test-dapp.com' - - await manager.messages.request(wallet!, testMessage, undefined, { source: customSource }) - - const messages = await manager.messages.list() - expect(messages.length).toBe(1) - - const message = messages[0]! - - expect(message.source).toBe(customSource) - expect(message.message).toBe(testMessage) - }) - - it('Should get message by ID', async () => { - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Test message for retrieval' - const signatureId = await manager.messages.request(wallet!, testMessage) - - const messages = await manager.messages.list() - expect(messages.length).toBe(1) - const messageId = messages[0]!.id - - // Get by message ID - const retrievedMessage = await manager.messages.get(messageId) - expect(retrievedMessage.id).toBe(messageId) - expect(retrievedMessage.message).toBe(testMessage) - expect(retrievedMessage.signatureId).toBe(signatureId) - - // Get by signature ID - const retrievedBySignature = await manager.messages.get(signatureId) - expect(retrievedBySignature.id).toBe(messageId) - expect(retrievedBySignature.message).toBe(testMessage) - }) - - it('Should throw error when getting non-existent message', async () => { - await expect(manager.messages.get('non-existent-id')).rejects.toThrow('Message non-existent-id not found') - }) - - it('Should complete message signing flow', async () => { - const mnemonic = Mnemonic.random(Mnemonic.english) - - const wallet = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Message to be signed' - const signatureId = await manager.messages.request(wallet!, testMessage) - - // Register mnemonic UI for signing - const unregisterUI = manager.registerMnemonicUI(async (respond) => { - await respond(mnemonic) - }) - - try { - // Get and sign the signature request - const sigRequest = await manager.signatures.get(signatureId) - const mnemonicSigner = sigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner?.status).toBe('actionable') - - await (mnemonicSigner as SignerActionable).handle() - - // Complete the message - const messageSignature = await manager.messages.complete(signatureId) - expect(messageSignature).toBeDefined() - expect(typeof messageSignature).toBe('string') - expect(messageSignature.startsWith('0x')).toBe(true) - - // Verify message status is now 'signed' - const completedMessage = await manager.messages.get(signatureId) - expect(completedMessage.status).toBe('signed') - expect((completedMessage as any).messageSignature).toBe(messageSignature) - } finally { - unregisterUI() - } - }) - - it('Should delete message request', async () => { - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Message to be deleted' - const signatureId = await manager.messages.request(wallet!, testMessage) - - // Verify message exists - let messages = await manager.messages.list() - expect(messages.length).toBe(1) - - // Delete the message - await manager.messages.delete(signatureId) - - // Verify message is gone - messages = await manager.messages.list() - expect(messages.length).toBe(0) - - // Should throw when getting deleted message - await expect(manager.messages.get(signatureId)).rejects.toThrow('Message ' + signatureId + ' not found') - }) - - it('Should handle multiple message requests', async () => { - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Create multiple messages - const messageTexts = ['First message', 'Second message', 'Third message'] - - const signatureIds: string[] = [] - for (const msg of messageTexts) { - const sigId = await manager.messages.request(wallet!, msg) - signatureIds.push(sigId) - } - - expect(signatureIds.length).toBe(3) - expect(new Set(signatureIds).size).toBe(3) // All unique - - const messageList = await manager.messages.list() - expect(messageList.length).toBe(3) - - // Verify all messages are present - const actualMessages = messageList.map((m) => m.message) - messageTexts.forEach((msg) => { - expect(actualMessages).toContain(msg) - }) - }) - - it('Should subscribe to messages updates', async () => { - manager = newManager(undefined, undefined, `msg_sub_${Date.now()}`) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - let updateCallCount = 0 - let lastMessages: any[] = [] - - const unsubscribe = manager.messages.onMessagesUpdate((messages) => { - updateCallCount++ - lastMessages = messages - }) - - try { - // Create a message - should trigger update - const testMessage = 'Subscription test message' - await manager.messages.request(wallet!, testMessage) - - // Wait a bit for async update - await new Promise((resolve) => setTimeout(resolve, 100)) - - expect(updateCallCount).toBeGreaterThan(0) - expect(lastMessages.length).toBe(1) - expect(lastMessages[0].message).toBe(testMessage) - } finally { - unsubscribe() - } - }) - - it('Should trigger messages update callback immediately when trigger=true', async () => { - manager = newManager(undefined, undefined, `msg_trigger_${Date.now()}`) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Create a message first - await manager.messages.request(wallet!, 'Pre-existing message') - - let immediateCallCount = 0 - let receivedMessages: any[] = [] - - const unsubscribe = manager.messages.onMessagesUpdate((messages) => { - immediateCallCount++ - receivedMessages = messages - }, true) // trigger=true for immediate callback - - // Wait a bit for the async trigger callback - await new Promise((resolve) => setTimeout(resolve, 50)) - - // Should have been called immediately - expect(immediateCallCount).toBe(1) - expect(receivedMessages.length).toBe(1) - expect(receivedMessages[0].message).toBe('Pre-existing message') - - unsubscribe() - }) - - it('Should subscribe to single message updates', async () => { - manager = newManager(undefined, undefined, `msg_single_sub_${Date.now()}`) - const mnemonic = Mnemonic.random(Mnemonic.english) - - const wallet = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Single message subscription test' - const signatureId = await manager.messages.request(wallet!, testMessage) - - const messages = await manager.messages.list() - const messageId = messages[0]!.id - - let updateCallCount = 0 - let lastMessage: any - - const unsubscribe = manager.messages.onMessageUpdate(messageId, (message) => { - updateCallCount++ - lastMessage = message - }) - - try { - // Sign the message to trigger an update - const unregisterUI = manager.registerMnemonicUI(async (respond) => { - await respond(mnemonic) - }) - - const sigRequest = await manager.signatures.get(signatureId) - const mnemonicSigner = sigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic') - await (mnemonicSigner as SignerActionable).handle() - unregisterUI() - - await manager.messages.complete(signatureId) - - // Wait for async update - await new Promise((resolve) => setTimeout(resolve, 100)) - - expect(updateCallCount).toBeGreaterThan(0) - expect(lastMessage?.status).toBe('signed') - } finally { - unsubscribe() - } - }) - - it('Should trigger single message update callback immediately when trigger=true', async () => { - manager = newManager(undefined, undefined, `msg_single_trigger_${Date.now()}`) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Immediate single message trigger test' - await manager.messages.request(wallet!, testMessage) - - const messages = await manager.messages.list() - const messageId = messages[0]!.id - - let callCount = 0 - let receivedMessage: any - - const unsubscribe = manager.messages.onMessageUpdate( - messageId, - (message) => { - callCount++ - receivedMessage = message - }, - true, - ) // trigger=true for immediate callback - - // Wait a bit for the async trigger callback - await new Promise((resolve) => setTimeout(resolve, 50)) - - // Should have been called immediately - expect(callCount).toBe(1) - expect(receivedMessage?.id).toBe(messageId) - expect(receivedMessage?.message).toBe(testMessage) - - unsubscribe() - }) - - it('Should handle message completion with chainId and network lookup', async () => { - manager = newManager(undefined, undefined, `msg_chainid_${Date.now()}`) - const mnemonic = Mnemonic.random(Mnemonic.english) - - const wallet = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Message with chainId for network lookup' - const signatureId = await manager.messages.request(wallet!, testMessage, Network.ChainId.ARBITRUM) - - const unregisterUI = manager.registerMnemonicUI(async (respond) => { - await respond(mnemonic) - }) - - try { - const sigRequest = await manager.signatures.get(signatureId) - const mnemonicSigner = sigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic') - await (mnemonicSigner as SignerActionable).handle() - - // This should trigger the network lookup code path (lines 194-200) - const messageSignature = await manager.messages.complete(signatureId) - expect(messageSignature).toBeDefined() - expect(typeof messageSignature).toBe('string') - expect(messageSignature.startsWith('0x')).toBe(true) - } finally { - unregisterUI() - } - }) - - it('Should throw error for unsupported network in message completion', async () => { - manager = newManager(undefined, undefined, `msg_bad_network_${Date.now()}`) - const mnemonic = Mnemonic.random(Mnemonic.english) - - const wallet = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Message with unsupported chainId' - // Use an unsupported chainId - const signatureId = await manager.messages.request(wallet!, testMessage, 999999) - - const unregisterUI = manager.registerMnemonicUI(async (respond) => { - await respond(mnemonic) - }) - - try { - const sigRequest = await manager.signatures.get(signatureId) - const mnemonicSigner = sigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic') - await (mnemonicSigner as SignerActionable).handle() - - // This should trigger the network not found error (lines 195-196) - await expect(manager.messages.complete(signatureId)).rejects.toThrow('Network not found for 999999') - } finally { - unregisterUI() - } - }) - - it('Should handle delete with non-existent message gracefully', async () => { - manager = newManager(undefined, undefined, `msg_delete_error_${Date.now()}`) - - // This should trigger the catch block in delete (line 247) - // Should not throw, just silently ignore - await expect(manager.messages.delete('non-existent-message-id')).resolves.toBeUndefined() - }) - - it('Should throw insufficient weight error when completing unsigned message', async () => { - manager = newManager(undefined, undefined, `msg_insufficient_weight_${Date.now()}`) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const testMessage = 'Message with insufficient weight' - const signatureId = await manager.messages.request(wallet!, testMessage) - - // Try to complete without signing - should trigger insufficient weight error (lines 188-189) - await expect(manager.messages.complete(signatureId)).rejects.toThrow('insufficient weight') - }) -}) diff --git a/packages/wallet/wdk/test/otp.test.ts b/packages/wallet/wdk/test/otp.test.ts deleted file mode 100644 index 8229dd7610..0000000000 --- a/packages/wallet/wdk/test/otp.test.ts +++ /dev/null @@ -1,750 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { IdentityInstrument, IdentityType, KeyType } from '@0xsequence/identity-instrument' -import { OtpHandler, PromptOtpHandler } from '../src/sequence/handlers/otp.js' -import { Signatures } from '../src/sequence/signatures.js' -import * as Db from '../src/dbs/index.js' -import { IdentitySigner } from '../src/identity/signer.js' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' -import { Kinds } from '../src/sequence/types/signer.js' - -// Mock the global crypto API -const mockCryptoSubtle = { - sign: vi.fn(), - generateKey: vi.fn(), - exportKey: vi.fn(), -} - -Object.defineProperty(global, 'window', { - value: { - crypto: { - subtle: mockCryptoSubtle, - }, - }, - writable: true, -}) - -// Mock dependencies with proper vi.fn() types -const mockCommitVerifier = vi.fn() -const mockCompleteAuth = vi.fn() -const mockAddSignature = vi.fn() -const mockGetBySigner = vi.fn() -const mockDelBySigner = vi.fn() -const mockAuthKeysSet = vi.fn() -const mockAddListener = vi.fn() - -const mockIdentityInstrument = { - commitVerifier: mockCommitVerifier, - completeAuth: mockCompleteAuth, -} as unknown as IdentityInstrument - -const mockSignatures = { - addSignature: mockAddSignature, -} as unknown as Signatures - -const mockAuthKeys = { - getBySigner: mockGetBySigner, - delBySigner: mockDelBySigner, - set: mockAuthKeysSet, - addListener: mockAddListener, -} as unknown as Db.AuthKeys - -// Mock the OtpChallenge constructor and methods -vi.mock('@0xsequence/identity-instrument', async () => { - const actual = await vi.importActual('@0xsequence/identity-instrument') - return { - ...actual, - OtpChallenge: { - fromRecipient: vi.fn(), - fromSigner: vi.fn(), - }, - } -}) - -// Import the mocked version -const { OtpChallenge: MockedOtpChallenge } = await import('@0xsequence/identity-instrument') - -describe('OtpHandler', () => { - let otpHandler: OtpHandler - let testWallet: Address.Address - let testRequest: BaseSignatureRequest - let mockPromptOtp: Mock - - beforeEach(() => { - vi.clearAllMocks() - - testWallet = '0x1234567890123456789012345678901234567890' as Address.Address - - // Create mock CryptoKey - const mockCryptoKey = { - algorithm: { name: 'ECDSA', namedCurve: 'P-256' }, - extractable: false, - type: 'private', - usages: ['sign'], - } as CryptoKey - - mockCryptoSubtle.generateKey.mockResolvedValue({ - publicKey: {} as CryptoKey, - privateKey: mockCryptoKey, - }) - - mockCryptoSubtle.exportKey.mockResolvedValue(new ArrayBuffer(64)) - - testRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: Payload.fromMessage(Hex.fromString('Test message')), - }, - } as BaseSignatureRequest - - mockPromptOtp = vi.fn() - - otpHandler = new OtpHandler(mockIdentityInstrument, mockSignatures, mockAuthKeys) - - // Setup mock OtpChallenge instances - const mockChallengeInstance = { - withAnswer: vi.fn().mockReturnThis(), - getCommitParams: vi.fn(), - getCompleteParams: vi.fn(), - } - - ;(MockedOtpChallenge.fromRecipient as any).mockReturnValue(mockChallengeInstance) - ;(MockedOtpChallenge.fromSigner as any).mockReturnValue(mockChallengeInstance) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - // === CONSTRUCTOR AND PROPERTIES === - - describe('Constructor', () => { - it('Should create OtpHandler with correct properties', () => { - const handler = new OtpHandler(mockIdentityInstrument, mockSignatures, mockAuthKeys) - - expect(handler.kind).toBe(Kinds.LoginEmailOtp) - expect(handler.identityType).toBe(IdentityType.Email) - }) - - it('Should initialize without UI callback registered', () => { - expect(otpHandler['onPromptOtp']).toBeUndefined() - }) - }) - - // === UI REGISTRATION === - - describe('UI Registration', () => { - it('Should register OTP UI callback', () => { - const mockCallback = vi.fn() - - const unregister = otpHandler.registerUI(mockCallback) - - expect(otpHandler['onPromptOtp']).toBe(mockCallback) - expect(typeof unregister).toBe('function') - }) - - it('Should unregister UI callback when returned function is called', () => { - const mockCallback = vi.fn() - - const unregister = otpHandler.registerUI(mockCallback) - expect(otpHandler['onPromptOtp']).toBe(mockCallback) - - unregister() - expect(otpHandler['onPromptOtp']).toBeUndefined() - }) - - it('Should unregister UI callback directly', () => { - const mockCallback = vi.fn() - - otpHandler.registerUI(mockCallback) - expect(otpHandler['onPromptOtp']).toBe(mockCallback) - - otpHandler.unregisterUI() - expect(otpHandler['onPromptOtp']).toBeUndefined() - }) - - it('Should allow multiple registrations (overwriting previous)', () => { - const firstCallback = vi.fn() - const secondCallback = vi.fn() - - otpHandler.registerUI(firstCallback) - expect(otpHandler['onPromptOtp']).toBe(firstCallback) - - otpHandler.registerUI(secondCallback) - expect(otpHandler['onPromptOtp']).toBe(secondCallback) - }) - }) - - // === GET SIGNER METHOD === - - describe('getSigner()', () => { - beforeEach(() => { - // Setup successful nitro operations - mockCommitVerifier.mockResolvedValue({ - loginHint: 'test@example.com', - challenge: 'test-challenge-code', - }) - - mockCompleteAuth.mockResolvedValue({ - signer: {} as IdentitySigner, - identity: { email: 'test@example.com' }, - }) - - // Mock auth key for successful operations - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - }) - - it('Should throw error when UI is not registered', async () => { - const email = 'test@example.com' - - await expect(otpHandler.getSigner(email)).rejects.toThrow('otp-handler-ui-not-registered') - }) - - it.skip('Should successfully get signer with valid OTP flow', async () => { - const email = 'test@example.com' - const otp = '123456' - - // Setup UI callback to automatically respond with OTP - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - expect(recipient).toBe('test@example.com') - await respond(otp) - }) - - otpHandler.registerUI(mockCallback) - - const result = await otpHandler.getSigner(email) - - expect(result.signer).toBeDefined() - expect(result.email).toBe('test@example.com') - - // Verify OtpChallenge.fromRecipient was called - expect(MockedOtpChallenge.fromRecipient).toHaveBeenCalledWith(IdentityType.Email, email) - - // Verify nitro operations were called - expect(mockCommitVerifier).toHaveBeenCalledOnce() - expect(mockCompleteAuth).toHaveBeenCalledOnce() - - // Verify UI callback was called - expect(mockCallback).toHaveBeenCalledWith('test@example.com', expect.any(Function)) - }) - - it('Should handle OTP verification failure', async () => { - const email = 'test@example.com' - const otp = 'wrong-otp' - - // Setup nitroCompleteAuth to fail - mockCompleteAuth.mockRejectedValueOnce(new Error('Invalid OTP')) - - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - await respond(otp) - }) - - otpHandler.registerUI(mockCallback) - - await expect(otpHandler.getSigner(email)).rejects.toThrow('Invalid OTP') - }) - - it('Should handle commitVerifier failure', async () => { - const email = 'test@example.com' - - // Setup commitVerifier to fail - mockCommitVerifier.mockRejectedValueOnce(new Error('Commit verification failed')) - - otpHandler.registerUI(mockPromptOtp) - - await expect(otpHandler.getSigner(email)).rejects.toThrow('Commit verification failed') - }) - - it.skip('Should handle UI callback errors', async () => { - const email = 'test@example.com' - - const mockCallback = vi.fn().mockRejectedValueOnce(new Error('UI callback failed')) - otpHandler.registerUI(mockCallback) - - await expect(otpHandler.getSigner(email)).rejects.toThrow('UI callback failed') - }, 10000) // Add longer timeout - - it.skip('Should pass correct challenge to withAnswer', async () => { - const email = 'test@example.com' - const otp = '123456' - const mockWithAnswer = vi.fn().mockReturnThis() - - const mockChallengeInstance = { - withAnswer: mockWithAnswer, - getCommitParams: vi.fn(), - getCompleteParams: vi.fn(), - } - - ;(MockedOtpChallenge.fromRecipient as any).mockReturnValue(mockChallengeInstance) - - // Ensure proper return structure with identity.email - mockCompleteAuth.mockResolvedValueOnce({ - signer: {} as IdentitySigner, - identity: { email: 'test@example.com' }, - }) - - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - await respond(otp) - }) - - otpHandler.registerUI(mockCallback) - - await otpHandler.getSigner(email) - - expect(mockWithAnswer).toHaveBeenCalledWith('test-challenge-code', otp) - }) - }) - - // === STATUS METHOD === - - describe('status()', () => { - it('Should return ready status when auth key signer exists', async () => { - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: testWallet, - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - - expect(result.status).toBe('ready') - expect(result.address).toBe(testWallet) - expect(result.handler).toBe(otpHandler) - expect(typeof (result as any).handle).toBe('function') - }) - - it('Should execute signing when handle is called on ready status', async () => { - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: testWallet, - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - - // Mock the signer's sign method - const mockSignature = { - type: 'hash' as const, - r: 0x1234567890abcdefn, - s: 0xfedcba0987654321n, - yParity: 0, - } - - vi.spyOn(IdentitySigner.prototype, 'sign').mockResolvedValueOnce(mockSignature) - - const handleResult = await (result as any).handle() - - expect(handleResult).toBe(true) - expect(mockAddSignature).toHaveBeenCalledOnce() - expect(mockAddSignature).toHaveBeenCalledWith(testRequest.id, { - address: testWallet, - signature: mockSignature, - }) - }) - - it('Should return unavailable status when UI is not registered and no auth key exists', async () => { - mockGetBySigner.mockResolvedValueOnce(null) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - - expect(result.status).toBe('unavailable') - expect(result.address).toBe(testWallet) - expect(result.handler).toBe(otpHandler) - expect((result as any).reason).toBe('ui-not-registered') - }) - - it('Should return actionable status when UI is registered and no auth key exists', async () => { - mockGetBySigner.mockResolvedValueOnce(null) - otpHandler.registerUI(mockPromptOtp) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - - expect(result.status).toBe('actionable') - expect(result.address).toBe(testWallet) - expect(result.handler).toBe(otpHandler) - expect((result as any).message).toBe('request-otp') - expect(typeof (result as any).handle).toBe('function') - }) - - it.skip('Should handle OTP authentication when actionable handle is called', async () => { - mockGetBySigner.mockResolvedValueOnce(null) - - // Setup successful nitro operations - mockCommitVerifier.mockResolvedValue({ - loginHint: 'user@example.com', - challenge: 'challenge-code', - }) - mockCompleteAuth.mockResolvedValue({ - signer: {} as IdentitySigner, - identity: { email: 'user@example.com' }, - }) - - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - expect(recipient).toBe('user@example.com') - await respond('123456') - }) - - otpHandler.registerUI(mockCallback) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - const handleResult = await (result as any).handle() - - expect(handleResult).toBe(true) - expect(MockedOtpChallenge.fromSigner).toHaveBeenCalledWith(IdentityType.Email, { - address: testWallet, - keyType: KeyType.Ethereum_Secp256k1, - }) - expect(mockCallback).toHaveBeenCalledWith('user@example.com', expect.any(Function)) - }) - - it('Should handle OTP authentication failure in actionable handle', async () => { - mockGetBySigner.mockResolvedValueOnce(null) - - mockCommitVerifier.mockResolvedValue({ - loginHint: 'user@example.com', - challenge: 'challenge-code', - }) - mockCompleteAuth.mockRejectedValueOnce(new Error('Authentication failed')) - - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - await respond('wrong-otp') - }) - - otpHandler.registerUI(mockCallback) - - const result = await otpHandler.status(testWallet, undefined, testRequest) - - // The handle resolves to false because of the try/catch in the code - const handleResult = await (result as any).handle() - expect(handleResult).toBe(false) - }) - }) - - // === INHERITED METHODS FROM IDENTITYHANDLER === - - describe('Inherited IdentityHandler methods', () => { - it('Should provide onStatusChange listener', () => { - const mockUnsubscribe = vi.fn() - mockAddListener.mockReturnValueOnce(mockUnsubscribe) - - const callback = vi.fn() - const unsubscribe = otpHandler.onStatusChange(callback) - - expect(mockAddListener).toHaveBeenCalledWith(callback) - expect(unsubscribe).toBe(mockUnsubscribe) - }) - - it('Should handle nitroCommitVerifier with OTP challenge', async () => { - const mockChallenge = { - getCommitParams: vi.fn().mockReturnValue({ - authMode: 'OTP', - identityType: 'Email', - handle: 'test@example.com', - metadata: {}, - }), - getCompleteParams: vi.fn(), - } - - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCommitVerifier.mockResolvedValueOnce({ - loginHint: 'test@example.com', - challenge: 'challenge-code', - }) - - const result = await otpHandler['nitroCommitVerifier'](mockChallenge) - - expect(mockDelBySigner).toHaveBeenCalledWith('') - expect(mockCommitVerifier).toHaveBeenCalledWith( - expect.objectContaining({ - address: mockAuthKey.address, - keyType: KeyType.WebCrypto_Secp256r1, - signer: mockAuthKey.identitySigner, - }), - mockChallenge, - ) - expect(result).toEqual({ - loginHint: 'test@example.com', - challenge: 'challenge-code', - }) - }) - - it('Should handle nitroCompleteAuth with OTP challenge', async () => { - const mockChallenge = { - getCommitParams: vi.fn(), - getCompleteParams: vi.fn().mockReturnValue({ - authMode: 'OTP', - identityType: 'Email', - verifier: 'test@example.com', - answer: '0xabcd1234', - }), - } - - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - const mockIdentityResult = { - signer: { address: testWallet }, - identity: { email: 'test@example.com' }, - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCompleteAuth.mockResolvedValueOnce(mockIdentityResult) - - const result = await otpHandler['nitroCompleteAuth'](mockChallenge) - - expect(mockCompleteAuth).toHaveBeenCalledWith( - expect.objectContaining({ - address: mockAuthKey.address, - }), - mockChallenge, - ) - - // Verify auth key cleanup and updates - expect(mockDelBySigner).toHaveBeenCalledWith('') - expect(mockDelBySigner).toHaveBeenCalledWith(testWallet) - expect(mockAuthKeysSet).toHaveBeenCalledWith( - expect.objectContaining({ - identitySigner: testWallet, - }), - ) - - expect(result.signer).toBeInstanceOf(IdentitySigner) - expect(result.email).toBe('test@example.com') - }) - }) - - // === ERROR HANDLING === - - describe('Error Handling', () => { - it('Should handle missing auth key in nitroCommitVerifier', async () => { - const mockChallenge = {} as any - mockGetBySigner.mockResolvedValueOnce(null) - - // Make crypto operations fail to prevent auto-generation - mockCryptoSubtle.generateKey.mockRejectedValueOnce(new Error('Crypto not available')) - - await expect(otpHandler['nitroCommitVerifier'](mockChallenge)).rejects.toThrow('Crypto not available') - }) - - it('Should handle missing auth key in nitroCompleteAuth', async () => { - const mockChallenge = {} as any - mockGetBySigner.mockResolvedValueOnce(null) - - // Make crypto operations fail to prevent auto-generation - mockCryptoSubtle.generateKey.mockRejectedValueOnce(new Error('Crypto not available')) - - await expect(otpHandler['nitroCompleteAuth'](mockChallenge)).rejects.toThrow('Crypto not available') - }) - - it('Should handle identity instrument failures', async () => { - const mockChallenge = {} as any - const mockAuthKey = { - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - } - - mockGetBySigner.mockResolvedValueOnce(mockAuthKey) - mockCommitVerifier.mockRejectedValueOnce(new Error('Identity service error')) - - await expect(otpHandler['nitroCommitVerifier'](mockChallenge)).rejects.toThrow('Identity service error') - }) - - it('Should handle auth keys database errors', async () => { - mockGetBySigner.mockRejectedValueOnce(new Error('Database error')) - - await expect(otpHandler.status(testWallet, undefined, testRequest)).rejects.toThrow('Database error') - }) - - it('Should handle invalid email addresses', async () => { - const invalidEmail = '' - - otpHandler.registerUI(mockPromptOtp) - - await expect(otpHandler.getSigner(invalidEmail)).rejects.toThrow() - }) - }) - - // === INTEGRATION TESTS === - - describe('Integration Tests', () => { - it('Should handle complete OTP flow from registration to signing', async () => { - const email = 'integration@example.com' - const otp = '654321' - - // Setup successful operations with proper structure - mockCommitVerifier.mockResolvedValue({ - loginHint: email, - challenge: 'integration-challenge', - }) - - mockCompleteAuth.mockResolvedValue({ - signer: {} as IdentitySigner, - identity: { email: email }, - }) - - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - // Step 1: Register UI - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - expect(recipient).toBe(email) - await respond(otp) - }) - - const unregister = otpHandler.registerUI(mockCallback) - - // Step 2: Get signer - const signerResult = await otpHandler.getSigner(email) - - expect(signerResult.signer).toBeDefined() - expect(signerResult.email).toBe(email) - - // Step 3: Check status (should be ready now) - mockGetBySigner.mockResolvedValueOnce({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: testWallet, - expiresAt: new Date(Date.now() + 3600000), - }) - - const statusResult = await otpHandler.status(testWallet, undefined, testRequest) - expect(statusResult.status).toBe('ready') - - // Step 4: Cleanup - unregister() - expect(otpHandler['onPromptOtp']).toBeUndefined() - }) - - it('Should handle OTP flow with different identity types', async () => { - // Test with different identity type (although constructor uses Email) - const customHandler = new OtpHandler(mockIdentityInstrument, mockSignatures, mockAuthKeys) - - expect(customHandler.identityType).toBe(IdentityType.Email) - expect(customHandler.kind).toBe(Kinds.LoginEmailOtp) - }) - - it('Should handle concurrent OTP requests', async () => { - const email1 = 'user1@example.com' - const email2 = 'user2@example.com' - - let requestCount = 0 - const mockCallback = vi.fn().mockImplementation(async (recipient, respond) => { - requestCount++ - const otp = `otp-${requestCount}` - await respond(otp) - }) - - otpHandler.registerUI(mockCallback) - - // Setup commit verifier for both requests - mockCommitVerifier - .mockResolvedValueOnce({ - loginHint: email1, - challenge: 'challenge1', - }) - .mockResolvedValueOnce({ - loginHint: email2, - challenge: 'challenge2', - }) - - // Setup complete auth with proper structure - mockCompleteAuth - .mockResolvedValueOnce({ - signer: {} as IdentitySigner, - identity: { email: email1 }, - }) - .mockResolvedValueOnce({ - signer: {} as IdentitySigner, - identity: { email: email2 }, - }) - - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - // Execute concurrent requests - const [result1, result2] = await Promise.all([otpHandler.getSigner(email1), otpHandler.getSigner(email2)]) - - expect(result1.email).toBe(email1) - expect(result2.email).toBe(email2) - expect(mockCallback).toHaveBeenCalledTimes(2) - }) - - it('Should handle UI callback replacement during operation', async () => { - const email = 'test@example.com' - - const firstCallback = vi.fn().mockImplementation(async (recipient, respond) => { - // This should not be called - await respond('first-otp') - }) - - const secondCallback = vi.fn().mockImplementation(async (recipient, respond) => { - await respond('second-otp') - }) - - // Register first callback - otpHandler.registerUI(firstCallback) - - // Setup async operations with proper structure - mockCommitVerifier.mockResolvedValue({ - loginHint: email, - challenge: 'challenge', - }) - - mockCompleteAuth.mockResolvedValue({ - signer: {} as IdentitySigner, - identity: { email: email }, - }) - - mockGetBySigner.mockResolvedValue({ - address: '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1', - privateKey: {} as CryptoKey, - identitySigner: '', - expiresAt: new Date(Date.now() + 3600000), - }) - - // Replace callback before getSigner completes - otpHandler.registerUI(secondCallback) - - const result = await otpHandler.getSigner(email) - - expect(result.email).toBe(email) - expect(secondCallback).toHaveBeenCalledOnce() - expect(firstCallback).not.toHaveBeenCalled() - }) - }) -}) diff --git a/packages/wallet/wdk/test/passkeys.test.ts b/packages/wallet/wdk/test/passkeys.test.ts deleted file mode 100644 index e73d0f4e3a..0000000000 --- a/packages/wallet/wdk/test/passkeys.test.ts +++ /dev/null @@ -1,640 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { State } from '@0xsequence/wallet-core' -import { Extensions } from '@0xsequence/wallet-primitives' -import { PasskeysHandler } from '../src/sequence/handlers/passkeys.js' -import { Signatures } from '../src/sequence/signatures.js' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' -import { Kinds } from '../src/sequence/types/signer.js' - -// Mock dependencies with proper vi.fn() types -const mockAddSignature = vi.fn() -const mockGetWalletsForSapient = vi.fn() -const mockGetWitnessForSapient = vi.fn() -const mockGetConfiguration = vi.fn() -const mockGetDeploy = vi.fn() -const mockGetWallets = vi.fn() -const mockGetWitnessFor = vi.fn() -const mockGetConfigurationUpdates = vi.fn() -const mockGetTree = vi.fn() -const mockGetPayload = vi.fn() -const mockSignSapient = vi.fn() -const mockLoadFromWitness = vi.fn() - -const mockSignatures = { - addSignature: mockAddSignature, -} as unknown as Signatures - -const mockStateReader = { - getWalletsForSapient: mockGetWalletsForSapient, - getWitnessForSapient: mockGetWitnessForSapient, - getConfiguration: mockGetConfiguration, - getDeploy: mockGetDeploy, - getWallets: mockGetWallets, - getWitnessFor: mockGetWitnessFor, - getConfigurationUpdates: mockGetConfigurationUpdates, - getTree: mockGetTree, - getPayload: mockGetPayload, -} as unknown as State.Reader - -const mockExtensions = { - passkeys: '0x1234567890123456789012345678901234567890' as Address.Address, -} as Pick - -// Mock the Extensions.Passkeys.decode function -vi.mock('@0xsequence/wallet-primitives', async () => { - const actual = await vi.importActual('@0xsequence/wallet-primitives') - return { - ...actual, - Extensions: { - ...((actual as any).Extensions || {}), - Passkeys: { - ...((actual as any).Extensions?.Passkeys || {}), - decode: vi.fn().mockReturnValue({ - embedMetadata: false, - }), - }, - }, - } -}) - -// Mock the Signers.Passkey.Passkey class - need to mock it directly -vi.mock('@0xsequence/wallet-core', async () => { - const actual = await vi.importActual('@0xsequence/wallet-core') - return { - ...actual, - Signers: { - ...((actual as any).Signers || {}), - Passkey: { - Passkey: { - loadFromWitness: mockLoadFromWitness, - }, - }, - }, - } -}) - -describe('PasskeysHandler', () => { - let passkeysHandler: PasskeysHandler - let testWallet: Address.Address - let testImageHash: Hex.Hex - let testRequest: BaseSignatureRequest - let mockPasskey: any - - beforeEach(() => { - vi.clearAllMocks() - - testWallet = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' as Address.Address - testImageHash = '0x1111111111111111111111111111111111111111111111111111111111111111' as Hex.Hex - - testRequest = { - id: 'test-request-id', - envelope: { - wallet: testWallet, - chainId: Network.ChainId.ARBITRUM, - payload: Payload.fromMessage(Hex.fromString('Test message')), - }, - } as BaseSignatureRequest - - // Create mock passkey object - mockPasskey = { - address: mockExtensions.passkeys, - imageHash: testImageHash, - credentialId: 'test-credential-id', - signSapient: mockSignSapient, - } - - // Setup mock witness data for getWitnessForSapient with proper structure - const witnessMessage = { - action: 'consent-to-be-part-of-wallet', - publicKey: { - x: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - y: '0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321', - requireUserVerification: true, - metadata: { - credentialId: 'test-credential-id', - name: 'Test Passkey', - }, - }, - metadata: { - credentialId: 'test-credential-id', - name: 'Test Passkey', - }, - } - - const mockWitness = { - chainId: Network.ChainId.ARBITRUM, - payload: Payload.fromMessage(Hex.fromString(JSON.stringify(witnessMessage))), - signature: { - type: 'sapient-signer-leaf' as const, - data: '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', // Mock encoded signature data - }, - } - - mockGetWitnessForSapient.mockResolvedValue(mockWitness) - - passkeysHandler = new PasskeysHandler(mockSignatures, mockExtensions, mockStateReader) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - // === CONSTRUCTOR AND PROPERTIES === - - describe('Constructor', () => { - it('Should create PasskeysHandler with correct properties', () => { - const handler = new PasskeysHandler(mockSignatures, mockExtensions, mockStateReader) - - expect(handler.kind).toBe(Kinds.LoginPasskey) - }) - - it('Should store dependencies correctly', () => { - expect(passkeysHandler['signatures']).toBe(mockSignatures) - expect(passkeysHandler['extensions']).toBe(mockExtensions) - expect(passkeysHandler['stateReader']).toBe(mockStateReader) - }) - }) - - // === ON STATUS CHANGE === - - describe('onStatusChange()', () => { - it('Should return a no-op unsubscribe function', () => { - const mockCallback = vi.fn() - - const unsubscribe = passkeysHandler.onStatusChange(mockCallback) - - expect(typeof unsubscribe).toBe('function') - - // Calling the unsubscribe function should not throw - expect(() => unsubscribe()).not.toThrow() - - // The callback should not be called since it's a no-op implementation - expect(mockCallback).not.toHaveBeenCalled() - }) - - it('Should not call the provided callback', () => { - const mockCallback = vi.fn() - - passkeysHandler.onStatusChange(mockCallback) - - expect(mockCallback).not.toHaveBeenCalled() - }) - }) - - // === LOAD PASSKEY (PRIVATE METHOD) === - - describe('loadPasskey() private method', () => { - it.skip('Should successfully load passkey when loadFromWitness succeeds', async () => { - mockLoadFromWitness.mockResolvedValueOnce(mockPasskey) - - const result = await passkeysHandler['loadPasskey'](testWallet, testImageHash) - - expect(result).toBe(mockPasskey) - expect(mockLoadFromWitness).toHaveBeenCalledWith(mockStateReader, mockExtensions, testWallet, testImageHash) - }) - - it('Should return undefined when loadFromWitness fails', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockLoadFromWitness.mockRejectedValueOnce(new Error('Failed to load passkey')) - - const result = await passkeysHandler['loadPasskey'](testWallet, testImageHash) - - expect(result).toBeUndefined() - expect(consoleSpy).toHaveBeenCalledWith('Failed to load passkey:', expect.any(Error)) - - consoleSpy.mockRestore() - }) - - it('Should handle various error types gracefully', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - - // Test with string error - mockLoadFromWitness.mockRejectedValueOnce('String error') - let result = await passkeysHandler['loadPasskey'](testWallet, testImageHash) - expect(result).toBeUndefined() - - // Test with null error - mockLoadFromWitness.mockRejectedValueOnce(null) - result = await passkeysHandler['loadPasskey'](testWallet, testImageHash) - expect(result).toBeUndefined() - - consoleSpy.mockRestore() - }) - }) - - // === STATUS METHOD === - - describe('status()', () => { - describe('Address mismatch scenarios', () => { - it('Should return unavailable when address does not match passkey module address', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - const wrongAddress = '0x9999999999999999999999999999999999999999' as Address.Address - - const result = await passkeysHandler.status(wrongAddress, testImageHash, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - expect(result.address).toBe(wrongAddress) - expect(result.imageHash).toBe(testImageHash) - expect(result.handler).toBe(passkeysHandler) - - expect(consoleSpy).toHaveBeenCalledWith( - 'PasskeySigner: status address does not match passkey module address', - wrongAddress, - mockExtensions.passkeys, - ) - - consoleSpy.mockRestore() - }) - - it('Should not attempt to load passkey when address mismatches', async () => { - const wrongAddress = '0x9999999999999999999999999999999999999999' as Address.Address - vi.spyOn(console, 'warn').mockImplementation(() => {}) - - await passkeysHandler.status(wrongAddress, testImageHash, testRequest) - - expect(mockLoadFromWitness).not.toHaveBeenCalled() - }) - }) - - describe('Missing imageHash scenarios', () => { - it('Should return unavailable when imageHash is undefined', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - - const result = await passkeysHandler.status(mockExtensions.passkeys, undefined, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - expect(result.address).toBe(mockExtensions.passkeys) - expect(result.imageHash).toBeUndefined() - expect(result.handler).toBe(passkeysHandler) - - expect(consoleSpy).toHaveBeenCalledWith( - 'PasskeySigner: status failed to load passkey', - mockExtensions.passkeys, - undefined, - ) - - consoleSpy.mockRestore() - }) - - it('Should not attempt to load passkey when imageHash is undefined', async () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - - await passkeysHandler.status(mockExtensions.passkeys, undefined, testRequest) - - expect(mockLoadFromWitness).not.toHaveBeenCalled() - }) - }) - - describe('Failed passkey loading scenarios', () => { - it('Should return unavailable when passkey loading fails', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockLoadFromWitness.mockResolvedValueOnce(undefined) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - expect(result.address).toBe(mockExtensions.passkeys) - expect(result.imageHash).toBe(testImageHash) - expect(result.handler).toBe(passkeysHandler) - - expect(consoleSpy).toHaveBeenCalledWith( - 'PasskeySigner: status failed to load passkey', - mockExtensions.passkeys, - testImageHash, - ) - - consoleSpy.mockRestore() - }) - - it.skip('Should attempt to load passkey with correct parameters', async () => { - vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockLoadFromWitness.mockResolvedValueOnce(undefined) - - await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(mockLoadFromWitness).toHaveBeenCalledWith( - mockStateReader, - mockExtensions, - testRequest.envelope.wallet, - testImageHash, - ) - }) - }) - - describe('Successful passkey loading scenarios', () => { - beforeEach(() => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - }) - - it.skip('Should return actionable status when passkey is successfully loaded', async () => { - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(result.status).toBe('actionable') - expect((result as any).message).toBe('request-interaction-with-passkey') - expect(result.address).toBe(mockExtensions.passkeys) - expect(result.imageHash).toBe(testImageHash) - expect(result.handler).toBe(passkeysHandler) - expect(typeof (result as any).handle).toBe('function') - }) - - it.skip('Should execute passkey signing when handle is called', async () => { - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - } - - mockSignSapient.mockResolvedValueOnce(mockSignature) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - const handleResult = await (result as any).handle() - - expect(handleResult).toBe(true) - expect(mockSignSapient).toHaveBeenCalledWith( - testRequest.envelope.wallet, - testRequest.envelope.chainId, - testRequest.envelope.payload, - testImageHash, - ) - expect(mockAddSignature).toHaveBeenCalledWith(testRequest.id, { - address: mockExtensions.passkeys, - imageHash: testImageHash, - signature: mockSignature, - }) - }) - - it.skip('Should handle signing errors gracefully', async () => { - mockSignSapient.mockRejectedValueOnce(new Error('User cancelled signing')) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - await expect((result as any).handle()).rejects.toThrow('User cancelled signing') - expect(mockAddSignature).not.toHaveBeenCalled() - }) - - it.skip('Should handle addSignature errors gracefully', async () => { - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - } - - mockSignSapient.mockResolvedValueOnce(mockSignature) - mockAddSignature.mockRejectedValueOnce(new Error('Database error')) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - await expect((result as any).handle()).rejects.toThrow('Database error') - }) - }) - }) - - // === ERROR HANDLING === - - describe('Error Handling', () => { - it('Should handle corrupted passkey data gracefully', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockLoadFromWitness.mockResolvedValueOnce(null) // Invalid passkey data - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - - consoleSpy.mockRestore() - }) - - it('Should handle state reader errors', async () => { - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - mockLoadFromWitness.mockRejectedValueOnce(new Error('State reader unavailable')) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - expect(consoleSpy).toHaveBeenCalledWith('Failed to load passkey:', expect.any(Error)) - - consoleSpy.mockRestore() - }) - - it('Should handle malformed extensions object', async () => { - const malformedExtensions = {} as Pick - const handlerWithBadExtensions = new PasskeysHandler(mockSignatures, malformedExtensions, mockStateReader) - - const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) - - const result = await handlerWithBadExtensions.status(mockExtensions.passkeys, testImageHash, testRequest) - - expect(result.status).toBe('unavailable') - expect((result as any).reason).toBe('unknown-error') - - consoleSpy.mockRestore() - }) - }) - - // === INTEGRATION TESTS === - - describe('Integration Tests', () => { - it.skip('Should handle complete passkey authentication flow', async () => { - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', - imageHash: testImageHash, - } - - mockLoadFromWitness.mockResolvedValueOnce(mockPasskey) - mockSignSapient.mockResolvedValueOnce(mockSignature) - - // Step 1: Check status - const statusResult = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - expect(statusResult.status).toBe('actionable') - expect((statusResult as any).message).toBe('request-interaction-with-passkey') - - // Step 2: Execute signing - const handleResult = await (statusResult as any).handle() - expect(handleResult).toBe(true) - - // Step 3: Verify all operations completed - expect(mockLoadFromWitness).toHaveBeenCalledOnce() - expect(mockSignSapient).toHaveBeenCalledOnce() - expect(mockAddSignature).toHaveBeenCalledOnce() - }) - - it.skip('Should handle multiple status checks efficiently', async () => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - // Multiple status checks - const results = await Promise.all([ - passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest), - passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest), - passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest), - ]) - - results.forEach((result) => { - expect(result.status).toBe('actionable') - expect((result as any).message).toBe('request-interaction-with-passkey') - }) - - expect(mockLoadFromWitness).toHaveBeenCalledTimes(3) - }) - - it.skip('Should handle different payloads correctly', async () => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - const transactionRequest = { - ...testRequest, - envelope: { - ...testRequest.envelope, - payload: Payload.fromCall(0n, 0n, [ - { - to: '0x1234567890123456789012345678901234567890' as Address.Address, - value: 0n, - data: '0x', - gasLimit: 21000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ]), - }, - } - - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - } - - mockSignSapient.mockResolvedValueOnce(mockSignature) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, transactionRequest) - await (result as any).handle() - - expect(mockSignSapient).toHaveBeenCalledWith( - transactionRequest.envelope.wallet, - transactionRequest.envelope.chainId, - transactionRequest.envelope.payload, - testImageHash, - ) - }) - - it.skip('Should handle different chain IDs correctly', async () => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - const polygonRequest = { - ...testRequest, - envelope: { - ...testRequest.envelope, - chainId: Network.ChainId.POLYGON, // Polygon - }, - } - - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - } - - mockSignSapient.mockResolvedValueOnce(mockSignature) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, polygonRequest) - await (result as any).handle() - - expect(mockSignSapient).toHaveBeenCalledWith( - polygonRequest.envelope.wallet, - Network.ChainId.POLYGON, - polygonRequest.envelope.payload, - testImageHash, - ) - }) - - it.skip('Should handle different image hashes correctly', async () => { - const alternativeImageHash = '0x2222222222222222222222222222222222222222222222222222222222222222' as Hex.Hex - - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - await passkeysHandler.status(mockExtensions.passkeys, alternativeImageHash, testRequest) - - expect(mockLoadFromWitness).toHaveBeenCalledWith( - mockStateReader, - mockExtensions, - testRequest.envelope.wallet, - alternativeImageHash, - ) - }) - }) - - // === EDGE CASES === - - describe('Edge Cases', () => { - it.skip('Should handle very long credential IDs', async () => { - const longCredentialId = 'a'.repeat(1000) - const passkeyWithLongId = { - ...mockPasskey, - credentialId: longCredentialId, - } - - mockLoadFromWitness.mockResolvedValueOnce(passkeyWithLongId) - mockSignSapient.mockResolvedValueOnce({ - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - }) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, testRequest) - await (result as any).handle() - - expect(mockSignSapient).toHaveBeenCalledOnce() - }) - - it.skip('Should handle zero-value chain IDs', async () => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - const zeroChainRequest = { - ...testRequest, - envelope: { - ...testRequest.envelope, - chainId: 0, - }, - } - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, zeroChainRequest) - expect(result.status).toBe('actionable') - }) - - it.skip('Should handle empty payload gracefully', async () => { - mockLoadFromWitness.mockResolvedValue(mockPasskey) - - const emptyPayloadRequest = { - ...testRequest, - envelope: { - ...testRequest.envelope, - payload: Payload.fromMessage('0x' as Hex.Hex), - }, - } - - const mockSignature = { - type: 'sapient-signer-leaf' as const, - signature: '0xabcdef1234567890', - imageHash: testImageHash, - } - - mockSignSapient.mockResolvedValueOnce(mockSignature) - - const result = await passkeysHandler.status(mockExtensions.passkeys, testImageHash, emptyPayloadRequest) - await (result as any).handle() - - expect(mockSignSapient).toHaveBeenCalledWith( - emptyPayloadRequest.envelope.wallet, - emptyPayloadRequest.envelope.chainId, - emptyPayloadRequest.envelope.payload, - testImageHash, - ) - }) - }) -}) diff --git a/packages/wallet/wdk/test/recovery.test.ts b/packages/wallet/wdk/test/recovery.test.ts deleted file mode 100644 index ebaab9d3a5..0000000000 --- a/packages/wallet/wdk/test/recovery.test.ts +++ /dev/null @@ -1,503 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { QueuedRecoveryPayload, SignerReady, TransactionDefined } from '../src/sequence/index.js' -import { Bytes, Hex, Mnemonic, Provider, RpcTransport } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { LOCAL_RPC_URL, newManager } from './constants.js' - -describe('Recovery', () => { - it('Should execute a recovery', async () => { - const manager = newManager({ - defaultRecoverySettings: { - requiredDeltaTime: 2n, // 2 seconds - minTimestamp: 0n, - }, - }) - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Add recovery mnemonic - const mnemonic2 = Mnemonic.random(Mnemonic.english) - const requestId1 = await manager.recovery.addMnemonic(wallet!, mnemonic2) - - expect(requestId1).toBeDefined() - - // Sign add recovery mnemonic - const request1 = await manager.signatures.get(requestId1) - expect(request1).toBeDefined() - - // Device must be the only ready signer now - const device = request1.signers.find((s) => s.status === 'ready') - expect(device).toBeDefined() - - const result1 = await device?.handle() - expect(result1).toBeDefined() - expect(result1).toBeTruthy() - - // Complete the add of the recovery mnemonic - await manager.recovery.completeUpdate(requestId1) - - // Get the recovery signers, there should be two one - // and one should not be the device address - const recoverySigners = await manager.recovery.getSigners(wallet!) - expect(recoverySigners).toBeDefined() - expect(recoverySigners!.length).toBe(2) - const nonDeviceSigner = recoverySigners!.find((s) => s.address !== device?.address) - expect(nonDeviceSigner).toBeDefined() - - // Transfer 1 wei to the wallet - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0x1'], - }) - - // Create a new recovery payload - const requestId2 = await manager.recovery.queuePayload(wallet!, Network.ChainId.ARBITRUM, { - type: 'call', - space: Bytes.toBigInt(Bytes.random(20)), - nonce: 0n, - calls: [ - { - to: Hex.from(Bytes.random(20)), - value: 1n, - data: '0x', - gasLimit: 1000000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - }) - - // Needs to be signed using the recovery mnemonic - // for this we need to define a handler for it - let handledMnemonic2 = 0 - const unregisterHandler = manager.registerMnemonicUI(async (respond) => { - handledMnemonic2++ - await respond(mnemonic2) - }) - - // Sign the queue recovery payload - const request2 = await manager.signatures.get(requestId2) - expect(request2).toBeDefined() - - // Complete the queue recovery payload - // the only signer available should be the device and the recovery mnemonic - // the both recovery deviecs that we have - expect(request2.signers.length).toBe(2) - expect(request2.signers.some((s) => s.handler?.kind === 'local-device')).toBeTruthy() - expect(request2.signers.some((s) => s.handler?.kind === 'login-mnemonic')).toBeTruthy() - - // Handle the login-mnemonic signer - const request2Signer = request2.signers.find((s) => s.handler?.kind === 'login-mnemonic') - expect(request2Signer).toBeDefined() - const result2 = await (request2Signer as SignerReady).handle() - expect(result2).toBeDefined() - expect(result2).toBeTruthy() - expect(handledMnemonic2).toBe(1) - unregisterHandler() - - // Complete the recovery payload - const { to, data } = await manager.recovery.completePayload(requestId2) - - // Send this transaction to anvil so we queue the payload - await provider.request({ - method: 'eth_sendTransaction', - params: [ - { - to, - data, - }, - ], - }) - - // Wait 3 seconds for the payload to become valid - await new Promise((resolve) => setTimeout(resolve, 3000)) - await manager.recovery.updateQueuedPayloads() - - // Get the recovery payloads - const recoveryPayloads = await new Promise((resolve) => { - const unsubscribe = manager.recovery.onQueuedPayloadsUpdate( - wallet!, - (payloads) => { - unsubscribe() - resolve(payloads) - }, - true, - ) - }) - - expect(recoveryPayloads).toBeDefined() - expect(recoveryPayloads.length).toBe(1) - const recoveryPayload = recoveryPayloads![0] - expect(recoveryPayload).toBeDefined() - expect(Payload.isCalls(recoveryPayload!.payload!)).toBeTruthy() - expect((recoveryPayload!.payload as Payload.Calls).calls.length).toBe(1) - - // Send this transaction as any other regular transaction - const requestId3 = await manager.transactions.request( - wallet!, - Network.ChainId.ARBITRUM, - (recoveryPayload!.payload as Payload.Calls).calls, - { - noConfigUpdate: true, - }, - ) - expect(requestId3).toBeDefined() - - // Define the same nonce and space for the recovery payload - await manager.transactions.define(requestId3, { - nonce: (recoveryPayload!.payload as Payload.Calls).nonce, - space: (recoveryPayload!.payload as Payload.Calls).space, - }) - - // Complete the transaction - const tx = await manager.transactions.get(requestId3) - expect(tx).toBeDefined() - expect(tx.status).toBe('defined') - expect((tx as TransactionDefined).relayerOptions.length).toBe(1) - - const localRelayer = (tx as TransactionDefined).relayerOptions[0]! - expect(localRelayer).toBeDefined() - expect(localRelayer.relayerId).toBe('local') - - // Define the relayer - const requestId4 = await manager.transactions.selectRelayer(requestId3, localRelayer.id) - expect(requestId4).toBeDefined() - - // Now we sign using the recovery module - const request4 = await manager.signatures.get(requestId4) - - // Find the signer that is the recovery module handler - const recoverySigner = request4.signers.find((s) => s.handler?.kind === 'recovery-extension') - expect(recoverySigner).toBeDefined() - expect(recoverySigner!.status).toBe('ready') - - // Handle the recovery signer - const result4 = await (recoverySigner as SignerReady).handle() - expect(result4).toBeDefined() - expect(result4).toBeTruthy() - - // Complete the transaction - await manager.transactions.relay(requestId4) - - // The balance of the wallet should be 0 wei - const balance = await provider.request({ - method: 'eth_getBalance', - params: [wallet!, 'latest'], - }) - expect(balance).toBeDefined() - expect(balance).toBe('0x0') - - // Refresh the queued recovery payloads, the executed one - // should be removed - await manager.recovery.updateQueuedPayloads() - const recoveryPayloads2 = await new Promise((resolve) => { - const unsubscribe = manager.recovery.onQueuedPayloadsUpdate( - wallet!, - (payloads) => { - unsubscribe() - resolve(payloads) - }, - true, - ) - }) - expect(recoveryPayloads2).toBeDefined() - expect(recoveryPayloads2.length).toBe(0) - }, 30000) - - it('Should fetch queued payloads for wallet with no recovery signers', async () => { - const manager = newManager() - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Wallet has no recovery signers, should return empty array - const payloads = await manager.recovery.fetchQueuedPayloads(wallet!) - expect(payloads).toBeDefined() - expect(Array.isArray(payloads)).toBeTruthy() - expect(payloads.length).toBe(0) - }) - - it('Should fetch queued payloads for wallet with recovery signers but no queued payloads', async () => { - const manager = newManager() - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Add recovery mnemonic - const mnemonic2 = Mnemonic.random(Mnemonic.english) - const requestId = await manager.recovery.addMnemonic(wallet!, mnemonic2) - - // Sign and complete the recovery signer addition - const request = await manager.signatures.get(requestId) - const device = request.signers.find((s) => s.status === 'ready') - expect(device).toBeDefined() - - await device?.handle() - await manager.recovery.completeUpdate(requestId) - - // Verify recovery signers exist - const recoverySigners = await manager.recovery.getSigners(wallet!) - expect(recoverySigners).toBeDefined() - expect(recoverySigners!.length).toBeGreaterThan(0) - - // Should return empty array since no payloads are queued - const payloads = await manager.recovery.fetchQueuedPayloads(wallet!) - expect(payloads).toBeDefined() - expect(Array.isArray(payloads)).toBeTruthy() - expect(payloads.length).toBe(0) - }) - - it('Should fetch queued payloads and match updateQueuedPayloads results', async () => { - const manager = newManager({ - defaultRecoverySettings: { - requiredDeltaTime: 2n, // 2 seconds - minTimestamp: 0n, - }, - }) - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Add recovery mnemonic - const mnemonic2 = Mnemonic.random(Mnemonic.english) - const requestId1 = await manager.recovery.addMnemonic(wallet!, mnemonic2) - - // Sign and complete the recovery signer addition - const request1 = await manager.signatures.get(requestId1) - const device = request1.signers.find((s) => s.status === 'ready') - expect(device).toBeDefined() - - await device?.handle() - await manager.recovery.completeUpdate(requestId1) - - // Transfer 1 wei to the wallet - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0x1'], - }) - - // Create and queue a recovery payload - const requestId2 = await manager.recovery.queuePayload(wallet!, Network.ChainId.ARBITRUM, { - type: 'call', - space: Bytes.toBigInt(Bytes.random(20)), - nonce: 0n, - calls: [ - { - to: Hex.from(Bytes.random(20)), - value: 1n, - data: '0x', - gasLimit: 1000000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - }) - - // Set up mnemonic handler and sign the payload - let _handledMnemonic2 = 0 - const unregisterHandler = manager.registerMnemonicUI(async (respond) => { - _handledMnemonic2++ - await respond(mnemonic2) - }) - - const request2 = await manager.signatures.get(requestId2) - const request2Signer = request2.signers.find((s) => s.handler?.kind === 'login-mnemonic') - expect(request2Signer).toBeDefined() - - await (request2Signer as SignerReady).handle() - unregisterHandler() - - // Complete the recovery payload and send to blockchain - const { to, data } = await manager.recovery.completePayload(requestId2) - await provider.request({ - method: 'eth_sendTransaction', - params: [{ to, data }], - }) - - // Wait for payload to become valid - await new Promise((resolve) => setTimeout(resolve, 3000)) - - // Test fetchQueuedPayloads directly - const fetchedPayloads = await manager.recovery.fetchQueuedPayloads(wallet!) - expect(fetchedPayloads).toBeDefined() - expect(Array.isArray(fetchedPayloads)).toBeTruthy() - expect(fetchedPayloads.length).toBe(1) - - const fetchedPayload = fetchedPayloads[0]! - expect(fetchedPayload).toBeDefined() - expect(fetchedPayload.wallet).toBe(wallet) - expect(fetchedPayload.chainId).toBe(Network.ChainId.ARBITRUM) - expect(fetchedPayload.index).toBe(0n) - expect(fetchedPayload.payload).toBeDefined() - expect(Payload.isCalls(fetchedPayload.payload!)).toBeTruthy() - expect((fetchedPayload.payload as Payload.Calls).calls.length).toBe(1) - - // Verify that fetchQueuedPayloads doesn't affect the database - // by checking current database state before and after - const payloadsBefore = await new Promise((resolve) => { - const unsubscribe = manager.recovery.onQueuedPayloadsUpdate( - wallet!, - (payloads) => { - unsubscribe() - resolve(payloads) - }, - true, - ) - }) - - // Call fetchQueuedPayloads again - const fetchedPayloads2 = await manager.recovery.fetchQueuedPayloads(wallet!) - - const payloadsAfter = await new Promise((resolve) => { - const unsubscribe = manager.recovery.onQueuedPayloadsUpdate( - wallet!, - (payloads) => { - unsubscribe() - resolve(payloads) - }, - true, - ) - }) - - // Database should be unchanged by fetchQueuedPayloads - expect(payloadsBefore.length).toBe(payloadsAfter.length) - - // Now update the database with updateQueuedPayloads - await manager.recovery.updateQueuedPayloads() - - const updatedPayloads = await new Promise((resolve) => { - const unsubscribe = manager.recovery.onQueuedPayloadsUpdate( - wallet!, - (payloads) => { - unsubscribe() - resolve(payloads) - }, - true, - ) - }) - - // Results should match between fetchQueuedPayloads and updateQueuedPayloads - expect(updatedPayloads.length).toBe(fetchedPayloads.length) - expect(updatedPayloads.length).toBe(fetchedPayloads2.length) - - if (updatedPayloads.length > 0 && fetchedPayloads.length > 0) { - const updated = updatedPayloads[0]! - const fetched = fetchedPayloads[0]! - - expect(updated.id).toBe(fetched.id) - expect(updated.wallet).toBe(fetched.wallet) - expect(updated.chainId).toBe(fetched.chainId) - expect(updated.index).toBe(fetched.index) - expect(updated.signer).toBe(fetched.signer) - expect(updated.payloadHash).toBe(fetched.payloadHash) - expect(updated.startTimestamp).toBe(fetched.startTimestamp) - expect(updated.endTimestamp).toBe(fetched.endTimestamp) - } - }, 30000) - - it('Should handle multiple queued payloads for the same wallet', async () => { - const manager = newManager({ - defaultRecoverySettings: { - requiredDeltaTime: 1n, // 1 second - minTimestamp: 0n, - }, - }) - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Add recovery mnemonic - const mnemonic2 = Mnemonic.random(Mnemonic.english) - const requestId1 = await manager.recovery.addMnemonic(wallet!, mnemonic2) - - // Sign and complete the recovery signer addition - const request1 = await manager.signatures.get(requestId1) - const device = request1.signers.find((s) => s.status === 'ready') - await device?.handle() - await manager.recovery.completeUpdate(requestId1) - - // Transfer some wei to the wallet - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0x10'], - }) - - // Set up mnemonic handler - const unregisterHandler = manager.registerMnemonicUI(async (respond) => { - await respond(mnemonic2) - }) - - // Create and queue multiple recovery payloads sequentially to avoid transaction conflicts - for (let i = 0; i < 3; i++) { - const requestId = await manager.recovery.queuePayload(wallet!, Network.ChainId.ARBITRUM, { - type: 'call', - space: Bytes.toBigInt(Bytes.random(20)), - nonce: BigInt(i), - calls: [ - { - to: Hex.from(Bytes.random(20)), - value: 1n, - data: '0x', - gasLimit: 1000000n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - }, - ], - }) - - const request = await manager.signatures.get(requestId) - const signer = request.signers.find((s) => s.handler?.kind === 'login-mnemonic') - await (signer as SignerReady).handle() - - const { to, data } = await manager.recovery.completePayload(requestId) - - // Send transactions sequentially to avoid nonce conflicts - await provider.request({ - method: 'eth_sendTransaction', - params: [{ to, data }], - }) - - // Small delay to ensure transaction ordering - await new Promise((resolve) => setTimeout(resolve, 100)) - } - unregisterHandler() - - // Wait for payloads to become valid - await new Promise((resolve) => setTimeout(resolve, 2000)) - - // Fetch all queued payloads - const fetchedPayloads = await manager.recovery.fetchQueuedPayloads(wallet!) - expect(fetchedPayloads).toBeDefined() - expect(Array.isArray(fetchedPayloads)).toBeTruthy() - expect(fetchedPayloads.length).toBe(3) - - // Verify each payload has unique properties - const indices = new Set(fetchedPayloads.map((p) => p.index.toString())) - const ids = new Set(fetchedPayloads.map((p) => p.id)) - const payloadHashes = new Set(fetchedPayloads.map((p) => p.payloadHash)) - - expect(indices.size).toBe(3) // All different indices - expect(ids.size).toBe(3) // All different IDs - expect(payloadHashes.size).toBe(3) // All different payload hashes - - // All should have the same wallet and chainId - fetchedPayloads.forEach((payload) => { - expect(payload.wallet).toBe(wallet) - expect(payload.chainId).toBe(Network.ChainId.ARBITRUM) - expect(payload.payload).toBeDefined() - expect(Payload.isCalls(payload.payload!)).toBeTruthy() - }) - }, 30000) -}) diff --git a/packages/wallet/wdk/test/sessions-idtoken.test.ts b/packages/wallet/wdk/test/sessions-idtoken.test.ts deleted file mode 100644 index 7c080af5c2..0000000000 --- a/packages/wallet/wdk/test/sessions-idtoken.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { Hash, Hex, Mnemonic, Secp256k1, Address as OxAddress } from 'ox' -import { Payload } from '@0xsequence/wallet-primitives' -import { newManager } from './constants.js' -import { Manager } from '../src/sequence/index.js' -import { Kinds } from '../src/sequence/types/signer.js' - -describe('Sessions ID token attestation', () => { - let manager: Manager | undefined - - afterEach(async () => { - await manager?.stop() - }) - - it('Should include issuer and audience hashes for google-id-token implicit session authorization', async () => { - manager = newManager({ - identity: { - google: { - enabled: true, - clientId: 'test-google-client-id', - authMethod: 'id-token', - }, - }, - }) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const signersModule = (manager as any).shared.modules.signers - vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginGoogle) - - const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) - const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { - target: 'https://example.com', - applicationData: '0x1234', - }) - - const request = await manager.signatures.get(requestId) - expect(request.action).toBe('session-implicit-authorize') - expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) - - if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { - throw new Error('Expected session implicit authorize payload') - } - - const attestation = request.envelope.payload.attestation - expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://accounts.google.com'))) - expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-google-client-id'))) - expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') - expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') - }) - - it('Should include issuer and audience hashes for apple implicit session authorization', async () => { - manager = newManager({ - identity: { - apple: { - enabled: true, - clientId: 'test-apple-client-id', - authMethod: 'id-token', - }, - }, - }) - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const signersModule = (manager as any).shared.modules.signers - vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginApple) - - const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) - const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { - target: 'https://example.com', - applicationData: '0x1234', - }) - - const request = await manager.signatures.get(requestId) - expect(request.action).toBe('session-implicit-authorize') - expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) - - if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { - throw new Error('Expected session implicit authorize payload') - } - - const attestation = request.envelope.payload.attestation - expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://appleid.apple.com'))) - expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-apple-client-id'))) - expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') - expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') - }) -}) diff --git a/packages/wallet/wdk/test/sessions.test.ts b/packages/wallet/wdk/test/sessions.test.ts deleted file mode 100644 index aa6ad05b8f..0000000000 --- a/packages/wallet/wdk/test/sessions.test.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { AbiFunction, Address, Bytes, Hex, Mnemonic, Provider, RpcTransport, Secp256k1 } from 'ox' -import { beforeEach, describe, expect, it } from 'vitest' -import { Signers as CoreSigners, Wallet as CoreWallet, Envelope, State } from '../../core/src/index.js' -import { ExplicitSession } from '../../core/src/utils/session/types.js' -import { Context, Extensions, Network, Payload, Permission } from '../../primitives/src/index.js' -import { Sequence } from '../src/index.js' -import { EMITTER_ABI, EMITTER_ADDRESS, LOCAL_RPC_URL } from './constants.js' - -const ALL_EXTENSIONS: { - name: string - extensions: Extensions.Extensions - context: Context.Context - context4337?: Context.Context -}[] = [ - { - name: 'Dev1', - extensions: Extensions.Dev1, - context: Context.Dev1, - }, - { - name: 'Dev2', - extensions: Extensions.Dev2, - context: Context.Dev2, - context4337: Context.Dev2_4337, - }, - { - name: 'Rc3', - extensions: Extensions.Rc3, - context: Context.Rc3, - context4337: Context.Rc3_4337, - }, - { - name: 'Rc4', - extensions: Extensions.Rc4, - context: Context.Rc4, - context4337: Context.Rc4_4337, - }, - { - name: 'Rc5', - extensions: Extensions.Rc5, - context: Context.Rc5, - context4337: Context.Rc5_4337, - }, -] - -for (const extension of ALL_EXTENSIONS) { - describe(`Sessions (via Manager ${extension.name})`, () => { - // Shared components - let provider: Provider.Provider - let chainId: number - let stateProvider: State.Provider - - // Wallet webapp components - let wdk: { - identitySignerAddress: Address.Address - manager: Sequence.Manager - } - - // Dapp components - let dapp: { - pkStore: CoreSigners.Pk.Encrypted.EncryptedPksDb - wallet: CoreWallet - sessionManager: CoreSigners.SessionManager - } - - const setupExplicitSession = async (explicitSession: ExplicitSession, isModify = false) => { - let requestId: string - if (isModify) { - requestId = await wdk.manager.sessions.modifyExplicitSession(dapp.wallet.address, explicitSession) - } else { - requestId = await wdk.manager.sessions.addExplicitSession(dapp.wallet.address, explicitSession) - } - - // Sign and complete the request - const sigRequest = await wdk.manager.signatures.get(requestId) - const identitySigner = sigRequest.signers.find((s) => Address.isEqual(s.address, wdk.identitySignerAddress)) - if (!identitySigner || (identitySigner.status !== 'actionable' && identitySigner.status !== 'ready')) { - throw new Error(`Identity signer not found or not ready/actionable: ${identitySigner?.status}`) - } - const handled = await identitySigner.handle() - if (!handled) { - throw new Error('Failed to handle identity signer') - } - await wdk.manager.sessions.complete(requestId) - } - - beforeEach(async () => { - // Create provider using LOCAL_RPC_URL - provider = Provider.from( - RpcTransport.fromHttp(LOCAL_RPC_URL, { - fetchOptions: { - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - }, - }), - ) - chainId = Number(await provider.request({ method: 'eth_chainId' })) - - // Create state provider - stateProvider = new State.Local.Provider() - - // Create manager - const opts = Sequence.applyManagerOptionsDefaults({ - stateProvider, - extensions: extension.extensions, - context: extension.context, - context4337: extension.context4337 ?? extension.context, - relayers: [], // No relayers needed for testing - networks: [ - { - chainId, - type: Network.NetworkType.MAINNET, - rpcUrl: LOCAL_RPC_URL, - name: 'XXX', - blockExplorer: { url: 'XXX' }, - nativeCurrency: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - }, - ], - }) - - // Create manager - const manager = new Sequence.Manager(opts) - - // Use a mnemonic to create the wallet - const identitySignerMnemonic = Mnemonic.random(Mnemonic.english) - const identitySignerPk = Mnemonic.toPrivateKey(identitySignerMnemonic, { as: 'Hex' }) - const identitySignerAddress = new CoreSigners.Pk.Pk(identitySignerPk).address - const walletAddress = await manager.wallets.signUp({ - kind: 'mnemonic', - mnemonic: identitySignerMnemonic, - noGuard: true, - noSessionManager: false, - }) - if (!walletAddress) { - throw new Error('Failed to create wallet') - } - - // Initialize the wdk components - wdk = { - identitySignerAddress, - manager, - } - manager.registerMnemonicUI(async (respond) => { - await respond(identitySignerMnemonic) - }) - - // Create the pk store and pk - const pkStore = new CoreSigners.Pk.Encrypted.EncryptedPksDb() - - // Create wallet in core - const coreWallet = new CoreWallet(walletAddress, { - guest: opts.guest, - // Share the state provider with wdk. In practice this will be the key machine. - stateProvider, - }) - - dapp = { - pkStore, - wallet: coreWallet, - sessionManager: new CoreSigners.SessionManager(coreWallet, { - provider, - sessionManagerAddress: extension.extensions.sessions, - }), - } - }) - - const signAndSend = async (call: Payload.Call) => { - const envelope = await dapp.wallet.prepareTransaction(provider, [call], { noConfigUpdate: true }) - const parentedEnvelope: Payload.Parented = { - ...envelope.payload, - parentWallets: [dapp.wallet.address], - } - - // Sign the envelope - const sessionImageHash = await dapp.sessionManager.imageHash - if (!sessionImageHash) { - throw new Error('Session image hash not found') - } - const signature = await dapp.sessionManager.signSapient( - dapp.wallet.address, - chainId ?? 1n, - parentedEnvelope, - sessionImageHash, - ) - const sapientSignature: Envelope.SapientSignature = { - imageHash: sessionImageHash, - signature, - } - const signedEnvelope = Envelope.toSigned(envelope, [sapientSignature]) - - // Build the transaction - const transaction = await dapp.wallet.buildTransaction(provider, signedEnvelope) - console.log('tx', transaction) - - // Generate and use a random sender address to prevent race conditions - const senderAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) - await provider.request({ - method: 'anvil_setBalance', - params: [senderAddress, Hex.fromNumber(1000000000000000000n)], - }) - await provider.request({ - method: 'anvil_impersonateAccount', - params: [senderAddress], - }) - - // Send the transaction - const txHash = await provider.request({ - method: 'eth_sendTransaction', - params: [ - { - ...transaction, - from: senderAddress, - }, - ], - }) - console.log('Transaction sent', txHash) - await new Promise((resolve) => setTimeout(resolve, 3000)) - const receipt = await provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) - console.log('Transaction receipt', receipt) - return txHash - } - - it('should add the session manager leaf when not present', { timeout: 60000 }, async () => { - // Recreate the wallet specifically for this test - const identitySignerMnemonic = Mnemonic.random(Mnemonic.english) - const identitySignerPk = Mnemonic.toPrivateKey(identitySignerMnemonic, { as: 'Hex' }) - const identitySignerAddress = new CoreSigners.Pk.Pk(identitySignerPk).address - const walletAddress = await wdk.manager.wallets.signUp({ - kind: 'mnemonic', - mnemonic: identitySignerMnemonic, - noGuard: true, - noSessionManager: true, - }) - if (!walletAddress) { - throw new Error('Failed to create wallet') - } - - // Initialize the wdk components - wdk.identitySignerAddress = identitySignerAddress - wdk.manager.registerMnemonicUI(async (respond) => { - await respond(identitySignerMnemonic) - }) - - // Create wallet in core - const coreWallet = new CoreWallet(walletAddress, { - stateProvider, - }) - - dapp.wallet = coreWallet - dapp.sessionManager = new CoreSigners.SessionManager(coreWallet, { - provider, - sessionManagerAddress: extension.extensions.sessions, - }) - - // At this point the wallet should NOT have a session topology - await expect(wdk.manager.sessions.getTopology(walletAddress)).rejects.toThrow('Session manager not found') - - // Create the explicit session signer - const e = await dapp.pkStore.generateAndStore() - const s = await dapp.pkStore.getEncryptedPkStore(e.address) - if (!s) { - throw new Error('Failed to create pk store') - } - const explicitSession: ExplicitSession = { - type: 'explicit', - sessionAddress: e.address, - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ - { - target: EMITTER_ADDRESS, - rules: [], - }, - ], - } - const explicitSigner = new CoreSigners.Session.Explicit(s, explicitSession) - // Add to manager - dapp.sessionManager = dapp.sessionManager.withExplicitSigner(explicitSigner) - - await setupExplicitSession(explicitSession) - - // Create a call payload - const call: Payload.Call = { - to: EMITTER_ADDRESS, - value: 0n, - data: AbiFunction.encodeData(EMITTER_ABI[0]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Sign and send the transaction - await signAndSend(call) - }) - - it('should create and sign with an explicit session', { timeout: 60000 }, async () => { - // Create the explicit session signer - const e = await dapp.pkStore.generateAndStore() - const s = await dapp.pkStore.getEncryptedPkStore(e.address) - if (!s) { - throw new Error('Failed to create pk store') - } - const explicitSession: ExplicitSession = { - type: 'explicit', - sessionAddress: e.address, - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ - { - target: EMITTER_ADDRESS, - rules: [ - { - // Require the explicitEmit selector - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromHex(AbiFunction.getSelector(EMITTER_ABI[0]), { size: 32 }), - offset: 0n, - mask: Bytes.fromHex('0xffffffff', { size: 32 }), - }, - ], - }, - ], - } - const explicitSigner = new CoreSigners.Session.Explicit(s, explicitSession) - // Add to manager - dapp.sessionManager = dapp.sessionManager.withExplicitSigner(explicitSigner) - - await setupExplicitSession(explicitSession) - - // Create a call payload - const call: Payload.Call = { - to: EMITTER_ADDRESS, - value: 0n, - data: AbiFunction.encodeData(EMITTER_ABI[0]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Sign and send the transaction - await signAndSend(call) - }) - - it('should modify an explicit session permission', { timeout: 60000 }, async () => { - // First we create the explicit sessions signer - const e = await dapp.pkStore.generateAndStore() - const s = await dapp.pkStore.getEncryptedPkStore(e.address) - if (!s) { - throw new Error('Failed to create pk store') - } - // Create the initial permissions - const explicitSession: ExplicitSession = { - type: 'explicit', - sessionAddress: e.address, - chainId, - valueLimit: 0n, - deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - permissions: [ - { - target: EMITTER_ADDRESS, - rules: [ - { - // Require the explicitEmit selector - cumulative: false, - operation: Permission.ParameterOperation.EQUAL, - value: Bytes.fromHex(AbiFunction.getSelector(EMITTER_ABI[0]), { size: 32 }), - offset: 0n, - mask: Bytes.fromHex('0xffffffff', { size: 32 }), - }, - ], - }, - ], - } - const explicitSigner = new CoreSigners.Session.Explicit(s, explicitSession) - // Add to manager - dapp.sessionManager = dapp.sessionManager.withExplicitSigner(explicitSigner) - - await setupExplicitSession(explicitSession) - - // Create a call payload - const call: Payload.Call = { - to: EMITTER_ADDRESS, - value: 0n, - data: AbiFunction.encodeData(EMITTER_ABI[0]), - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Sign and send the transaction - await signAndSend(call) - - // Now we modify the permissions target contract to zero address - // This should cause any session call to the EMITTER_ADDRESS contract to fail - explicitSession.permissions[0]!.target = '0x0000000000000000000000000000000000000000' - - await setupExplicitSession(explicitSession, true) - - // Sign and send the transaction - // Should fail with 'No signer supported for call' - await expect(signAndSend(call)).rejects.toThrow('No signer supported for call') - }) - - it('should create and sign with an implicit session', { timeout: 60000 }, async () => { - // Create the implicit session signer - const e = await dapp.pkStore.generateAndStore() - const s = await dapp.pkStore.getEncryptedPkStore(e.address) - if (!s) { - throw new Error('Failed to create pk store') - } - - // Request the session authorization from the WDK - const requestId = await wdk.manager.sessions.prepareAuthorizeImplicitSession(dapp.wallet.address, e.address, { - target: 'https://example.com', - }) - - // Sign the request (Wallet UI action) - const sigRequest = await wdk.manager.signatures.get(requestId) - const identitySigner = sigRequest.signers[0] - if (!identitySigner || (identitySigner.status !== 'actionable' && identitySigner.status !== 'ready')) { - throw new Error(`Identity signer not found or not ready/actionable: ${identitySigner?.status}`) - } - const handled = await identitySigner.handle() - if (!handled) { - throw new Error('Failed to handle identity signer') - } - - // Complete the request - const { attestation, signature: identitySignature } = - await wdk.manager.sessions.completeAuthorizeImplicitSession(requestId) - - // Load the implicit signer - const implicitSigner = new CoreSigners.Session.Implicit( - s, - attestation, - identitySignature, - dapp.sessionManager.address, - ) - dapp.sessionManager = dapp.sessionManager.withImplicitSigner(implicitSigner) - - // Create a call payload - const call: Payload.Call = { - to: EMITTER_ADDRESS, - value: 0n, - data: AbiFunction.encodeData(EMITTER_ABI[1]), // implicitEmit - gasLimit: 0n, - delegateCall: false, - onlyFallback: false, - behaviorOnError: 'revert', - } - - // Sign and send the transaction - await signAndSend(call) - }) - }) -} diff --git a/packages/wallet/wdk/test/setup.ts b/packages/wallet/wdk/test/setup.ts deleted file mode 100644 index 4aa336a55a..0000000000 --- a/packages/wallet/wdk/test/setup.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - indexedDB, - IDBFactory, - IDBKeyRange, - IDBDatabase, - IDBObjectStore, - IDBIndex, - IDBCursor, - IDBCursorWithValue, - IDBTransaction, - IDBRequest, - IDBOpenDBRequest, - IDBVersionChangeEvent, -} from 'fake-indexeddb' -import { Provider, RpcTransport } from 'ox' -import { vi } from 'vitest' -import { LOCAL_RPC_URL } from './constants.js' - -// Add IndexedDB support to the test environment using fake-indexeddb -global.indexedDB = indexedDB -global.IDBFactory = IDBFactory as unknown as typeof global.IDBFactory -global.IDBKeyRange = IDBKeyRange as unknown as typeof global.IDBKeyRange -global.IDBDatabase = IDBDatabase as unknown as typeof global.IDBDatabase -global.IDBObjectStore = IDBObjectStore as unknown as typeof global.IDBObjectStore -global.IDBIndex = IDBIndex as unknown as typeof global.IDBIndex -global.IDBCursor = IDBCursor as unknown as typeof global.IDBCursor -global.IDBCursorWithValue = IDBCursorWithValue as unknown as typeof global.IDBCursorWithValue -global.IDBTransaction = IDBTransaction as unknown as typeof global.IDBTransaction -global.IDBRequest = IDBRequest as unknown as typeof global.IDBRequest -global.IDBOpenDBRequest = IDBOpenDBRequest as unknown as typeof global.IDBOpenDBRequest -global.IDBVersionChangeEvent = IDBVersionChangeEvent as unknown as typeof global.IDBVersionChangeEvent - -// Mock navigator.locks API for Node.js environment --- - -// 1. Ensure the global navigator object exists -if (typeof global.navigator === 'undefined') { - console.log('mocking navigator') - global.navigator = {} as Navigator -} - -// 2. Define or redefine the 'locks' property on navigator -// Check if 'locks' is falsy (null or undefined), OR if it's an object -// that doesn't have the 'request' property we expect in our mock. -if (!global.navigator.locks || !('request' in global.navigator.locks)) { - Object.defineProperty(global.navigator, 'locks', { - // The value of the 'locks' property will be our mock object - value: { - // Mock the 'request' method - request: vi - .fn() - .mockImplementation(async (name: string, callback: (lock: { name: string } | null) => Promise) => { - // Simulate acquiring the lock immediately in the test environment. - const mockLock = { name } // A minimal mock lock object - try { - // Execute the callback provided to navigator.locks.request - const result = await callback(mockLock) - return result // Return the result of the callback - } catch (e) { - // Log errors from the callback for better debugging in tests - console.error(`Error occurred within mocked lock callback for lock "${name}":`, e) - throw e // Re-throw the error so the test potentially fails - } - }), - // Mock the 'query' method - query: vi.fn().mockResolvedValue({ held: [], pending: [] }), - }, - writable: true, - configurable: true, - enumerable: true, - }) -} else { - console.log('navigator.locks already exists and appears to have a "request" property.') -} - -export function mockEthereum() { - // Add window.ethereum support, pointing to the the Anvil local RPC - if (typeof (window as any).ethereum === 'undefined') { - ;(window as any).ethereum = { - request: vi.fn().mockImplementation(async (args: any) => { - // Pipe the request to the Anvil local RPC - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - return provider.request(args) - }), - } - } -} diff --git a/packages/wallet/wdk/test/signers-kindof.test.ts b/packages/wallet/wdk/test/signers-kindof.test.ts deleted file mode 100644 index 06b01cb780..0000000000 --- a/packages/wallet/wdk/test/signers-kindof.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -import { Kinds } from '../src/sequence/index.js' -import { newManager } from './constants.js' - -describe('Signers.kindOf', () => { - it('does not probe Sessions/Witness for non-witnessable signers', async () => { - const getWitnessFor = vi.fn().mockResolvedValue(undefined) - const getWitnessForSapient = vi.fn().mockResolvedValue(undefined) - - const manager = newManager({ - stateProvider: { - getWitnessFor, - getWitnessForSapient, - } as any, - }) - - const signers = (manager as any).shared.modules.signers - const extensions = (manager as any).shared.sequence.extensions - - const wallet = '0x1111111111111111111111111111111111111111' - const imageHash = ('0x' + '00'.repeat(32)) as `0x${string}` - - // Sessions extension signer (sapient leaf) never publishes a witness. - await signers.kindOf(wallet, extensions.sessions, imageHash) - - // Passkeys module is a known sapient signer kind. - expect(await signers.kindOf(wallet, extensions.passkeys, imageHash)).toBe(Kinds.LoginPasskey) - - // Sequence dev multisig (default guard topology leaf) never publishes a witness. - await signers.kindOf(wallet, '0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4') - - expect(getWitnessFor).not.toHaveBeenCalled() - expect(getWitnessForSapient).not.toHaveBeenCalled() - - // Unknown signers still rely on a witness probe. - await signers.kindOf(wallet, '0x2222222222222222222222222222222222222222') - expect(getWitnessFor).toHaveBeenCalledTimes(1) - }) - - it('normalizes legacy Google PKCE signer kind to the canonical Google signer kind', async () => { - const getWitnessFor = vi.fn().mockResolvedValue({ - payload: { - type: 'message', - message: '0x' + Buffer.from(JSON.stringify({ signerKind: 'login-google-pkce' }), 'utf8').toString('hex'), - }, - }) - - const manager = newManager({ - stateProvider: { - getWitnessFor, - getWitnessForSapient: vi.fn(), - } as any, - }) - - const signers = (manager as any).shared.modules.signers - const wallet = '0x1111111111111111111111111111111111111111' - const signer = '0x2222222222222222222222222222222222222222' - - await expect(signers.kindOf(wallet, signer)).resolves.toBe(Kinds.LoginGoogle) - }) -}) diff --git a/packages/wallet/wdk/test/test-ssr-safety.js b/packages/wallet/wdk/test/test-ssr-safety.js deleted file mode 100644 index 71eb361702..0000000000 --- a/packages/wallet/wdk/test/test-ssr-safety.js +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env node -/* global console, process */ -/** - * Comprehensive SSR Safety Test (Runtime Execution) - * - * This script tests that the entire wdk package can be imported and used in a Node.js - * environment (SSR context) without throwing errors about missing window. - * - * It executes the code at runtime to catch any SSR issues. - * - * Run with: node test-ssr-comprehensive.mjs - */ - -import { readFile } from 'fs/promises' -import { fileURLToPath } from 'url' -import { dirname, join } from 'path' -import { createRequire } from 'module' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const require = createRequire(import.meta.url) - -console.log('Testing SSR safety with runtime execution...\n') - -// Ensure we're in a Node.js environment (no window) -if (typeof window !== 'undefined') { - console.error('ERROR: window is defined! This should not happen in Node.js.') - process.exit(1) -} - -console.log('✓ window is undefined (as expected in Node.js)\n') - -const errors = [] -const warnings = [] - -// Read package.json to get package name and exports -let packageJson -try { - const packageJsonPath = join(__dirname, '..', 'package.json') - packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8')) -} catch (err) { - console.error('Failed to read package.json:', err.message) - process.exit(1) -} - -// Test 1: Import main module via package name -console.log('='.repeat(60)) -console.log('Test 1: Importing package via package name') -console.log('='.repeat(60)) - -let wdk -try { - // Use the package name from package.json - const packageName = packageJson.name - console.log(`Importing ${packageName}...`) - - // Try to resolve the package - const packagePath = require.resolve(packageName) - console.log(` Package resolved to: ${packagePath}`) - - // Import the package - wdk = await import(packageName) - console.log('✓ Successfully imported package') - console.log(' Top-level exports:', Object.keys(wdk)) -} catch (error) { - // Check if it's an SSR-related error - if ( - error.message.includes('window is not defined') || - error.message.includes('window') || - error.message.includes('document is not defined') || - error.message.includes('document') || - error.message.includes('localStorage') || - error.message.includes('sessionStorage') - ) { - errors.push(`SSR ERROR: Package accesses browser globals at module load time: ${error.message}`) - if (error.stack) { - console.error('\nError stack:') - console.error(error.stack) - } - } else { - errors.push(`Failed to import package: ${error.message}`) - if (error.stack) { - console.error('Stack:', error.stack) - } - } - - // Don't exit immediately - let the summary show the error - if (errors.length > 0) { - // Skip remaining tests if import failed - wdk = null - } -} - -// Test 2: Recursively access and test all exports -console.log('\n' + '='.repeat(60)) -console.log('Test 2: Accessing and testing all exports') -console.log('='.repeat(60)) - -if (!wdk) { - console.log('Skipping - package import failed') -} else { - async function testExports(obj, path = '', depth = 0) { - if (depth > 5) return // Prevent infinite recursion - - for (const [key, value] of Object.entries(obj)) { - const currentPath = path ? `${path}.${key}` : key - - try { - // Skip if it's a circular reference or already tested - if (value === null || value === undefined) { - continue - } - - // Test accessing the value (this executes any getters) - const accessed = value - - // Test different types - if (typeof accessed === 'function') { - // Try to get function properties - try { - const props = Object.getOwnPropertyNames(accessed) - if (props.length > 0 && depth < 3) { - // Test static properties on functions - for (const prop of props.slice(0, 3)) { - try { - const propValue = accessed[prop] - if (typeof propValue === 'object' && propValue !== null && depth < 2) { - await testExports(propValue, `${currentPath}.${prop}`, depth + 1) - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`${currentPath}.${prop}: ${err.message}`) - } - } - } - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`${currentPath}: ${err.message}`) - } - } - } else if (typeof accessed === 'object' && accessed !== null) { - // Test object properties - if (Array.isArray(accessed)) { - // Test array elements - for (let i = 0; i < Math.min(accessed.length, 3); i++) { - try { - const item = accessed[i] - if (typeof item === 'object' && item !== null && depth < 3) { - await testExports(item, `${currentPath}[${i}]`, depth + 1) - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`${currentPath}[${i}]: ${err.message}`) - } - } - } - } else { - // Test object properties recursively - await testExports(accessed, currentPath, depth + 1) - } - } - } catch (error) { - // Check if it's an SSR-related error - if ( - error.message.includes('window is not defined') || - error.message.includes('window') || - error.message.includes('document is not defined') || - error.message.includes('document') || - error.message.includes('localStorage') || - error.message.includes('sessionStorage') - ) { - errors.push(`${currentPath}: ${error.message}`) - } else { - // Other errors are warnings (might be expected, like missing dependencies) - warnings.push(`${currentPath}: ${error.message}`) - } - } - } - } - - // Test all top-level exports - console.log('Testing all exports recursively...') - await testExports(wdk) -} - -// Test 3: Try to access specific critical exports and use them -console.log('\n' + '='.repeat(60)) -console.log('Test 3: Testing critical exports with actual usage') -console.log('='.repeat(60)) - -if (!wdk) { - console.log('Skipping - package import failed') -} else { - // Test ManagerOptionsDefaults - try { - if (wdk.Sequence?.ManagerOptionsDefaults) { - console.log('Testing ManagerOptionsDefaults...') - const defaults = wdk.Sequence.ManagerOptionsDefaults - - // Access all properties - Object.keys(defaults).forEach((key) => { - try { - const value = defaults[key] - console.log(` ✓ ${key}: ${typeof value}`) - - // If it's a function, try calling it - if (typeof value === 'function' && key === 'relayers') { - const result = value() - console.log( - ` Called ${key}(), returned:`, - Array.isArray(result) ? `${result.length} items` : typeof result, - ) - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`ManagerOptionsDefaults.${key}: ${err.message}`) - } - } - }) - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`ManagerOptionsDefaults: ${err.message}`) - } - } - - // Test applyManagerOptionsDefaults function - try { - if (wdk.Sequence?.applyManagerOptionsDefaults) { - console.log('Testing applyManagerOptionsDefaults...') - const result = wdk.Sequence.applyManagerOptionsDefaults() - console.log(' ✓ Function executed successfully') - console.log(' Result keys:', Object.keys(result).slice(0, 5).join(', '), '...') - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`applyManagerOptionsDefaults: ${err.message}`) - } - } -} - -// Test 4: Try importing sub-modules that might be imported separately -console.log('\n' + '='.repeat(60)) -console.log('Test 4: Testing sub-module imports') -console.log('='.repeat(60)) - -if (!wdk) { - console.log('Skipping - package import failed') -} else { - // Get the package path and try importing from dist - try { - const packagePath = require.resolve(packageJson.name) - const packageDir = dirname(packagePath) - - // Try to import from the exports field if available - if (packageJson.exports) { - for (const [exportPath, exportConfig] of Object.entries(packageJson.exports)) { - if (exportPath === '.') { - const modulePath = exportConfig.default || exportConfig.types - if (modulePath) { - try { - const fullPath = join(packageDir, '..', modulePath) - console.log(`Testing import from ${exportPath}...`) - const subModule = await import(fullPath) - console.log(` ✓ Imported successfully`) - - // Test accessing exports - const subExports = Object.keys(subModule) - if (subExports.length > 0) { - console.log(` Exports: ${subExports.slice(0, 5).join(', ')}${subExports.length > 5 ? '...' : ''}`) - } - } catch (err) { - if (err.message.includes('window') || err.message.includes('document')) { - errors.push(`Import ${exportPath}: ${err.message}`) - } else if (!err.message.includes('Cannot find module')) { - warnings.push(`Import ${exportPath}: ${err.message}`) - } - } - } - } - } - } - } catch (err) { - warnings.push(`Could not test sub-modules: ${err.message}`) - } -} - -// Summary -console.log('\n' + '='.repeat(60)) -console.log('Test Summary') -console.log('='.repeat(60)) - -if (errors.length === 0) { - console.log('\n✅ All SSR Safety Tests PASSED!') - console.log('The package can be safely imported and used in a Node.js/SSR environment.') - if (warnings.length > 0) { - console.log(`\n⚠️ ${warnings.length} warning(s) (non-SSR related):`) - warnings.slice(0, 5).forEach((warn) => console.log(` - ${warn}`)) - if (warnings.length > 5) { - console.log(` ... and ${warnings.length - 5} more`) - } - } - process.exit(0) -} else { - console.log('\n❌ ERRORS FOUND:') - errors.forEach((err) => console.log(` - ${err}`)) - console.log('\n❌ SSR Safety Test FAILED!') - if (warnings.length > 0) { - console.log(`\n⚠️ ${warnings.length} warning(s):`) - warnings.slice(0, 5).forEach((warn) => console.log(` - ${warn}`)) - } - process.exit(1) -} diff --git a/packages/wallet/wdk/test/transactions.test.ts b/packages/wallet/wdk/test/transactions.test.ts deleted file mode 100644 index 295d00d014..0000000000 --- a/packages/wallet/wdk/test/transactions.test.ts +++ /dev/null @@ -1,992 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { - Manager, - SignerActionable, - Transaction, - TransactionDefined, - TransactionRelayed, -} from '../src/sequence/index.js' -import { Address, Hex, Mnemonic, Provider, RpcTransport } from 'ox' -import { LOCAL_RPC_URL, newManager } from './constants.js' -import { Payload, Network } from '@0xsequence/wallet-primitives' - -describe('Transactions', () => { - let manager: Manager | undefined - - afterEach(async () => { - await manager?.stop() - }) - - it('Should send a transaction from a new wallet', async () => { - manager = newManager() - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0xa'], - }) - - const recipient = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: recipient, - value: 9n, - }, - ]) - - expect(txId).toBeDefined() - await manager.transactions.define(txId!) - - let tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx.status).toBe('defined') - - if (tx.status !== 'defined') { - throw new Error('Transaction status is not defined') - } - - expect(tx.relayerOptions.length).toBe(1) - expect(tx.relayerOptions[0]!.id).toBeDefined() - - const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0]!.id) - expect(sigId).toBeDefined() - - tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx.status).toBe('formed') - - // Sign using the device signer - const sigRequest = await manager.signatures.get(sigId!) - expect(sigRequest).toBeDefined() - expect(sigRequest.status).toBe('pending') - expect(sigRequest.signers.filter((s) => s.status === 'ready').length).toBe(1) - - const deviceSigner = sigRequest.signers.find((s) => s.status === 'ready')! - expect(deviceSigner).toBeDefined() - - await deviceSigner.handle() - - await manager.transactions.relay(txId) - - // Check the balance of the wallet - const balance = await provider.request({ - method: 'eth_getBalance', - params: [wallet!, 'latest'], - }) - expect(balance).toBeDefined() - expect(balance).toBe('0x1') - - // Check the balance of the recipient - const recipientBalance = await provider.request({ - method: 'eth_getBalance', - params: [recipient, 'latest'], - }) - expect(recipientBalance).toBeDefined() - expect(recipientBalance).toBe('0x9') - }) - - it('Should send a transaction after logging in to a wallet', async () => { - manager = newManager() - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - // Logout without removing the device - await manager.wallets.logout(wallet!, { skipRemoveDevice: true }) - - // Login to the same wallet - const loginId = await manager.wallets.login({ wallet: wallet! }) - expect(loginId).toBeDefined() - - // Register the UI for the mnemonic signer - let signRequests = 0 - let unregisteredUI = manager.registerMnemonicUI(async (respond) => { - signRequests++ - await respond(mnemonic) - }) - - const loginRequest = await manager.signatures.get(loginId!) - expect(loginRequest).toBeDefined() - expect(loginRequest.action).toBe('login') - - const mnemonicSigner = loginRequest.signers.find((signer) => signer.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner).toBeDefined() - expect(mnemonicSigner?.status).toBe('actionable') - - signRequests = 0 - unregisteredUI = manager.registerMnemonicUI(async (respond) => { - signRequests++ - await respond(mnemonic) - }) - - await (mnemonicSigner as SignerActionable).handle() - expect(signRequests).toBe(1) - unregisteredUI() - - await manager.wallets.completeLogin(loginId!) - expect((await manager.signatures.get(loginId!))?.status).toBe('completed') - - // Set balance for the wallet - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0xa'], - }) - - // Send a transaction - const recipient = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: recipient, - value: 9n, - }, - ]) - - expect(txId).toBeDefined() - await manager.transactions.define(txId!) - - let tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx.status).toBe('defined') - - if (tx.status !== 'defined') { - throw new Error('Transaction status is not defined') - } - - expect(tx.relayerOptions.length).toBe(1) - expect(tx.relayerOptions[0]!.id).toBeDefined() - - const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0]!.id) - expect(sigId).toBeDefined() - - tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx.status).toBe('formed') - - // Sign using the device signer - const sigRequest = await manager.signatures.get(sigId!) - expect(sigRequest).toBeDefined() - expect(sigRequest.status).toBe('pending') - expect(sigRequest.signers.filter((s) => s.status === 'ready').length).toBe(1) - - const deviceSigner = sigRequest.signers.find((s) => s.status === 'ready')! - expect(deviceSigner).toBeDefined() - - await deviceSigner.handle() - - await manager.transactions.relay(txId) - - // Check the balance of the wallet - const balance = await provider.request({ - method: 'eth_getBalance', - params: [wallet!, 'latest'], - }) - expect(balance).toBeDefined() - expect(balance).toBe('0x1') - - // Check the balance of the recipient - const recipientBalance = await provider.request({ - method: 'eth_getBalance', - params: [recipient, 'latest'], - }) - expect(recipientBalance).toBeDefined() - expect(recipientBalance).toBe('0x9') - }) - - it('Should call onTransactionsUpdate when a new transaction is requested', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - let transactions: Transaction[] = [] - let calledTimes = 0 - manager.transactions.onTransactionsUpdate((txs) => { - transactions = txs - calledTimes++ - }) - - const to = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to, - value: 9n, - }, - ]) - - expect(txId).toBeDefined() - await manager.transactions.define(txId!) - - expect(calledTimes).toBe(1) - expect(transactions.length).toBe(1) - const tx = transactions[0]! - expect(tx.status).toBe('requested') - expect(tx.wallet).toBe(wallet!) - expect(tx.requests.length).toBe(1) - expect(tx.requests[0]!.to).toEqual(to) - expect(tx.requests[0]!.value).toEqual(9n) - }) - - it('Should call onTransactionUpdate when a transaction is defined, relayer selected and relayed', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - const to = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to, - }, - ]) - - let tx: Transaction | undefined - let calledTimes = 0 - manager.transactions.onTransactionUpdate(txId!, (t) => { - tx = t - calledTimes++ - }) - - expect(txId).toBeDefined() - await manager.transactions.define(txId!) - - while (calledTimes < 1) { - await new Promise((resolve) => setTimeout(resolve, 1)) - } - - expect(calledTimes).toBe(1) - expect(tx).toBeDefined() - expect(tx!.status).toBe('defined') - expect(tx!.wallet).toBe(wallet!) - expect(tx!.requests.length).toBe(1) - expect(tx!.requests[0]!.to).toEqual(to) - expect(tx!.requests[0]!.value).toBeUndefined() - expect(tx!.requests[0]!.gasLimit).toBeUndefined() - expect(tx!.requests[0]!.data).toBeUndefined() - - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) - expect(sigId).toBeDefined() - - while (calledTimes < 2) { - await new Promise((resolve) => setTimeout(resolve, 1)) - } - - expect(calledTimes).toBe(2) - expect(tx!.status).toBe('formed') - - // Sign the transaction - const sigRequest = await manager.signatures.get(sigId!) - expect(sigRequest).toBeDefined() - expect(sigRequest.status).toBe('pending') - expect(sigRequest.signers.filter((s) => s.status === 'ready').length).toBe(1) - - const deviceSigner = sigRequest.signers.find((s) => s.status === 'ready')! - await deviceSigner.handle() - - await manager.transactions.relay(txId!) - while (calledTimes < 3) { - await new Promise((resolve) => setTimeout(resolve, 1)) - } - - expect(calledTimes).toBe(3) - expect(tx!.status).toBe('relayed') - expect((tx! as TransactionRelayed).opHash).toBeDefined() - }) - - it('Should delete an existing transaction before it is defined', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const to = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to, - }, - ]) - - expect(txId).toBeDefined() - - await manager.transactions.delete(txId!) - await expect(manager.transactions.get(txId!)).rejects.toThrow() - }) - - it('Should delete an existing transaction before the relayer is selected', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const to = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to, - }, - ]) - - expect(txId).toBeDefined() - - await manager.transactions.define(txId!) - - await manager.transactions.delete(txId!) - await expect(manager.transactions.get(txId!)).rejects.toThrow() - }) - - it('Should delete an existing transaction before it is relayed', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const to = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to, - }, - ]) - - expect(txId).toBeDefined() - - await manager.transactions.define(txId!) - - const tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx!.status).toBe('defined') - - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) - expect(sigId).toBeDefined() - - await manager.transactions.delete(txId!) - await expect(manager.transactions.get(txId!)).rejects.toThrow() - - // Signature request should be canceled - const sigRequest = await manager.signatures.get(sigId!) - expect(sigRequest).toBeDefined() - expect(sigRequest.status).toBe('cancelled') - }) - - it('Should update the onchain configuration when a transaction is sent', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Add a recovery signer, just to change the configuration - const rSigId = await manager.recovery.addSigner(wallet!, Address.from(Hex.random(20))) - expect(rSigId).toBeDefined() - - // Sign using the device signer - const rSigRequest = await manager.signatures.get(rSigId!) - expect(rSigRequest).toBeDefined() - expect(rSigRequest.status).toBe('pending') - expect(rSigRequest.signers.filter((s) => s.status === 'ready').length).toBe(1) - - const rDeviceSigner = rSigRequest.signers.find((s) => s.status === 'ready')! - await rDeviceSigner.handle() - - await expect(manager.wallets.isUpdatedOnchain(wallet!, Network.ChainId.ARBITRUM)).resolves.toBeTruthy() - - await manager.recovery.completeUpdate(rSigId!) - - // It should no longer be updated onchain - await expect(manager.wallets.isUpdatedOnchain(wallet!, Network.ChainId.ARBITRUM)).resolves.toBeFalsy() - - const randomAddress = Address.from(Hex.random(20)) - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: randomAddress, - }, - ]) - - await manager.transactions.define(txId!) - - let tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx!.status).toBe('defined') - - // The transaction should contain the one that we want to perform - // and a configuration update - expect((tx.envelope.payload as Payload.Calls).calls.length).toBe(2) - - // The first call should be to the random address - // and the second one should be a call to self - const call1 = (tx.envelope.payload as Payload.Calls).calls[0]! - const call2 = (tx.envelope.payload as Payload.Calls).calls[1]! - expect(call1.to).toEqual(randomAddress) - expect(call2.to).toEqual(wallet) - - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) - expect(sigId).toBeDefined() - - tx = await manager.transactions.get(txId!) - expect(tx).toBeDefined() - expect(tx!.status).toBe('formed') - - // Sign using the device signer - const sigRequest = await manager.signatures.get(sigId!) - expect(sigRequest).toBeDefined() - expect(sigRequest.status).toBe('pending') - expect(sigRequest.signers.filter((s) => s.status === 'ready').length).toBe(1) - - const deviceSigner = sigRequest.signers.find((s) => s.status === 'ready')! - await deviceSigner.handle() - - await manager.transactions.relay(txId!) - - // wait 1 second - await new Promise((resolve) => setTimeout(resolve, 1000)) - - // The onchain configuration should be updated - await expect(manager.wallets.isUpdatedOnchain(wallet!, Network.ChainId.ARBITRUM)).resolves.toBeTruthy() - }) - - it('Should reject unsafe transactions in safe mode (call to self)', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId1 = manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: wallet!, - data: '0x1234', - }, - ]) - - await expect(txId1).rejects.toThrow() - }) - - it('Should allow native token transfer to self in safe mode', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId1 = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: wallet!, - value: 1n, - }, - ]) - - expect(txId1).toBeDefined() - }) - - it('Should allow transactions to self in unsafe mode', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId1 = await manager.transactions.request( - wallet!, - Network.ChainId.ARBITRUM, - [ - { - to: wallet!, - }, - ], - { - unsafe: true, - }, - ) - - expect(txId1).toBeDefined() - }) - - // === NEW TESTS FOR IMPROVED COVERAGE === - - it('Should verify transactions list functionality through callbacks', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - let transactionsList: Transaction[] = [] - let _updateCount = 0 - - // Use onTransactionsUpdate to verify list functionality - const unsubscribe = manager.transactions.onTransactionsUpdate((txs) => { - transactionsList = txs - _updateCount++ - }) - - // Initially should be empty - await new Promise((resolve) => setTimeout(resolve, 10)) - expect(transactionsList).toEqual([]) - - // Create a transaction - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Wait for callback - await new Promise((resolve) => setTimeout(resolve, 10)) - - // Should now have one transaction - expect(transactionsList.length).toBe(1) - const tx = transactionsList[0]! - expect(tx.id).toBe(txId) - expect(tx.status).toBe('requested') - expect(tx.wallet).toBe(wallet) - - unsubscribe() - }) - - it('Should trigger onTransactionsUpdate callback immediately when trigger=true', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - let callCount = 0 - let receivedTransactions: Transaction[] = [] - - // Create a transaction first - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Subscribe with trigger=true should call immediately - const unsubscribe = manager.transactions.onTransactionsUpdate((txs) => { - callCount++ - receivedTransactions = txs - }, true) - - // Give time for async callback - await new Promise((resolve) => setTimeout(resolve, 10)) - - expect(callCount).toBe(1) - expect(receivedTransactions.length).toBe(1) - expect(receivedTransactions[0]!.id).toBe(txId) - - unsubscribe() - }) - - it('Should trigger onTransactionUpdate callback immediately when trigger=true', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - let callCount = 0 - let receivedTransaction: Transaction | undefined - - // Subscribe with trigger=true should call immediately - const unsubscribe = manager.transactions.onTransactionUpdate( - txId, - (tx) => { - callCount++ - receivedTransaction = tx - }, - true, - ) - - // Give time for async callback - await new Promise((resolve) => setTimeout(resolve, 10)) - - expect(callCount).toBe(1) - expect(receivedTransaction).toBeDefined() - expect(receivedTransaction!.id).toBe(txId) - expect(receivedTransaction!.status).toBe('requested') - - unsubscribe() - }) - - it('Should handle define with nonce changes', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Define with custom nonce - await manager.transactions.define(txId, { - nonce: 999n, - }) - - const tx = await manager.transactions.get(txId) - expect(tx.status).toBe('defined') - expect(tx.envelope.payload.nonce).toBe(999n) - }) - - it('Should handle define with space changes', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Define with custom space - await manager.transactions.define(txId, { - space: 555n, - }) - - const tx = await manager.transactions.get(txId) - expect(tx.status).toBe('defined') - expect(tx.envelope.payload.space).toBe(555n) - }) - - it('Should handle define with gas limit changes', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - { - to: Address.from(Hex.random(20)), - value: 200n, - }, - ]) - - // Define with custom gas limits - await manager.transactions.define(txId, { - calls: [{ gasLimit: 50000n }, { gasLimit: 75000n }], - }) - - const tx = await manager.transactions.get(txId) - expect(tx.status).toBe('defined') - expect(tx.envelope.payload.calls[0]!.gasLimit).toBe(50000n) - expect(tx.envelope.payload.calls[1]!.gasLimit).toBe(75000n) - }) - - it('Should throw error when defining transaction not in requested state', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Define once - await manager.transactions.define(txId) - - // Try to define again - should throw error - await expect(manager.transactions.define(txId)).rejects.toThrow(`Transaction ${txId} is not in the requested state`) - }) - - it('Should throw error when call count mismatch in define changes', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - // Try to define with wrong number of gas limit changes - await expect( - manager.transactions.define(txId, { - calls: [ - { gasLimit: 50000n }, - { gasLimit: 75000n }, // Too many calls - ], - }), - ).rejects.toThrow(`Invalid number of calls for transaction ${txId}`) - }) - - it('Should handle transaction requests with custom options', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const customSpace = 12345n - const txId = await manager.transactions.request( - wallet!, - Network.ChainId.ARBITRUM, - [ - { - to: Address.from(Hex.random(20)), - value: 100n, - data: '0x1234', - gasLimit: 21000n, - }, - ], - { - source: 'test-dapp', - noConfigUpdate: true, - space: customSpace, - }, - ) - - const tx = await manager.transactions.get(txId) - expect(tx.status).toBe('requested') - expect(tx.source).toBe('test-dapp') - expect(tx.envelope.payload.space).toBe(customSpace) - expect(tx.requests[0]!.data).toBe('0x1234') - expect(tx.requests[0]!.gasLimit).toBe(21000n) - }) - - it('Should throw error for unknown network', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const unknownChainId = 999999 - await expect( - manager.transactions.request(wallet!, unknownChainId, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]), - ).rejects.toThrow(`Network not found for ${unknownChainId}`) - }) - - it('Should handle transactions with default values', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - // No value, data, or gasLimit - should use defaults - }, - ]) - - const tx = await manager.transactions.get(txId) - expect(tx.status).toBe('requested') - expect(tx.envelope.payload.calls[0]!.value).toBe(0n) - expect(tx.envelope.payload.calls[0]!.data).toBe('0x') - expect(tx.envelope.payload.calls[0]!.gasLimit).toBe(0n) - expect(tx.envelope.payload.calls[0]!.delegateCall).toBe(false) - expect(tx.envelope.payload.calls[0]!.onlyFallback).toBe(false) - expect(tx.envelope.payload.calls[0]!.behaviorOnError).toBe('revert') - }) - - it('Should handle relay with signature ID instead of transaction ID', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) - await provider.request({ - method: 'anvil_setBalance', - params: [wallet!, '0xa'], - }) - - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 1n, - }, - ]) - - await manager.transactions.define(txId) - const tx = await manager.transactions.get(txId) - - if (tx.status !== 'defined') { - throw new Error('Transaction not defined') - } - - const sigId = await manager.transactions.selectRelayer(txId, tx.relayerOptions[0]!.id) - - // Sign the transaction - const sigRequest = await manager.signatures.get(sigId) - const deviceSigner = sigRequest.signers.find((s) => s.status === 'ready')! - await deviceSigner.handle() - - // Relay using signature ID instead of transaction ID - await manager.transactions.relay(sigId) - - const finalTx = await manager.transactions.get(txId) - expect(finalTx.status).toBe('relayed') - }) - - it('Should get transaction and throw error for non-existent transaction', async () => { - manager = newManager() - const nonExistentId = 'non-existent-transaction-id' - - await expect(manager.transactions.get(nonExistentId)).rejects.toThrow(`Transaction ${nonExistentId} not found`) - }) - - it.skip('Should handle multiple transactions and subscriptions correctly', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - let allTransactionsUpdates = 0 - let allTransactions: Transaction[] = [] - - const unsubscribeAll = manager.transactions.onTransactionsUpdate((txs) => { - allTransactionsUpdates++ - allTransactions = txs - }) - - // Create first transaction - const txId1 = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { to: Address.from(Hex.random(20)), value: 100n }, - ]) - - // Create second transaction - const txId2 = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { to: Address.from(Hex.random(20)), value: 200n }, - ]) - - // Wait for callbacks - await new Promise((resolve) => setTimeout(resolve, 10)) - - expect(allTransactionsUpdates).toBeGreaterThanOrEqual(2) - expect(allTransactions.length).toBe(2) - expect(allTransactions.map((tx) => tx.id)).toContain(txId1) - expect(allTransactions.map((tx) => tx.id)).toContain(txId2) - - // Test individual transaction subscriptions - let tx1Updates = 0 - let tx2Updates = 0 - - const unsubscribe1 = manager.transactions.onTransactionUpdate(txId1, () => { - tx1Updates++ - }) - - const unsubscribe2 = manager.transactions.onTransactionUpdate(txId2, () => { - tx2Updates++ - }) - - // Update only first transaction - await manager.transactions.define(txId1) - await new Promise((resolve) => setTimeout(resolve, 50)) - - expect(tx1Updates).toBe(1) - expect(tx2Updates).toBe(0) - - // Update second transaction - await manager.transactions.define(txId2) - await new Promise((resolve) => setTimeout(resolve, 50)) - - expect(tx1Updates).toBe(1) - expect(tx2Updates).toBe(1) - - // Cleanup subscriptions - unsubscribeAll() - unsubscribe1() - unsubscribe2() - }) - - it('Should handle transaction source defaults', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Request without source - const txId = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ - { - to: Address.from(Hex.random(20)), - value: 100n, - }, - ]) - - const tx = await manager.transactions.get(txId) - expect(tx.source).toBe('unknown') - }) -}) diff --git a/packages/wallet/wdk/test/wallets.test.ts b/packages/wallet/wdk/test/wallets.test.ts deleted file mode 100644 index d686ac257d..0000000000 --- a/packages/wallet/wdk/test/wallets.test.ts +++ /dev/null @@ -1,1166 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { Manager, SignerActionable, SignerReady } from '../src/sequence/index.js' -import { Mnemonic, Address } from 'ox' -import { newManager } from './constants.js' -import { Config, Constants, Network } from '@0xsequence/wallet-primitives' -import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce.js' -import { IdTokenHandler } from '../src/sequence/handlers/idtoken.js' -import { IdentitySigner } from '../src/identity/signer.js' -import { MnemonicHandler } from '../src/sequence/handlers/mnemonic.js' -import { Kinds } from '../src/sequence/types/signer.js' - -describe('Wallets', () => { - let manager: Manager | undefined - - afterEach(async () => { - await manager?.stop() - }) - - // === BASIC WALLET MANAGEMENT === - - it('Should create a new wallet using a mnemonic', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - }) - - it('Should create a new wallet using google-id-token when Google ID token auth is enabled', async () => { - manager = newManager({ - identity: { - google: { - enabled: true, - clientId: 'test-google-client-id', - authMethod: 'id-token', - }, - }, - }) - - const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const loginSigner = MnemonicHandler.toSigner(loginMnemonic) - if (!loginSigner) { - throw new Error('Failed to create login signer for test') - } - - const completeAuthSpy = vi - .spyOn(handler, 'completeAuth') - .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'google-user@example.com' }]) - - const wallet = await manager.wallets.signUp({ - kind: 'google-id-token', - idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', - noGuard: true, - }) - - expect(wallet).toBeDefined() - expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - const walletEntry = await manager.wallets.get(wallet!) - expect(walletEntry).toBeDefined() - expect(walletEntry!.loginType).toBe(Kinds.LoginGoogle) - expect(walletEntry!.loginEmail).toBe('google-user@example.com') - - const configuration = await manager.wallets.getConfiguration(wallet!) - expect(configuration.login).toHaveLength(1) - expect(configuration.login[0]!.kind).toBe(Kinds.LoginGoogle) - }) - - it('Should create a new wallet using apple-id-token when Apple ID token auth is enabled', async () => { - manager = newManager({ - identity: { - apple: { - enabled: true, - clientId: 'test-apple-client-id', - authMethod: 'id-token', - }, - }, - }) - - const handler = (manager as any).shared.handlers.get(Kinds.LoginApple) as IdTokenHandler - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const loginSigner = MnemonicHandler.toSigner(loginMnemonic) - if (!loginSigner) { - throw new Error('Failed to create login signer for test') - } - - const completeAuthSpy = vi - .spyOn(handler, 'completeAuth') - .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'apple-user@example.com' }]) - - const wallet = await manager.wallets.signUp({ - kind: 'apple-id-token', - idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', - noGuard: true, - }) - - expect(wallet).toBeDefined() - expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') - await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() - - const walletEntry = await manager.wallets.get(wallet!) - expect(walletEntry).toBeDefined() - expect(walletEntry!.loginType).toBe(Kinds.LoginApple) - expect(walletEntry!.loginEmail).toBe('apple-user@example.com') - - const configuration = await manager.wallets.getConfiguration(wallet!) - expect(configuration.login).toHaveLength(1) - expect(configuration.login[0]!.kind).toBe(Kinds.LoginApple) - }) - - it('Should register and unregister Google ID token UI callbacks through the manager', async () => { - manager = newManager({ - identity: { - google: { - enabled: true, - clientId: 'test-google-client-id', - authMethod: 'id-token', - }, - }, - }) - - const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler - const promptIdToken = vi.fn() - - const unregister = manager.registerIdTokenUI(promptIdToken) - - expect(handler['onPromptIdToken']).toBe(promptIdToken) - - unregister() - - expect(handler['onPromptIdToken']).toBeUndefined() - }) - - it('Should keep Google PKCE redirect flow as the default when authMethod is not specified', async () => { - manager = newManager({ - identity: { - google: { - enabled: true, - clientId: 'test-google-client-id', - }, - }, - }) - - const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as AuthCodePkceHandler - expect(handler).toBeInstanceOf(AuthCodePkceHandler) - - const commitAuthSpy = vi - .spyOn(handler, 'commitAuth') - .mockResolvedValue('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') - - const url = await manager.wallets.startSignUpWithRedirect({ - kind: 'google-pkce', - target: '/auth/return', - metadata: {}, - }) - - expect(url).toBe('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') - expect(commitAuthSpy).toHaveBeenCalledWith('/auth/return', { type: 'auth' }) - }) - - it('Should reject google-id-token signup when Google is configured for redirect auth', async () => { - manager = newManager({ - identity: { - google: { - enabled: true, - clientId: 'test-google-client-id', - }, - }, - }) - - await expect( - manager.wallets.signUp({ - kind: 'google-id-token', - idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', - noGuard: true, - }), - ).rejects.toThrow('handler-does-not-support-id-token') - }) - - it('Should reject apple-id-token signup when Apple is configured for redirect auth', async () => { - manager = newManager({ - identity: { - apple: { - enabled: true, - clientId: 'test-apple-client-id', - }, - }, - }) - - await expect( - manager.wallets.signUp({ - kind: 'apple-id-token', - idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', - noGuard: true, - }), - ).rejects.toThrow('handler-does-not-support-id-token') - }) - - it('Should reject custom ID token signup when the provider uses redirect auth', async () => { - manager = newManager({ - identity: { - customProviders: [ - { - kind: 'custom-oidc', - authMethod: 'authcode', - issuer: 'https://issuer.example.com', - oauthUrl: 'https://issuer.example.com/oauth/authorize', - clientId: 'test-custom-client-id', - }, - ], - }, - }) - - await expect( - manager.wallets.signUp({ - kind: 'custom-oidc', - idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', - noGuard: true, - }), - ).rejects.toThrow('handler-does-not-support-id-token') - }) - - it('Should get a specific wallet by address', async () => { - manager = newManager() - const mnemonic = Mnemonic.random(Mnemonic.english) - const walletAddress = await manager.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - expect(walletAddress).toBeDefined() - - // Test successful get - const wallet = await manager.wallets.get(walletAddress!) - expect(wallet).toBeDefined() - expect(wallet!.address).toBe(walletAddress) - expect(wallet!.status).toBe('ready') - expect(wallet!.loginType).toBe('login-mnemonic') - expect(wallet!.device).toBeDefined() - expect(wallet!.loginDate).toBeDefined() - expect(wallet!.useGuard).toBe(false) - - // Test get for non-existent wallet - const nonExistentWallet = await manager.wallets.get('0x1234567890123456789012345678901234567890') - expect(nonExistentWallet).toBeUndefined() - }) - - it('Should return correct wallet list', async () => { - manager = newManager() - - // Initially empty - const initialWallets = await manager.wallets.list() - expect(initialWallets).toEqual([]) - - // Create first wallet - const wallet1 = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const walletsAfterFirst = await manager.wallets.list() - expect(walletsAfterFirst.length).toBe(1) - expect(walletsAfterFirst[0]!.address).toBe(wallet1) - }) - - // === WALLET SELECTOR REGISTRATION === - - it('Should register and unregister wallet selector', async () => { - manager = newManager() - - let _selectorCalls = 0 - const mockSelector = async () => { - _selectorCalls++ - return 'create-new' as const - } - - // Test registration - const unregister = manager!.wallets.registerWalletSelector(mockSelector) - expect(typeof unregister).toBe('function') - - // Test that registering another throws error - const secondSelector = async () => 'create-new' as const - expect(() => manager!.wallets.registerWalletSelector(secondSelector)).toThrow('wallet-selector-already-registered') - - // Test unregistration via returned function - unregister() - - // Should be able to register again after unregistration - const unregister2 = manager!.wallets.registerWalletSelector(secondSelector) - expect(typeof unregister2).toBe('function') - - // Test unregistration via method - manager!.wallets.unregisterWalletSelector(secondSelector) - - // Test unregistering wrong handler throws error - expect(() => manager!.wallets.unregisterWalletSelector(mockSelector)).toThrow('wallet-selector-not-registered') - - // Test unregistering with no handler (should work) - manager!.wallets.unregisterWalletSelector() - }) - - it('Should use wallet selector during signup when existing wallets found', async () => { - manager = newManager() - - const mnemonic = Mnemonic.random(Mnemonic.english) - - // Create initial wallet - const firstWallet = await manager!.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - expect(firstWallet).toBeDefined() - - // Stop the manager to simulate a fresh session, but keep the state provider data - await manager!.stop() - - // Create a new manager instance (simulating a fresh browser session) - manager = newManager() - - let selectorCalled = false - let selectorOptions: any - - const mockSelector = async (options: any) => { - selectorCalled = true - selectorOptions = options - return 'create-new' as const - } - - manager!.wallets.registerWalletSelector(mockSelector) - - // Sign up again with same mnemonic - should trigger selector - const secondWallet = await manager!.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - expect(selectorCalled).toBe(true) - // Use address comparison that handles case differences - expect( - selectorOptions.existingWallets.some((addr: string) => - Address.isEqual(addr as Address.Address, firstWallet! as Address.Address), - ), - ).toBe(true) - expect(selectorOptions.signerAddress).toBeDefined() - expect(selectorOptions.context.isRedirect).toBe(false) - expect(secondWallet).toBeDefined() - expect(secondWallet).not.toBe(firstWallet) // Should be new wallet - }) - - it('Should abort signup when wallet selector returns abort-signup', async () => { - manager = newManager() - - const mnemonic = Mnemonic.random(Mnemonic.english) - - // Create initial wallet - const _firstWallet = await manager!.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - // Stop and restart manager to simulate fresh session - await manager!.stop() - manager = newManager() - - const mockSelector = async () => 'abort-signup' as const - manager!.wallets.registerWalletSelector(mockSelector) - - // Sign up again - should abort - const result = await manager!.wallets.signUp({ - mnemonic, - kind: 'mnemonic', - noGuard: true, - }) - - expect(result).toBeUndefined() - }) - - // === BLOCKCHAIN INTEGRATION === - - it('Should get nonce for wallet', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - // Test getNonce - this requires network access, so we expect it to work or throw network error - try { - const nonce = await manager.wallets.getNonce(Network.ChainId.MAINNET, wallet!, 0n) - expect(typeof nonce).toBe('bigint') - expect(nonce).toBeGreaterThanOrEqual(0n) - } catch (error) { - // Network errors are acceptable in tests - expect(error).toBeDefined() - } - }) - - it('Should check if wallet is updated onchain', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - // Test isUpdatedOnchain - try { - const isUpdated = await manager.wallets.isUpdatedOnchain(wallet!, Network.ChainId.MAINNET) - expect(typeof isUpdated).toBe('boolean') - } catch (error) { - // Network errors are acceptable in tests - expect(error).toBeDefined() - } - }) - - it('Should throw error for unsupported network in getNonce', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Use a chainId that doesn't exist in the test networks - await expect(manager.wallets.getNonce(999999, wallet!, 0n)).rejects.toThrow('network-not-found') - }) - - it('Should throw error for unsupported network in isUpdatedOnchain', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - await expect(manager.wallets.isUpdatedOnchain(wallet!, 999999)).rejects.toThrow('network-not-found') - }) - - // === CONFIGURATION MANAGEMENT === - - it('Should complete configuration update', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - // Create a configuration update by logging out - const requestId = await manager.wallets.logout(wallet!) - - const request = await manager.signatures.get(requestId) - const deviceSigner = request.signers.find((s) => s.handler?.kind === 'local-device') - await (deviceSigner as SignerReady).handle() - - // Test completeConfigurationUpdate directly - await manager.wallets.completeConfigurationUpdate(requestId) - - const completedRequest = await manager.signatures.get(requestId) - expect(completedRequest.status).toBe('completed') - }) - - it('Should get detailed wallet configuration', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const config = await manager.wallets.getConfiguration(wallet!) - - expect(config.devices).toBeDefined() - expect(config.devices.length).toBe(1) - expect(config.devices[0]!.kind).toBe('local-device') - expect(config.devices[0]!.address).toBeDefined() - - expect(config.login).toBeDefined() - expect(config.login.length).toBe(1) - expect(config.login[0]!.kind).toBe('login-mnemonic') - - expect(config.walletGuard).not.toBeDefined() // No guard for noGuard: true - - expect(config.raw).toBeDefined() - expect(config.raw.loginTopology).toBeDefined() - expect(config.raw.devicesTopology).toBeDefined() - expect(config.raw.modules).toBeDefined() - }) - - it('Should include guard configuration when enabled', async () => { - manager = newManager(undefined, undefined, `guard_enabled_${Date.now()}`) - const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet - const sessionsGuardAddress = (manager as any).shared.sequence.guardAddresses.sessions - const sessionsModuleAddress = (manager as any).shared.sequence.extensions.sessions - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: false, - }) - - const config = await manager.wallets.getConfiguration(wallet!) - - expect(config.walletGuard?.address).toBe(guardAddress) - expect(config.raw.guardTopology).toBeDefined() - expect(Config.findSignerLeaf(config.raw.guardTopology!, guardAddress)).toBeDefined() - expect( - Config.findSignerLeaf(config.raw.guardTopology!, Constants.PlaceholderAddress as Address.Address), - ).toBeUndefined() - - const sessionsModule = config.raw.modules.find((m: any) => - Address.isEqual(m.sapientLeaf.address, sessionsModuleAddress), - ) - expect(sessionsModule?.guardLeaf).toBeDefined() - expect(Config.findSignerLeaf(sessionsModule!.guardLeaf!, sessionsGuardAddress)).toBeDefined() - expect( - Config.findSignerLeaf(sessionsModule!.guardLeaf!, Constants.PlaceholderAddress as Address.Address), - ).toBeUndefined() - - expect(config.moduleGuards.get(sessionsModuleAddress as Address.Address)?.address).toBe(sessionsGuardAddress) - }) - - it('Should support non-nested guard topologies', async () => { - manager = newManager( - { - defaultGuardTopology: { - type: 'signer', - address: Constants.PlaceholderAddress, - weight: 1n, - }, - }, - undefined, - `flat_guard_${Date.now()}`, - ) - - const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet - const sessionsGuardAddress = (manager as any).shared.sequence.guardAddresses.sessions - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: false, - }) - - const config = await manager.wallets.getConfiguration(wallet!) - - expect(config.walletGuard?.address).toBe(guardAddress) - expect(config.raw.guardTopology).toBeDefined() - expect(Config.findSignerLeaf(config.raw.guardTopology!, guardAddress)).toBeDefined() - expect( - Config.findSignerLeaf(config.raw.guardTopology!, Constants.PlaceholderAddress as Address.Address), - ).toBeUndefined() - - const sessionsModuleAddress = (manager as any).shared.sequence.extensions.sessions - const sessionsModule = config.raw.modules.find((m: any) => - Address.isEqual(m.sapientLeaf.address, sessionsModuleAddress), - ) - expect(sessionsModule?.guardLeaf).toBeDefined() - expect(Config.findSignerLeaf(sessionsModule!.guardLeaf!, sessionsGuardAddress)).toBeDefined() - }) - - it('Should fail signup when default guard topology lacks placeholder address', async () => { - manager = newManager( - { - defaultGuardTopology: { - type: 'signer', - address: '0x0000000000000000000000000000000000000001', - weight: 1n, - }, - }, - undefined, - `guard_missing_placeholder_${Date.now()}`, - ) - - await expect( - manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: false, - }), - ).rejects.toThrow('Guard address replacement failed for role wallet') - }) - - // === ERROR HANDLING === - - it('Should throw error when trying to get configuration for non-existent wallet', async () => { - manager = newManager() - await expect(manager.wallets.getConfiguration('0x1234567890123456789012345678901234567890')).rejects.toThrow() - }) - - it('Should throw error when trying to list devices for non-existent wallet', async () => { - manager = newManager() - await expect(manager.wallets.listDevices('0x1234567890123456789012345678901234567890')).rejects.toThrow( - 'wallet-not-found', - ) - }) - - it('Should throw error when wallet selector returns invalid result', async () => { - manager = newManager() - - const mnemonic = Mnemonic.random(Mnemonic.english) - await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - await manager.wallets.logout(await manager.wallets.list().then((w) => w[0]!.address), { skipRemoveDevice: true }) - - const invalidSelector = async () => 'invalid-result' as any - manager.wallets.registerWalletSelector(invalidSelector) - - await expect(manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true })).rejects.toThrow( - 'invalid-result-from-wallet-selector', - ) - }) - - // === EXISTING TESTS (keeping them for backward compatibility) === - - it('Should logout from a wallet using the login key', async () => { - const manager = newManager() - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic: loginMnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - const wallets = await manager.wallets.list() - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - - const requestId = await manager.wallets.logout(wallet!) - expect(requestId).toBeDefined() - - let signRequests = 0 - const unregistedUI = manager.registerMnemonicUI(async (respond) => { - signRequests++ - await respond(loginMnemonic) - }) - - const request = await manager.signatures.get(requestId) - expect(request).toBeDefined() - expect(request.action).toBe('logout') - - const loginSigner = request.signers.find((signer) => signer.handler?.kind === 'login-mnemonic') - expect(loginSigner).toBeDefined() - expect(loginSigner?.status).toBe('actionable') - - const result = await (loginSigner as SignerActionable).handle() - expect(result).toBe(true) - - expect(signRequests).toBe(1) - unregistedUI() - - await manager.wallets.completeLogout(requestId) - expect((await manager.signatures.get(requestId))?.status).toBe('completed') - const wallets2 = await manager.wallets.list() - expect(wallets2.length).toBe(0) - await expect(manager.wallets.has(wallet!)).resolves.toBeFalsy() - }) - - it('Should logout from a wallet using the device key', async () => { - const manager = newManager() - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic: loginMnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - const wallets = await manager.wallets.list() - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - expect(wallets[0]!.status).toBe('ready') - - const requestId = await manager.wallets.logout(wallet!) - expect(requestId).toBeDefined() - - const wallets2 = await manager.wallets.list() - expect(wallets2.length).toBe(1) - expect(wallets2[0]!.address).toBe(wallet!) - expect(wallets2[0]!.status).toBe('logging-out') - - const request = await manager.signatures.get(requestId) - expect(request).toBeDefined() - expect(request.action).toBe('logout') - - const deviceSigner = request.signers.find((signer) => signer.handler?.kind === 'local-device') - expect(deviceSigner).toBeDefined() - expect(deviceSigner?.status).toBe('ready') - - const result = await (deviceSigner as SignerReady).handle() - expect(result).toBe(true) - - await manager.wallets.completeLogout(requestId) - expect((await manager.signatures.get(requestId))?.status).toBe('completed') - const wallets3 = await manager.wallets.list() - expect(wallets3.length).toBe(0) - await expect(manager.wallets.has(wallet!)).resolves.toBeFalsy() - }) - - it('Should login to an existing wallet using the mnemonic signer', async () => { - manager = newManager() - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - // Clear the storage without logging out - await manager.stop() - - manager = newManager(undefined, undefined, 'device-2') - await expect(manager.wallets.list()).resolves.toEqual([]) - const requestId1 = await manager.wallets.login({ wallet: wallet! }) - expect(requestId1).toBeDefined() - - const wallets = await manager.wallets.list() - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - expect(wallets[0]!.status).toBe('logging-in') - - let signRequests = 0 - const unregistedUI = manager.registerMnemonicUI(async (respond) => { - signRequests++ - await respond(mnemonic) - }) - - const request = await manager.signatures.get(requestId1!) - expect(request).toBeDefined() - expect(request.action).toBe('login') - - const mnemonicSigner = request.signers.find((signer) => signer.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner).toBeDefined() - expect(mnemonicSigner?.status).toBe('actionable') - - const result = await (mnemonicSigner as SignerActionable).handle() - expect(result).toBe(true) - - expect(signRequests).toBe(1) - unregistedUI() - - // Complete the login process - await manager.wallets.completeLogin(requestId1!) - expect((await manager.signatures.get(requestId1!))?.status).toBe('completed') - const wallets2 = await manager.wallets.list() - expect(wallets2.length).toBe(1) - expect(wallets2[0]!.address).toBe(wallet!) - expect(wallets2[0]!.status).toBe('ready') - - // The wallet should have 2 device keys and 2 recovery keys - const config = await manager.wallets.getConfiguration(wallet!) - expect(config.devices.length).toBe(2) - const recovery = await manager.recovery.getSigners(wallet!) - expect(recovery?.length).toBe(2) - }) - - it('Should logout and then login to an existing wallet using the mnemonic signer', async () => { - manager = newManager() - - await expect(manager.wallets.list()).resolves.toEqual([]) - - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - const wallets = await manager.wallets.list() - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - - const requestId = await manager.wallets.logout(wallet!) - expect(requestId).toBeDefined() - - const request = await manager.signatures.get(requestId) - expect(request).toBeDefined() - expect(request.action).toBe('logout') - - const deviceSigner = request.signers.find((signer) => signer.handler?.kind === 'local-device') - expect(deviceSigner).toBeDefined() - expect(deviceSigner?.status).toBe('ready') - - const result = await (deviceSigner as SignerReady).handle() - expect(result).toBe(true) - - await manager.wallets.completeLogout(requestId) - expect((await manager.signatures.get(requestId))?.status).toBe('completed') - - await expect(manager.wallets.list()).resolves.toEqual([]) - - // Login again to the same wallet - const requestId2 = await manager.wallets.login({ wallet: wallet! }) - expect(requestId2).toBeDefined() - - let signRequests2 = 0 - const unregistedUI2 = manager.registerMnemonicUI(async (respond) => { - signRequests2++ - await respond(mnemonic) - }) - - const request2 = await manager.signatures.get(requestId2!) - expect(request2).toBeDefined() - expect(request2.action).toBe('login') - - const mnemonicSigner2 = request2.signers.find((signer) => signer.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner2).toBeDefined() - expect(mnemonicSigner2?.status).toBe('actionable') - - const result2 = await (mnemonicSigner2 as SignerActionable).handle() - expect(result2).toBe(true) - - expect(signRequests2).toBe(1) - unregistedUI2() - - await manager.wallets.completeLogin(requestId2!) - expect((await manager.signatures.get(requestId2!))?.status).toBe('completed') - const wallets3 = await manager.wallets.list() - expect(wallets3.length).toBe(1) - expect(wallets3[0]!.address).toBe(wallet!) - - // The wallet should have a single device key and a single recovery key - const config = await manager.wallets.getConfiguration(wallet!) - expect(config.devices.length).toBe(1) - const recovery = await manager.recovery.getSigners(wallet!) - expect(recovery?.length).toBe(1) - - // The kind of the device key should be 'local-device' - expect(config.devices[0]!.kind).toBe('local-device') - - // The kind of the recovery key should be 'local-recovery' - expect(recovery?.[0]!.kind).toBe('local-device') - }) - - it('Should fail to logout from a non-existent wallet', async () => { - const manager = newManager() - const requestId = manager.wallets.logout('0x1234567890123456789012345678901234567890') - await expect(requestId).rejects.toThrow('wallet-not-found') - }) - - it('Should fail to login to an already logged in wallet', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const requestId = manager.wallets.login({ wallet: wallet! }) - await expect(requestId).rejects.toThrow('wallet-already-logged-in') - }) - - it('Should make you select among a single option if login with mnemonic', async () => { - const manager = newManager() - const mnemonic = Mnemonic.random(Mnemonic.english) - const wallet = await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - expect(wallet).toBeDefined() - - await manager.wallets.logout(wallet!, { skipRemoveDevice: true }) - - let signRequests = 0 - const unregistedUI = manager.registerMnemonicUI(async (respond) => { - signRequests++ - await respond(mnemonic) - }) - - let selectWalletCalls = 0 - const requestId = await manager.wallets.login({ - mnemonic: mnemonic, - kind: 'mnemonic', - selectWallet: async () => { - selectWalletCalls++ - return wallet! - }, - }) - - expect(selectWalletCalls).toBe(1) - expect(requestId).toBeDefined() - - const wallets = await manager.wallets.list() - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - expect(wallets[0]!.status).toBe('logging-in') - - const request = await manager.signatures.get(requestId!) - expect(request).toBeDefined() - expect(request.action).toBe('login') - - const mnemonicSigner = request.signers.find((signer) => signer.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner).toBeDefined() - expect(mnemonicSigner?.status).toBe('ready') - - const result = await (mnemonicSigner as SignerActionable).handle() - expect(result).toBe(true) - - // The sign request should be completed immediately because the signer is ready - // and not trigger the onPromptMnemonic callback - expect(signRequests).toBe(0) - unregistedUI() - - await manager.wallets.completeLogin(requestId!) - expect((await manager.signatures.get(requestId!))?.status).toBe('completed') - const wallets2 = await manager.wallets.list() - expect(wallets2.length).toBe(1) - expect(wallets2[0]!.address).toBe(wallet!) - expect(wallets2[0]!.status).toBe('ready') - }) - - it('Should trigger an update when a wallet is logged in', async () => { - const manager = newManager() - // eslint-disable-next-line - let wallet: Address.Address | undefined - - let callbackCalls = 0 - let unregisterCallback: (() => void) | undefined - - const callbackFiredPromise = new Promise((resolve) => { - unregisterCallback = manager.wallets.onWalletsUpdate((wallets) => { - callbackCalls++ - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - expect(wallets[0]!.status).toBe('ready') - resolve() - }) - }) - - wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - await callbackFiredPromise - - expect(callbackCalls).toBe(1) - unregisterCallback!() - }) - - it('Should trigger an update when a wallet is logged out', async () => { - const manager = newManager() - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - let callbackCalls = 0 - let unregisterCallback: (() => void) | undefined - const callbackFiredPromise = new Promise((resolve) => { - unregisterCallback = manager.wallets.onWalletsUpdate((wallets) => { - callbackCalls++ - expect(wallets.length).toBe(0) - resolve() - }) - }) - - await manager.wallets.logout(wallet!, { skipRemoveDevice: true }) - await callbackFiredPromise - - expect(callbackCalls).toBe(1) - unregisterCallback!() - }) - - it('Should trigger an update when a wallet is logging out', async () => { - const manager = newManager() - - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - let callbackCalls = 0 - let unregisterCallback: (() => void) | undefined - const callbackFiredPromise = new Promise((resolve) => { - unregisterCallback = manager.wallets.onWalletsUpdate((wallets) => { - callbackCalls++ - expect(wallets.length).toBe(1) - expect(wallets[0]!.address).toBe(wallet!) - expect(wallets[0]!.status).toBe('logging-out') - resolve() - }) - }) - - await manager.wallets.logout(wallet!) - await callbackFiredPromise - - expect(callbackCalls).toBe(1) - unregisterCallback!() - }) - - it('Should list all active devices for a wallet', async () => { - const manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - - const devices = await manager.wallets.listDevices(wallet!) - expect(devices.length).toBe(1) - expect(devices[0]).toBeDefined() - expect(devices[0]!.address).not.toBe(wallet) - expect(devices[0]!.isLocal).toBe(true) - }) - - it('Should list all active devices for a wallet, including a new remote device', async () => { - // Step 1: Wallet signs up on device 1 - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const managerDevice1 = newManager(undefined, undefined, 'device-1') - - const wallet = await managerDevice1.wallets.signUp({ - mnemonic: loginMnemonic, - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - // Verify initial state from Device 1's perspective - const devices1 = await managerDevice1.wallets.listDevices(wallet!) - expect(devices1.length).toBe(1) - expect(devices1[0]!.isLocal).toBe(true) - const device1Address = devices1[0]!.address - - // Wallet logs in on device 2 - const managerDevice2 = newManager(undefined, undefined, 'device-2') - - // Initiate the login process from Device 2. This returns a signature request ID. - const requestId = await managerDevice2.wallets.login({ wallet: wallet! }) - expect(requestId).toBeDefined() - - // Register the Mnemonic UI handler for Device 2 to authorize the new device. - // It will provide the master mnemonic when asked. - const unregisterUI = managerDevice2.registerMnemonicUI(async (respond) => { - await respond(loginMnemonic) - }) - - // Get the signature request and handle it using the mnemonic signer. - const sigRequest = await managerDevice2.signatures.get(requestId) - const mnemonicSigner = sigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic') - expect(mnemonicSigner).toBeDefined() - expect(mnemonicSigner?.status).toBe('actionable') - - const handled = await (mnemonicSigner as SignerActionable).handle() - expect(handled).toBe(true) - - // Clean up the UI handler - unregisterUI() - - // Finalize the login for Device 2 - await managerDevice2.wallets.completeLogin(requestId) - - // Step 3: Verification from both devices' perspectives - - // Verify from Device 2's perspective - const devices2 = await managerDevice2.wallets.listDevices(wallet!) - expect(devices2.length).toBe(2) - - const device2Entry = devices2.find((d) => d.isLocal === true) // Device 2 is the local device - const device1EntryForDevice2 = devices2.find((d) => d.isLocal === false) // Device 1 is the remote device - - expect(device2Entry).toBeDefined() - expect(device2Entry?.isLocal).toBe(true) - expect(device1EntryForDevice2).toBeDefined() - expect(device1EntryForDevice2?.address).toBe(device1Address) - - // Verify from Device 1's perspective - const devices1AfterLogin = await managerDevice1.wallets.listDevices(wallet!) - expect(devices1AfterLogin.length).toBe(2) // Now the wallet has logged in on two devices - - const device1EntryForDevice1 = devices1AfterLogin.find((d) => d.isLocal === true) - const device2EntryForDevice1 = devices1AfterLogin.find((d) => d.isLocal === false) - - expect(device1EntryForDevice1).toBeDefined() - expect(device1EntryForDevice1?.isLocal).toBe(true) - expect(device1EntryForDevice1?.address).toBe(device1Address) - expect(device2EntryForDevice1).toBeDefined() - expect(device2EntryForDevice1?.isLocal).toBe(false) - - // Stop the managers to clean up resources - await managerDevice1.stop() - await managerDevice2.stop() - }) - - it('Should remotely log out a device', async () => { - // === Step 1: Setup with two devices === - const loginMnemonic = Mnemonic.random(Mnemonic.english) - const managerDevice1 = newManager(undefined, undefined, 'device-1') - - const wallet = await managerDevice1.wallets.signUp({ - mnemonic: loginMnemonic, - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const managerDevice2 = newManager(undefined, undefined, 'device-2') - const loginRequestId = await managerDevice2.wallets.login({ wallet: wallet! }) - - const unregisterUI = managerDevice2.registerMnemonicUI(async (respond) => { - await respond(loginMnemonic) - }) - - const loginSigRequest = await managerDevice2.signatures.get(loginRequestId) - const mnemonicSigner = loginSigRequest.signers.find((s) => s.handler?.kind === 'login-mnemonic')! - await (mnemonicSigner as SignerActionable).handle() - unregisterUI() - - await managerDevice2.wallets.completeLogin(loginRequestId) - - const initialDevices = await managerDevice1.wallets.listDevices(wallet!) - console.log('Initial devices', initialDevices) - expect(initialDevices.length).toBe(2) - const device2Address = initialDevices.find((d) => !d.isLocal)!.address - - // === Step 2: Initiate remote logout from Device 1 === - const remoteLogoutRequestId = await managerDevice1.wallets.remoteLogout(wallet!, device2Address) - expect(remoteLogoutRequestId).toBeDefined() - - // === Step 3: Authorize the remote logout from Device 1 === - const logoutSigRequest = await managerDevice1.signatures.get(remoteLogoutRequestId) - expect(logoutSigRequest.action).toBe('remote-logout') - - const device1Signer = logoutSigRequest.signers.find((s) => s.handler?.kind === 'local-device') - expect(device1Signer).toBeDefined() - expect(device1Signer?.status).toBe('ready') - - const handled = await (device1Signer as SignerReady).handle() - expect(handled).toBe(true) - - await managerDevice1.wallets.completeConfigurationUpdate(remoteLogoutRequestId) - - // The signature request should now be marked as completed - expect((await managerDevice1.signatures.get(remoteLogoutRequestId))?.status).toBe('completed') - - // === Step 5: Verification === - const finalDevices = await managerDevice1.wallets.listDevices(wallet!) - console.log('Final devices', finalDevices) - expect(finalDevices.length).toBe(1) - expect(finalDevices[0]!.isLocal).toBe(true) - expect(finalDevices[0]!.address).not.toBe(device2Address) - - await managerDevice1.stop() - await managerDevice2.stop() - }) - - it('Should not be able to remotely log out from the current device', async () => { - manager = newManager() - const wallet = await manager.wallets.signUp({ - mnemonic: Mnemonic.random(Mnemonic.english), - kind: 'mnemonic', - noGuard: true, - }) - expect(wallet).toBeDefined() - - const devices = await manager.wallets.listDevices(wallet!) - expect(devices.length).toBe(1) - const localDeviceAddress = devices[0]!.address - - const remoteLogoutPromise = manager.wallets.remoteLogout(wallet!, localDeviceAddress) - - await expect(remoteLogoutPromise).rejects.toThrow('cannot-remote-logout-from-local-device') - }) -}) diff --git a/packages/wallet/wdk/tsconfig.json b/packages/wallet/wdk/tsconfig.json deleted file mode 100644 index fed9c77b49..0000000000 --- a/packages/wallet/wdk/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@repo/typescript-config/base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "types": ["node"] - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/wallet/wdk/vitest.config.ts b/packages/wallet/wdk/vitest.config.ts deleted file mode 100644 index 9c2092c0c4..0000000000 --- a/packages/wallet/wdk/vitest.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - environment: 'happy-dom', - globals: true, - setupFiles: ['./test/setup.ts'], - minWorkers: 1, - maxWorkers: 1, - environmentOptions: { - happyDOM: { - settings: { - fetch: { - disableSameOriginPolicy: true, - }, - }, - }, - }, - }, -}) diff --git a/playgrounds/next/.gitignore b/playgrounds/next/.gitignore new file mode 100644 index 0000000000..8f322f0d8f --- /dev/null +++ b/playgrounds/next/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# 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 diff --git a/playgrounds/next/next.config.mjs b/playgrounds/next/next.config.mjs new file mode 100644 index 0000000000..8831ff653a --- /dev/null +++ b/playgrounds/next/next.config.mjs @@ -0,0 +1,20 @@ +import bundleAnalyzer from '@next/bundle-analyzer' + +const withBundleAnalyzer = bundleAnalyzer({ + enabled: process.env.ANALYZE === 'true', +}) + +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + externalDir: true, + }, + webpack(config) { + config.resolve.extensionAlias = { + '.js': ['.js', '.ts'], + } + return config + }, +} + +export default withBundleAnalyzer(nextConfig) diff --git a/playgrounds/next/package.json b/playgrounds/next/package.json new file mode 100644 index 0000000000..2cb3c92089 --- /dev/null +++ b/playgrounds/next/package.json @@ -0,0 +1,31 @@ +{ + "name": "next-app", + "private": true, + "scripts": { + "analyze": "ANALYZE=true next build", + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@next/bundle-analyzer": "^15.2.4", + "@tanstack/react-query": ">=5.45.1", + "next": "15.4.10", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "viem": "2.*", + "wagmi": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.14.1", + "@types/react": ">=18.3.1", + "@types/react-dom": ">=18.3.0", + "bufferutil": "^4.0.8", + "encoding": "^0.1.13", + "lokijs": "^1.5.12", + "pino-pretty": "^13.0.0", + "supports-color": "^9.4.0", + "utf-8-validate": "^6.0.5" + } +} diff --git a/playgrounds/next/src/app/contracts.ts b/playgrounds/next/src/app/contracts.ts new file mode 100644 index 0000000000..d7d66754a2 --- /dev/null +++ b/playgrounds/next/src/app/contracts.ts @@ -0,0 +1,202 @@ +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'approved', + type: 'address', + }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'operator', + type: 'address', + }, + { + indexed: false, + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { indexed: true, name: 'to', type: 'address' }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'operator', type: 'address' }, + { name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/playgrounds/next/src/app/globals.css b/playgrounds/next/src/app/globals.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/playgrounds/next/src/app/globals.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/playgrounds/next/src/app/layout.tsx b/playgrounds/next/src/app/layout.tsx new file mode 100644 index 0000000000..72ec934a03 --- /dev/null +++ b/playgrounds/next/src/app/layout.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from 'next' +import { Inter } from 'next/font/google' +import { headers } from 'next/headers' +import type { ReactNode } from 'react' +import { cookieToInitialState } from 'wagmi' +import './globals.css' + +import { getConfig } from '../wagmi' +import { Providers } from './providers' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'Create Wagmi', + description: 'Generated by create-wagmi', +} + +export default async function RootLayout(props: { children: ReactNode }) { + const initialState = cookieToInitialState( + getConfig(), + (await headers()).get('cookie'), + ) + return ( + + + {props.children} + + + ) +} diff --git a/playgrounds/next/src/app/page.tsx b/playgrounds/next/src/app/page.tsx new file mode 100644 index 0000000000..33c1bf0abb --- /dev/null +++ b/playgrounds/next/src/app/page.tsx @@ -0,0 +1,412 @@ +'use client' + +import type { FormEvent } from 'react' +import { type Hex, formatEther, parseAbi, parseEther } from 'viem' +import { + type BaseError, + useAccount, + useAccountEffect, + useBalance, + useBlockNumber, + useChainId, + useConfig, + useConnect, + useConnections, + useConnectorClient, + useDisconnect, + useEnsName, + useReadContract, + useReadContracts, + useSendTransaction, + useSignMessage, + useSwitchAccount, + useSwitchChain, + useWaitForTransactionReceipt, + useWriteContract, +} from 'wagmi' +import { switchChain } from 'wagmi/actions' +import { optimism, sepolia } from 'wagmi/chains' + +import { wagmiContractConfig } from './contracts' + +export default function App() { + useAccountEffect({ + onConnect(_data) { + // console.log('onConnect', data) + }, + onDisconnect() { + // console.log('onDisconnect') + }, + }) + + return ( + <> + + + + + + + + + + + + + + + + ) +} + +function Account() { + const account = useAccount() + const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ + address: account.address, + }) + + return ( +
+

Account

+ +
+ account: {account.address} {ensName} +
+ chainId: {account.chainId} +
+ status: {account.status} +
+ + {account.status === 'connected' && ( + + )} +
+ ) +} + +function Connect() { + const chainId = useChainId() + const { connectors, connect, status, error } = useConnect() + + return ( +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ ) +} + +function SwitchAccount() { + const account = useAccount() + const { connectors, switchAccount } = useSwitchAccount() + + return ( +
+

Switch Account

+ + {connectors.map((connector) => ( + + ))} +
+ ) +} + +function SwitchChain() { + const chainId = useChainId() + const { chains, switchChain, error } = useSwitchChain() + + return ( +
+

Switch Chain

+ + {chains.map((chain) => ( + + ))} + + {error?.message} +
+ ) +} + +function SignMessage() { + const { data, signMessage } = useSignMessage() + + return ( +
+

Sign Message

+ +
{ + event.preventDefault() + const formData = new FormData(event.target as HTMLFormElement) + signMessage({ message: formData.get('message') as string }) + }} + > + + +
+ + {data} +
+ ) +} + +function Connections() { + const connections = useConnections() + + return ( +
+

Connections

+ + {connections.map((connection) => ( +
+
connector {connection.connector.name}
+
accounts: {JSON.stringify(connection.accounts)}
+
chainId: {connection.chainId}
+
+ ))} +
+ ) +} + +function Balance() { + const { address } = useAccount() + + const { data: default_ } = useBalance({ address }) + const { data: account_ } = useBalance({ address }) + const { data: optimism_ } = useBalance({ + address, + chainId: optimism.id, + }) + + return ( +
+

Balance

+ +
+ Balance (Default Chain):{' '} + {!!default_?.value && formatEther(default_.value)} +
+
+ Balance (Account Chain):{' '} + {!!account_?.value && formatEther(account_.value)} +
+
+ Balance (Optimism Chain):{' '} + {!!optimism_?.value && formatEther(optimism_.value)} +
+
+ ) +} + +function BlockNumber() { + const { data: default_ } = useBlockNumber({ watch: true }) + const { data: account_ } = useBlockNumber({ + watch: true, + }) + const { data: optimism_ } = useBlockNumber({ + chainId: optimism.id, + watch: true, + }) + + return ( +
+

Block Number

+ +
Block Number (Default Chain): {default_?.toString()}
+
Block Number (Account Chain): {account_?.toString()}
+
Block Number (Optimism): {optimism_?.toString()}
+
+ ) +} + +function ConnectorClient() { + const { data, error } = useConnectorClient() + return ( +
+

Connector Client

+ client {data?.account?.address} {data?.chain?.id} + {error?.message} +
+ ) +} + +function SendTransaction() { + const { data: hash, error, isPending, sendTransaction } = useSendTransaction() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as Hex + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Send Transaction

+
+ + + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +function ReadContract() { + const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
+ ) +} + +function ReadContracts() { + const { data } = useReadContracts({ + allowFailure: false, + contracts: [ + { + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }, + { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69n], + }, + { + ...wagmiContractConfig, + functionName: 'totalSupply', + }, + ], + }) + const [balance, ownerOf, totalSupply] = data || [] + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
Owner of Token 69: {ownerOf?.toString()}
+
Total Supply: {totalSupply?.toString()}
+
+ ) +} + +function WriteContract() { + const { data: hash, error, isPending, writeContract } = useWriteContract() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: parseAbi(['function mint(uint256 tokenId)']), + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Write Contract

+
+ + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +function Repro() { + const config = useConfig() + const chainId = useChainId() + + // biome-ignore lint/suspicious/noConsoleLog: + console.log('chainId from useChainId is', chainId) + return ( +
+ Current Chain Id: {chainId} + + +
+ ) +} diff --git a/playgrounds/next/src/app/providers.tsx b/playgrounds/next/src/app/providers.tsx new file mode 100644 index 0000000000..3f0082dd22 --- /dev/null +++ b/playgrounds/next/src/app/providers.tsx @@ -0,0 +1,23 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { type ReactNode, useState } from 'react' +import { type State, WagmiProvider } from 'wagmi' + +import { getConfig } from '../wagmi' + +export function Providers(props: { + children: ReactNode + initialState?: State +}) { + const [config] = useState(() => getConfig()) + const [queryClient] = useState(() => new QueryClient()) + + return ( + + + {props.children} + + + ) +} diff --git a/playgrounds/next/src/wagmi.ts b/playgrounds/next/src/wagmi.ts new file mode 100644 index 0000000000..0c9db90a5d --- /dev/null +++ b/playgrounds/next/src/wagmi.ts @@ -0,0 +1,31 @@ +import { http, cookieStorage, createConfig, createStorage } from 'wagmi' +import { mainnet, optimism, sepolia } from 'wagmi/chains' +import { injected, metaMask, walletConnect } from 'wagmi/connectors' + +export function getConfig() { + return createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + injected(), + walletConnect({ + projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!, + }), + metaMask(), + ], + storage: createStorage({ + storage: cookieStorage, + }), + ssr: true, + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + }, + }) +} + +declare module 'wagmi' { + interface Register { + config: ReturnType + } +} diff --git a/playgrounds/next/tsconfig.json b/playgrounds/next/tsconfig.json new file mode 100644 index 0000000000..9eb40a20d5 --- /dev/null +++ b/playgrounds/next/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/playgrounds/nuxt/.gitignore b/playgrounds/nuxt/.gitignore new file mode 100644 index 0000000000..4a7f73a2ed --- /dev/null +++ b/playgrounds/nuxt/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/playgrounds/nuxt/app.vue b/playgrounds/nuxt/app.vue new file mode 100644 index 0000000000..98b46bf528 --- /dev/null +++ b/playgrounds/nuxt/app.vue @@ -0,0 +1,28 @@ + + + diff --git a/playgrounds/nuxt/components/Account.vue b/playgrounds/nuxt/components/Account.vue new file mode 100644 index 0000000000..33f1491dac --- /dev/null +++ b/playgrounds/nuxt/components/Account.vue @@ -0,0 +1,22 @@ + + + diff --git a/playgrounds/nuxt/components/Connect.vue b/playgrounds/nuxt/components/Connect.vue new file mode 100644 index 0000000000..93320448c0 --- /dev/null +++ b/playgrounds/nuxt/components/Connect.vue @@ -0,0 +1,19 @@ + + + diff --git a/playgrounds/nuxt/nuxt.config.ts b/playgrounds/nuxt/nuxt.config.ts new file mode 100644 index 0000000000..adfe7fd2d8 --- /dev/null +++ b/playgrounds/nuxt/nuxt.config.ts @@ -0,0 +1,7 @@ +import { defineNuxtConfig } from 'nuxt/config' + +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + devtools: { enabled: true }, + modules: ['@wagmi/vue/nuxt'], +}) diff --git a/playgrounds/nuxt/package.json b/playgrounds/nuxt/package.json new file mode 100644 index 0000000000..6f5ff9b1a6 --- /dev/null +++ b/playgrounds/nuxt/package.json @@ -0,0 +1,20 @@ +{ + "name": "nuxt-app", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "_postinstall": "nuxt prepare" + }, + "dependencies": { + "@tanstack/vue-query": ">=5.45.0", + "@wagmi/vue": "workspace:*", + "nuxt": "^3.19.0", + "viem": "2.*", + "vue": ">=3.4.21", + "vue-router": "^4.3.2" + } +} diff --git a/playgrounds/nuxt/plugins/wagmi.ts b/playgrounds/nuxt/plugins/wagmi.ts new file mode 100644 index 0000000000..b6abe5bcd2 --- /dev/null +++ b/playgrounds/nuxt/plugins/wagmi.ts @@ -0,0 +1,10 @@ +import { VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { defineNuxtPlugin } from 'nuxt/app' + +import { config } from '../wagmi' + +// TODO: Move to @wagmi/vue/nuxt nitro plugin +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.vueApp.use(WagmiPlugin, { config }).use(VueQueryPlugin, {}) +}) diff --git a/playgrounds/nuxt/public/favicon.ico b/playgrounds/nuxt/public/favicon.ico new file mode 100644 index 0000000000..18993ad91c Binary files /dev/null and b/playgrounds/nuxt/public/favicon.ico differ diff --git a/playgrounds/nuxt/server/tsconfig.json b/playgrounds/nuxt/server/tsconfig.json new file mode 100644 index 0000000000..b9ed69c19e --- /dev/null +++ b/playgrounds/nuxt/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +} diff --git a/playgrounds/nuxt/tsconfig.json b/playgrounds/nuxt/tsconfig.json new file mode 100644 index 0000000000..4b34df1571 --- /dev/null +++ b/playgrounds/nuxt/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nuxt/tsconfig.json" +} diff --git a/playgrounds/nuxt/wagmi.ts b/playgrounds/nuxt/wagmi.ts new file mode 100644 index 0000000000..83e8569ea8 --- /dev/null +++ b/playgrounds/nuxt/wagmi.ts @@ -0,0 +1,29 @@ +import { http, cookieStorage, createConfig, createStorage } from '@wagmi/vue' +import { mainnet, optimism, sepolia } from '@wagmi/vue/chains' +import { injected, metaMask, walletConnect } from '@wagmi/vue/connectors' + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + injected(), + walletConnect({ + projectId: process.env.NUXT_PUBLIC_WC_PROJECT_ID!, + }), + metaMask(), + ], + storage: createStorage({ + storage: cookieStorage, + }), + ssr: true, + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + }, +}) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/playgrounds/vite-core/.gitignore b/playgrounds/vite-core/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/playgrounds/vite-core/.gitignore @@ -0,0 +1,24 @@ +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/playgrounds/vite-core/index.html b/playgrounds/vite-core/index.html new file mode 100644 index 0000000000..0e8cfb2692 --- /dev/null +++ b/playgrounds/vite-core/index.html @@ -0,0 +1,12 @@ + + + + + + Vite Core + + +
+ + + diff --git a/playgrounds/vite-core/package.json b/playgrounds/vite-core/package.json new file mode 100644 index 0000000000..c420b46e8e --- /dev/null +++ b/playgrounds/vite-core/package.json @@ -0,0 +1,24 @@ +{ + "name": "vite-core", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@wagmi/connectors": "workspace:*", + "@wagmi/core": "workspace:*", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "viem": "2.*" + }, + "devDependencies": { + "@types/react": ">=18.3.1", + "@types/react-dom": ">=18.3.0", + "@vitejs/plugin-react": "^4.2.1", + "buffer": "^6.0.3", + "vite": "^7.3.0" + } +} diff --git a/playgrounds/vite-core/src/App.tsx b/playgrounds/vite-core/src/App.tsx new file mode 100644 index 0000000000..3c8646be39 --- /dev/null +++ b/playgrounds/vite-core/src/App.tsx @@ -0,0 +1,186 @@ +import { + type GetBalanceReturnType, + type GetBlockNumberReturnType, + connect, + disconnect, + getAccount, + getBalance, + getBlockNumber, + reconnect, + switchAccount, + watchAccount, + watchBlockNumber, +} from '@wagmi/core' +import { useEffect, useReducer, useState } from 'react' + +import { formatEther } from 'viem' +import { config } from './wagmi' + +function App() { + useEffect(() => { + reconnect(config) + }, []) + + return ( + <> + + + + + + + ) +} + +function Account() { + const [account, setAccount] = useState(getAccount(config)) + + useEffect(() => { + return watchAccount(config, { + onChange(data) { + setAccount(data) + }, + }) + }, []) + + return ( +
+

Account

+ +
+ account: {account.address} +
+ chainId: {account.chainId} +
+ status: {account.status} +
+ + {account.status === 'connected' && ( + + )} +
+ ) +} + +function Connect() { + const [, rerender] = useReducer((count) => count + 1, 0) + + useEffect(() => { + return config.subscribe(({ connections }) => connections, rerender) + }, []) + + return ( +
+

Connect

+ + {config.connectors.map((connector) => ( + + ))} +
+ ) +} + +function SwitchAccount() { + const [, rerender] = useReducer((count) => count + 1, 0) + + useEffect(() => { + return config.subscribe( + ({ connections, current }) => ({ connections, current }), + rerender, + ) + }, []) + + return ( +
+

SwitchAccount

+ + {config.connectors + .filter((connector) => config.state.connections.has(connector.uid)) + .map((connector) => ( + + ))} +
+ ) +} + +function Balance() { + const [account, setAccount] = useState(getAccount(config)) + + useEffect(() => { + return watchAccount(config, { + onChange(data) { + setAccount(data) + }, + }) + }, []) + + ///////////////////////////////////////////////////////// + + const [balance, setBalance] = useState() + + useEffect(() => { + if (!account.address) return + return watchBlockNumber(config, { + async onBlockNumber() { + try { + const balance = await getBalance(config, { + address: account.address!, + }) + setBalance(balance) + } catch (error) { + console.error('Error fetching balance', error) + } + }, + }) + }, [account.address]) + + return ( +
+

Balance

+ +
Balance: {!!balance?.value && formatEther(balance.value)}
+
+ ) +} + +function BlockNumber() { + const [blockNumber, setBlockNumber] = useState< + GetBlockNumberReturnType | undefined + >() + + useEffect(() => { + ;(async () => { + setBlockNumber(await getBlockNumber(config)) + + watchBlockNumber(config, { onBlockNumber: setBlockNumber }) + })() + }, []) + + return ( +
+

Block Number

+ +
Block Number (Default Chain): {blockNumber?.toString()}
+
+ ) +} + +export default App diff --git a/playgrounds/vite-core/src/index.css b/playgrounds/vite-core/src/index.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/playgrounds/vite-core/src/index.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/playgrounds/vite-core/src/main.tsx b/playgrounds/vite-core/src/main.tsx new file mode 100644 index 0000000000..180d9e1fc0 --- /dev/null +++ b/playgrounds/vite-core/src/main.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' + +import { Buffer } from 'buffer' + +// `@coinbase-wallet/sdk` uses `Buffer` +globalThis.Buffer = Buffer + +import App from './App.tsx' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , +) diff --git a/playgrounds/vite-core/src/vite-env.d.ts b/playgrounds/vite-core/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/playgrounds/vite-core/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/playgrounds/vite-core/src/wagmi.ts b/playgrounds/vite-core/src/wagmi.ts new file mode 100644 index 0000000000..d6152f9d24 --- /dev/null +++ b/playgrounds/vite-core/src/wagmi.ts @@ -0,0 +1,22 @@ +import { coinbaseWallet, metaMask, walletConnect } from '@wagmi/connectors' +import { http, createConfig, createStorage } from '@wagmi/core' +import { mainnet, optimism, sepolia } from '@wagmi/core/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }), + coinbaseWallet(), + metaMask(), + ], + storage: createStorage({ storage: localStorage, key: 'vite-core' }), + transports: { + [mainnet.id]: http( + 'https://eth-mainnet.g.alchemy.com/v2/StF61Ht3J9nXAojZX-b21LVt9l0qDL38', + ), + [sepolia.id]: http( + 'https://eth-sepolia.g.alchemy.com/v2/roJyEHxkj7XWg1T9wmYnxvktDodQrFAS', + ), + [optimism.id]: http(), + }, +}) diff --git a/playgrounds/vite-core/tsconfig.json b/playgrounds/vite-core/tsconfig.json new file mode 100644 index 0000000000..36d04ef376 --- /dev/null +++ b/playgrounds/vite-core/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "@wagmi/connectors": ["../../packages/connectors/src/exports"], + "@wagmi/core": ["../../packages/core/src/exports"], + "@wagmi/core/*": ["../../packages/core/src/exports/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/playgrounds/vite-core/tsconfig.node.json b/playgrounds/vite-core/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/playgrounds/vite-core/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/playgrounds/vite-core/vite.config.ts b/playgrounds/vite-core/vite.config.ts new file mode 100644 index 0000000000..36f7f4e1bc --- /dev/null +++ b/playgrounds/vite-core/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/playgrounds/vite-react/.gitignore b/playgrounds/vite-react/.gitignore new file mode 100644 index 0000000000..1b226cc615 --- /dev/null +++ b/playgrounds/vite-react/.gitignore @@ -0,0 +1,26 @@ +src/generated.ts + +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/playgrounds/vite-react/index.html b/playgrounds/vite-react/index.html new file mode 100644 index 0000000000..c29681356c --- /dev/null +++ b/playgrounds/vite-react/index.html @@ -0,0 +1,12 @@ + + + + + + Vite React + + +
+ + + diff --git a/playgrounds/vite-react/package.json b/playgrounds/vite-react/package.json new file mode 100644 index 0000000000..b4cbd89fd4 --- /dev/null +++ b/playgrounds/vite-react/package.json @@ -0,0 +1,29 @@ +{ + "name": "vite-react", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/query-sync-storage-persister": "5.0.5", + "@tanstack/react-query": ">=5.45.1", + "@tanstack/react-query-persist-client": "5.0.5", + "idb-keyval": "^6.2.1", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "viem": "2.*", + "wagmi": "workspace:*" + }, + "devDependencies": { + "@tanstack/react-query-devtools": "5.0.5", + "@types/react": ">=18.3.1", + "@types/react-dom": ">=18.3.0", + "@vitejs/plugin-react": "^4.2.1", + "@wagmi/cli": "workspace:*", + "buffer": "^6.0.3", + "vite": "^7.3.0" + } +} diff --git a/playgrounds/vite-react/public/manifest.json b/playgrounds/vite-react/public/manifest.json new file mode 100644 index 0000000000..c5d4517110 --- /dev/null +++ b/playgrounds/vite-react/public/manifest.json @@ -0,0 +1,5 @@ +{ + "name": "vite-react", + "description": "vite-react playground", + "iconPath": "favicon.ico" +} diff --git a/playgrounds/vite-react/src/App.tsx b/playgrounds/vite-react/src/App.tsx new file mode 100644 index 0000000000..ad2e36b06f --- /dev/null +++ b/playgrounds/vite-react/src/App.tsx @@ -0,0 +1,389 @@ +import type { FormEvent } from 'react' +import { type Hex, formatEther, parseAbi, parseEther } from 'viem' +import { + type BaseError, + useAccount, + useAccountEffect, + useBalance, + useBlockNumber, + useChainId, + useConnect, + useConnections, + useConnectorClient, + useDisconnect, + useEnsName, + useReadContract, + useReadContracts, + useSendTransaction, + useSignMessage, + useSwitchAccount, + useSwitchChain, + useWaitForTransactionReceipt, + useWriteContract, +} from 'wagmi' +import { optimism } from 'wagmi/chains' + +import { wagmiContractConfig } from './contracts' + +function App() { + useAccountEffect({ + onConnect(_data) { + // console.log('onConnect', data) + }, + onDisconnect() { + // console.log('onDisconnect') + }, + }) + + return ( + <> + + + + + + + + + + + + + + + ) +} + +function Account() { + const account = useAccount() + const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ + address: account.address, + }) + + return ( +
+

Account

+ +
+ account: {account.address} {ensName} +
+ chainId: {account.chainId} +
+ status: {account.status} +
+ + {account.status !== 'disconnected' && ( + + )} +
+ ) +} + +function Connect() { + const chainId = useChainId() + const { connectors, connect, status, error } = useConnect() + + return ( +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ ) +} + +function SwitchAccount() { + const account = useAccount() + const { connectors, switchAccount } = useSwitchAccount() + + return ( +
+

Switch Account

+ + {connectors.map((connector) => ( + + ))} +
+ ) +} + +function SwitchChain() { + const chainId = useChainId() + const { chains, switchChain, error } = useSwitchChain() + + return ( +
+

Switch Chain

+ + {chains.map((chain) => ( + + ))} + + {error?.message} +
+ ) +} + +function SignMessage() { + const { data, signMessage } = useSignMessage() + + return ( +
+

Sign Message

+ +
{ + event.preventDefault() + const formData = new FormData(event.target as HTMLFormElement) + signMessage({ message: formData.get('message') as string }) + }} + > + + +
+ + {data} +
+ ) +} + +function Connections() { + const connections = useConnections() + + return ( +
+

Connections

+ + {connections.map((connection) => ( +
+
connector {connection.connector.name}
+
accounts: {JSON.stringify(connection.accounts)}
+
chainId: {connection.chainId}
+
+ ))} +
+ ) +} + +function Balance() { + const { address } = useAccount() + + const { data: default_ } = useBalance({ address }) + const { data: account_ } = useBalance({ address }) + const { data: optimism_ } = useBalance({ + address, + chainId: optimism.id, + }) + + return ( +
+

Balance

+ +
+ Balance (Default Chain):{' '} + {!!default_?.value && formatEther(default_.value)} +
+
+ Balance (Account Chain):{' '} + {!!account_?.value && formatEther(account_.value)} +
+
+ Balance (Optimism Chain):{' '} + {!!optimism_?.value && formatEther(optimism_.value)} +
+
+ ) +} + +function BlockNumber() { + const { data: default_ } = useBlockNumber({ watch: true }) + const { data: account_ } = useBlockNumber({ + watch: true, + }) + const { data: optimism_ } = useBlockNumber({ + chainId: optimism.id, + watch: true, + }) + + return ( +
+

Block Number

+ +
Block Number (Default Chain): {default_?.toString()}
+
Block Number (Account Chain): {account_?.toString()}
+
Block Number (Optimism): {optimism_?.toString()}
+
+ ) +} + +function ConnectorClient() { + const { data, error } = useConnectorClient() + return ( +
+

Connector Client

+ client {data?.account?.address} {data?.chain?.id} + {error?.message} +
+ ) +} + +function SendTransaction() { + const { data: hash, error, isPending, sendTransaction } = useSendTransaction() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as Hex + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Send Transaction

+
+ + + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +function ReadContract() { + const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
+ ) +} + +function ReadContracts() { + const { data } = useReadContracts({ + allowFailure: false, + contracts: [ + { + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }, + { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69n], + }, + { + ...wagmiContractConfig, + functionName: 'totalSupply', + }, + ], + }) + const [balance, ownerOf, totalSupply] = data || [] + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
Owner of Token 69: {ownerOf?.toString()}
+
Total Supply: {totalSupply?.toString()}
+
+ ) +} + +function WriteContract() { + const { data: hash, error, isPending, writeContract } = useWriteContract() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: parseAbi(['function mint(uint256 tokenId)']), + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Write Contract

+
+ + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +export default App diff --git a/playgrounds/vite-react/src/contracts.ts b/playgrounds/vite-react/src/contracts.ts new file mode 100644 index 0000000000..d7d66754a2 --- /dev/null +++ b/playgrounds/vite-react/src/contracts.ts @@ -0,0 +1,202 @@ +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'approved', + type: 'address', + }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'operator', + type: 'address', + }, + { + indexed: false, + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { indexed: true, name: 'to', type: 'address' }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'operator', type: 'address' }, + { name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/playgrounds/vite-react/src/index.css b/playgrounds/vite-react/src/index.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/playgrounds/vite-react/src/index.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/playgrounds/vite-react/src/main.tsx b/playgrounds/vite-react/src/main.tsx new file mode 100644 index 0000000000..ad801a958d --- /dev/null +++ b/playgrounds/vite-react/src/main.tsx @@ -0,0 +1,49 @@ +import { Buffer } from 'buffer' +import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' +import { QueryClient } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' +import React from 'react' +import ReactDOM from 'react-dom/client' +import { WagmiProvider, deserialize, serialize } from 'wagmi' + +import './index.css' + +// `@coinbase-wallet/sdk` uses `Buffer` +globalThis.Buffer = Buffer + +import App from './App.tsx' +import { config } from './wagmi.ts' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1_000 * 60 * 60 * 24, // 24 hours + networkMode: 'offlineFirst', + refetchOnWindowFocus: false, + retry: 0, + }, + mutations: { networkMode: 'offlineFirst' }, + }, +}) + +const persister = createSyncStoragePersister({ + key: 'vite-react.cache', + serialize, + storage: window.localStorage, + deserialize, +}) + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + + + + , +) diff --git a/playgrounds/vite-react/src/vite-env.d.ts b/playgrounds/vite-react/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/playgrounds/vite-react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/playgrounds/vite-react/src/wagmi.ts b/playgrounds/vite-react/src/wagmi.ts new file mode 100644 index 0000000000..94b86f6fd7 --- /dev/null +++ b/playgrounds/vite-react/src/wagmi.ts @@ -0,0 +1,40 @@ +import { del, get, set } from 'idb-keyval' +import { http, createConfig } from 'wagmi' +import { celo, mainnet, optimism, sepolia } from 'wagmi/chains' +import { coinbaseWallet, metaMask, walletConnect } from 'wagmi/connectors' + +// biome-ignore lint/correctness/noUnusedVariables: +const indexedDBStorage = { + async getItem(name: string) { + return get(name) + }, + async setItem(name: string, value: string) { + await set(name, value) + }, + async removeItem(name: string) { + await del(name) + }, +} + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism, celo], + connectors: [ + walletConnect({ + projectId: import.meta.env.VITE_WC_PROJECT_ID, + }), + coinbaseWallet(), + metaMask(), + ], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + [celo.id]: http(), + }, +}) + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} diff --git a/playgrounds/vite-react/tsconfig.json b/playgrounds/vite-react/tsconfig.json new file mode 100644 index 0000000000..2a91e18f44 --- /dev/null +++ b/playgrounds/vite-react/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "@wagmi/connectors": ["../../packages/connectors/src/exports"], + "@wagmi/core": ["../../packages/core/src/exports"], + "@wagmi/core/*": ["../../packages/core/src/exports/*"], + "wagmi": ["../../packages/react/src/exports"], + "wagmi/*": ["../../packages/react/src/exports/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/playgrounds/vite-react/tsconfig.node.json b/playgrounds/vite-react/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/playgrounds/vite-react/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/playgrounds/vite-react/vite.config.ts b/playgrounds/vite-react/vite.config.ts new file mode 100644 index 0000000000..36f7f4e1bc --- /dev/null +++ b/playgrounds/vite-react/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/playgrounds/vite-react/wagmi.config.ts b/playgrounds/vite-react/wagmi.config.ts new file mode 100644 index 0000000000..d524220b09 --- /dev/null +++ b/playgrounds/vite-react/wagmi.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from '@wagmi/cli' +import { foundry, hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + out: 'src/generated.ts', + contracts: [], + plugins: [ + foundry({ + namePrefix: 'foundry', + project: '../../packages/cli/src/plugins/__fixtures__/foundry', + }), + hardhat({ + namePrefix: 'hardhat', + project: '../../packages/cli/src/plugins/__fixtures__/hardhat', + }), + ], +}) diff --git a/playgrounds/vite-vue/.gitignore b/playgrounds/vite-vue/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/playgrounds/vite-vue/.gitignore @@ -0,0 +1,24 @@ +# 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 +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/playgrounds/vite-vue/index.html b/playgrounds/vite-vue/index.html new file mode 100644 index 0000000000..3f59f59114 --- /dev/null +++ b/playgrounds/vite-vue/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite Vue + + +
+ + + diff --git a/playgrounds/vite-vue/package.json b/playgrounds/vite-vue/package.json new file mode 100644 index 0000000000..61b1d81a01 --- /dev/null +++ b/playgrounds/vite-vue/package.json @@ -0,0 +1,22 @@ +{ + "name": "vite-vue", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/vue-query": ">=5.45.0", + "@wagmi/vue": "workspace:*", + "viem": "2.*", + "vue": ">=3.4.21" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "buffer": "^6.0.3", + "vite": "^7.3.0", + "vue-tsc": "^2.0.6" + } +} diff --git a/playgrounds/vite-vue/public/vite.svg b/playgrounds/vite-vue/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/playgrounds/vite-vue/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/playgrounds/vite-vue/src/App.vue b/playgrounds/vite-vue/src/App.vue new file mode 100644 index 0000000000..5a7f194a02 --- /dev/null +++ b/playgrounds/vite-vue/src/App.vue @@ -0,0 +1,29 @@ + + + diff --git a/playgrounds/vite-vue/src/components/Account.vue b/playgrounds/vite-vue/src/components/Account.vue new file mode 100644 index 0000000000..33f1491dac --- /dev/null +++ b/playgrounds/vite-vue/src/components/Account.vue @@ -0,0 +1,22 @@ + + + diff --git a/playgrounds/vite-vue/src/components/Balance.vue b/playgrounds/vite-vue/src/components/Balance.vue new file mode 100644 index 0000000000..b064d45e9a --- /dev/null +++ b/playgrounds/vite-vue/src/components/Balance.vue @@ -0,0 +1,12 @@ + + + diff --git a/playgrounds/vite-vue/src/components/BlockNumber.vue b/playgrounds/vite-vue/src/components/BlockNumber.vue new file mode 100644 index 0000000000..25f8744a71 --- /dev/null +++ b/playgrounds/vite-vue/src/components/BlockNumber.vue @@ -0,0 +1,17 @@ + + + diff --git a/playgrounds/vite-vue/src/components/Client.vue b/playgrounds/vite-vue/src/components/Client.vue new file mode 100644 index 0000000000..52e7f12f7b --- /dev/null +++ b/playgrounds/vite-vue/src/components/Client.vue @@ -0,0 +1,14 @@ + + + diff --git a/playgrounds/vite-vue/src/components/Connect.vue b/playgrounds/vite-vue/src/components/Connect.vue new file mode 100644 index 0000000000..93320448c0 --- /dev/null +++ b/playgrounds/vite-vue/src/components/Connect.vue @@ -0,0 +1,19 @@ + + + diff --git a/playgrounds/vite-vue/src/components/Connections.vue b/playgrounds/vite-vue/src/components/Connections.vue new file mode 100644 index 0000000000..21405f2483 --- /dev/null +++ b/playgrounds/vite-vue/src/components/Connections.vue @@ -0,0 +1,15 @@ + + + diff --git a/playgrounds/vite-vue/src/components/ConnectorClient.vue b/playgrounds/vite-vue/src/components/ConnectorClient.vue new file mode 100644 index 0000000000..157156d3ee --- /dev/null +++ b/playgrounds/vite-vue/src/components/ConnectorClient.vue @@ -0,0 +1,14 @@ + + + diff --git a/playgrounds/vite-vue/src/components/ReadContract.vue b/playgrounds/vite-vue/src/components/ReadContract.vue new file mode 100644 index 0000000000..2f236ac9ed --- /dev/null +++ b/playgrounds/vite-vue/src/components/ReadContract.vue @@ -0,0 +1,16 @@ + + + diff --git a/playgrounds/vite-vue/src/components/SendTransaction.vue b/playgrounds/vite-vue/src/components/SendTransaction.vue new file mode 100644 index 0000000000..8a509b5e5f --- /dev/null +++ b/playgrounds/vite-vue/src/components/SendTransaction.vue @@ -0,0 +1,35 @@ + + + diff --git a/playgrounds/vite-vue/src/components/SwitchAccount.vue b/playgrounds/vite-vue/src/components/SwitchAccount.vue new file mode 100644 index 0000000000..bc1814bfa7 --- /dev/null +++ b/playgrounds/vite-vue/src/components/SwitchAccount.vue @@ -0,0 +1,20 @@ + + + diff --git a/playgrounds/vite-vue/src/components/SwitchChain.vue b/playgrounds/vite-vue/src/components/SwitchChain.vue new file mode 100644 index 0000000000..b385052049 --- /dev/null +++ b/playgrounds/vite-vue/src/components/SwitchChain.vue @@ -0,0 +1,22 @@ + + + diff --git a/playgrounds/vite-vue/src/components/WriteContract.vue b/playgrounds/vite-vue/src/components/WriteContract.vue new file mode 100644 index 0000000000..aa3102b3f1 --- /dev/null +++ b/playgrounds/vite-vue/src/components/WriteContract.vue @@ -0,0 +1,28 @@ + + + diff --git a/playgrounds/vite-vue/src/contracts.ts b/playgrounds/vite-vue/src/contracts.ts new file mode 100644 index 0000000000..d7d66754a2 --- /dev/null +++ b/playgrounds/vite-vue/src/contracts.ts @@ -0,0 +1,202 @@ +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'approved', + type: 'address', + }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'operator', + type: 'address', + }, + { + indexed: false, + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { indexed: true, name: 'to', type: 'address' }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'operator', type: 'address' }, + { name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/playgrounds/vite-vue/src/main.ts b/playgrounds/vite-vue/src/main.ts new file mode 100644 index 0000000000..820eed3722 --- /dev/null +++ b/playgrounds/vite-vue/src/main.ts @@ -0,0 +1,17 @@ +import { Buffer } from 'buffer' +import { VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { createApp } from 'vue' + +// `@coinbase-wallet/sdk` uses `Buffer` +globalThis.Buffer = Buffer + +import App from './App.vue' +import './style.css' +import { config } from './wagmi' + +const app = createApp(App) + +app.use(WagmiPlugin, { config }).use(VueQueryPlugin, {}) + +app.mount('#app') diff --git a/playgrounds/vite-vue/src/style.css b/playgrounds/vite-vue/src/style.css new file mode 100644 index 0000000000..0733a7ee6b --- /dev/null +++ b/playgrounds/vite-vue/src/style.css @@ -0,0 +1,21 @@ +:root { + background-color: #181818; + color: rgba(255, 255, 255, 0.87); + color-scheme: light dark; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-synthesis: none; + font-weight: 400; + line-height: 1.5; + text-rendering: optimizeLegibility; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-color-scheme: light) { + :root { + background-color: #f8f8f8; + color: #181818; + } +} diff --git a/playgrounds/vite-vue/src/vite-env.d.ts b/playgrounds/vite-vue/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/playgrounds/vite-vue/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/playgrounds/vite-vue/src/wagmi.ts b/playgrounds/vite-vue/src/wagmi.ts new file mode 100644 index 0000000000..f5b3f286cc --- /dev/null +++ b/playgrounds/vite-vue/src/wagmi.ts @@ -0,0 +1,26 @@ +import { http, createConfig, createStorage } from '@wagmi/vue' +import { mainnet, optimism, sepolia } from '@wagmi/vue/chains' +import { coinbaseWallet, metaMask, walletConnect } from '@wagmi/vue/connectors' + +export const config = createConfig({ + chains: [mainnet, sepolia, optimism], + connectors: [ + walletConnect({ + projectId: import.meta.env.VITE_WC_PROJECT_ID, + }), + coinbaseWallet({ appName: 'Vite Vue Playground', darkMode: true }), + metaMask(), + ], + storage: createStorage({ storage: localStorage, key: 'vite-vue' }), + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + [optimism.id]: http(), + }, +}) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/playgrounds/vite-vue/tsconfig.json b/playgrounds/vite-vue/tsconfig.json new file mode 100644 index 0000000000..18f095cd22 --- /dev/null +++ b/playgrounds/vite-vue/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/playgrounds/vite-vue/tsconfig.node.json b/playgrounds/vite-vue/tsconfig.node.json new file mode 100644 index 0000000000..97ede7ee6f --- /dev/null +++ b/playgrounds/vite-vue/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/playgrounds/vite-vue/vite.config.ts b/playgrounds/vite-vue/vite.config.ts new file mode 100644 index 0000000000..c3fa62d865 --- /dev/null +++ b/playgrounds/vite-vue/vite.config.ts @@ -0,0 +1,7 @@ +import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54ad63e327..f486582696 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7401 +1,24197 @@ lockfileVersion: '9.0' settings: - autoInstallPeers: true + autoInstallPeers: false excludeLinksFromLockfile: false -overrides: - ox: ^0.9.17 +catalogs: + default: + '@tanstack/query-core': + specifier: 5.49.1 + version: 5.49.1 + '@tanstack/react-query': + specifier: 5.49.2 + version: 5.49.2 + '@tanstack/vue-query': + specifier: 5.49.1 + version: 5.49.1 + '@testing-library/dom': + specifier: 10.4.0 + version: 10.4.0 + '@testing-library/react': + specifier: 16.0.1 + version: 16.0.1 + '@types/react': + specifier: 18.3.1 + version: 18.3.1 + '@types/react-dom': + specifier: 18.3.0 + version: 18.3.0 + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1 + vue: + specifier: 3.4.27 + version: 3.4.27 importers: .: devDependencies: + '@arethetypeswrong/cli': + specifier: ^0.16.4 + version: 0.16.4 + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@changesets/changelog-github': + specifier: 0.4.6 + version: 0.4.6(encoding@0.1.13) '@changesets/cli': - specifier: ^2.29.8 - version: 2.29.8(@types/node@25.3.0) - lefthook: - specifier: ^2.1.1 - version: 2.1.1 - prettier: - specifier: ^3.8.1 - version: 3.8.1 - rimraf: - specifier: ^6.1.3 - version: 6.1.3 - syncpack: - specifier: ^14.0.0 - version: 14.0.0 - turbo: - specifier: ^2.8.10 - version: 2.8.10 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - extras/docs: - dependencies: - '@repo/ui': - specifier: workspace:^ - version: link:../../repo/ui - next: - specifier: ^15.5.14 - version: 15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: - specifier: ^19.2.3 - version: 19.2.3 - react-dom: - specifier: ^19.2.3 - version: 19.2.3(react@19.2.3) - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@types/react': - specifier: ^19.2.7 - version: 19.2.7 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.7) - eslint: - specifier: ^9.39.2 - version: 9.39.2 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - extras/web: - dependencies: - '@repo/ui': - specifier: workspace:^ - version: link:../../repo/ui - next: - specifier: ^15.5.14 - version: 15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: - specifier: ^19.2.3 - version: 19.2.3 - react-dom: - specifier: ^19.2.3 - version: 19.2.3(react@19.2.3) - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@types/react': - specifier: ^19.2.7 - version: 19.2.7 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.7) - eslint: - specifier: ^9.39.2 - version: 9.39.2 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/services/api: - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/services/builder: - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/services/guard: - dependencies: - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - packages/services/identity-instrument: - dependencies: - json-canonicalize: - specifier: ^2.0.0 - version: 2.0.0 - jwt-decode: - specifier: ^4.0.0 - version: 4.0.0 - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 + specifier: ^2.27.8 + version: 2.27.8 + '@types/bun': + specifier: ^1.1.10 + version: 1.1.10 + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@2.1.9(@edge-runtime/vm@3.2.0)(@types/node@24.0.1)(happy-dom@20.0.2)(lightningcss@1.30.2)(msw@2.4.9(typescript@5.8.3))(terser@5.44.1)) + '@wagmi/test': + specifier: workspace:* + version: link:packages/test + bun: + specifier: ^1.1.30 + version: 1.1.30 + happy-dom: + specifier: ^20.0.2 + version: 20.0.2 + knip: + specifier: ^5.30.6 + version: 5.30.6(@types/node@24.0.1)(typescript@5.8.3) + prool: + specifier: ^0.0.23 + version: 0.0.23 + publint: + specifier: ^0.3.0 + version: 0.3.12 + sherif: + specifier: ^1.0.0 + version: 1.0.0 + simple-git-hooks: + specifier: ^2.11.1 + version: 2.11.1 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: 5.8.3 + version: 5.8.3 + viem: + specifier: 2.31.4 + version: 2.31.4(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - packages/services/indexer: + specifier: ^2.1.9 + version: 2.1.9(@edge-runtime/vm@3.2.0)(@types/node@24.0.1)(happy-dom@20.0.2)(lightningcss@1.30.2)(msw@2.4.9(typescript@5.8.3))(terser@5.44.1) + + packages/cli: + dependencies: + abitype: + specifier: ^1.0.4 + version: 1.0.4(typescript@5.9.3)(zod@3.25.20) + bundle-require: + specifier: ^5.1.0 + version: 5.1.0(esbuild@0.27.2) + cac: + specifier: ^6.7.14 + version: 6.7.14 + change-case: + specifier: ^5.4.4 + version: 5.4.4 + chokidar: + specifier: 4.0.3 + version: 4.0.3 + dedent: + specifier: ^1.5.3 + version: 1.6.0 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + dotenv-expand: + specifier: ^10.0.0 + version: 10.0.0 + esbuild: + specifier: ~0.27.0 + version: 0.27.2 + escalade: + specifier: 3.2.0 + version: 3.2.0 + fdir: + specifier: ^6.1.1 + version: 6.1.1(picomatch@4.0.2) + nanospinner: + specifier: 1.2.2 + version: 1.2.2 + pathe: + specifier: ^2.0.3 + version: 2.0.3 + picocolors: + specifier: ^1.0.0 + version: 1.0.0 + picomatch: + specifier: ^4.0.2 + version: 4.0.2 + prettier: + specifier: ^3.0.3 + version: 3.0.3 + viem: + specifier: 2.x + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + zod: + specifier: ^3.22.3 + version: 3.25.20 devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config + '@types/dedent': + specifier: ^0.7.2 + version: 0.7.2 '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^22.14.0 + version: 22.15.31 + fixturez: + specifier: ^1.1.0 + version: 1.1.0 + msw: + specifier: ^2.4.9 + version: 2.4.9(typescript@5.9.3) - packages/services/marketplace: + packages/cli/src/plugins/__fixtures__/hardhat: devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/services/metadata: + hardhat: + specifier: ^2.22.3 + version: 2.22.3(bufferutil@4.1.0)(ts-node@10.9.1(@types/node@24.0.1)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + + packages/connectors: + dependencies: + '@coinbase/wallet-sdk': + specifier: 4.3.0 + version: 4.3.0 + '@metamask/sdk': + specifier: 0.33.1 + version: 0.33.1(bufferutil@4.1.0)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@safe-global/safe-apps-provider': + specifier: 0.18.6 + version: 0.18.6(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@safe-global/safe-apps-sdk': + specifier: 9.1.0 + version: 9.1.0(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/ethereum-provider': + specifier: 2.20.2 + version: 2.20.2(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + cbw-sdk: + specifier: npm:@coinbase/wallet-sdk@3.9.3 + version: '@coinbase/wallet-sdk@3.9.3' devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 + '@wagmi/core': + specifier: workspace:* + version: link:../core + msw: + specifier: ^2.4.9 + version: 2.4.9(typescript@5.9.3) - packages/services/relayer: + packages/core: dependencies: - '@0xsequence/wallet-primitives': - specifier: workspace:^ - version: link:../../wallet/primitives + eventemitter3: + specifier: 5.0.1 + version: 5.0.1 mipd: - specifier: ^0.0.7 + specifier: 0.0.7 version: 0.0.7(typescript@5.9.3) - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - viem: - specifier: ^2.40.3 - version: 2.42.1(typescript@5.9.3)(zod@4.2.0) + zustand: + specifier: 5.0.0 + version: 5.0.0(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - packages/services/userdata: + '@tanstack/query-core': + specifier: 'catalog:' + version: 5.49.1 + + packages/create-wagmi: + dependencies: + cac: + specifier: ^6.7.14 + version: 6.7.14 + cross-spawn: + specifier: ^7.0.5 + version: 7.0.6 + picocolors: + specifier: ^1.0.0 + version: 1.0.0 + prompts: + specifier: ^2.4.2 + version: 2.4.2 devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config + '@types/cross-spawn': + specifier: ^6.0.6 + version: 6.0.6 '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/utils/abi: + specifier: ^20.12.10 + version: 20.12.10 + '@types/prompts': + specifier: ^2.4.9 + version: 2.4.9 + + packages/react: + dependencies: + '@wagmi/connectors': + specifier: workspace:* + version: link:../connectors + '@wagmi/core': + specifier: workspace:* + version: link:../core + use-sync-external-store: + specifier: 1.4.0 + version: 1.4.0(react@18.3.1) devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 + '@tanstack/react-query': + specifier: 'catalog:' + version: 5.49.2(react@18.3.1) + '@testing-library/dom': + specifier: 'catalog:' + version: 10.4.0 + '@testing-library/react': + specifier: 'catalog:' + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: 'catalog:' + version: 18.3.1 + '@types/react-dom': + specifier: 'catalog:' + version: 18.3.0 + '@types/use-sync-external-store': + specifier: ^0.0.6 + version: 0.0.6 + react: + specifier: 'catalog:' + version: 18.3.1 + react-dom: + specifier: 'catalog:' + version: 18.3.1(react@18.3.1) - packages/wallet/core: + packages/register-tests/react: dependencies: - '@0xsequence/guard': - specifier: workspace:^ - version: link:../../services/guard - '@0xsequence/relayer': - specifier: workspace:^ - version: link:../../services/relayer - '@0xsequence/wallet-primitives': - specifier: workspace:^ - version: link:../primitives - mipd: - specifier: ^0.0.7 - version: 0.0.7(typescript@5.9.3) - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - viem: - specifier: ^2.40.3 - version: 2.42.1(typescript@5.9.3)(zod@4.2.0) + '@tanstack/react-query': + specifier: 'catalog:' + version: 5.49.2(react@18.3.1) + react: + specifier: 'catalog:' + version: 18.3.1 + wagmi: + specifier: workspace:* + version: link:../../react devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@vitest/coverage-v8': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) - dotenv: - specifier: ^17.3.1 - version: 17.3.1 - fake-indexeddb: - specifier: ^6.2.5 - version: 6.2.5 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - packages/wallet/dapp-client: - dependencies: - '@0xsequence/guard': - specifier: workspace:^ - version: link:../../services/guard - '@0xsequence/relayer': - specifier: workspace:^ - version: link:../../services/relayer - '@0xsequence/wallet-core': - specifier: workspace:^ - version: link:../core + '@types/react': + specifier: 'catalog:' + version: 18.3.1 + + packages/register-tests/vue: + dependencies: + '@tanstack/vue-query': + specifier: 'catalog:' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@wagmi/vue': + specifier: workspace:* + version: link:../../vue + vue: + specifier: 'catalog:' + version: 3.4.27(typescript@5.9.3) + + packages/sequence-core-1.0.0: + dependencies: + 0xsequence: + specifier: ^2.3.33 + version: 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/connect': + specifier: 0.0.0-20250924112110 + version: 0.0.0-20250924112110(@types/react-dom@18.3.0)(@types/react@18.3.27)(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(utf-8-validate@6.0.5)(viem@2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20))(wagmi@packages+react)(zod@3.25.20) + '@0xsequence/waas': + specifier: ^2.3.33 + version: 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': + specifier: ~2.3.33 + version: 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet-contracts': + specifier: ~3.0.1 + version: 3.0.1(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5) '@0xsequence/wallet-primitives': - specifier: workspace:^ - version: link:../primitives - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) + specifier: 0.0.0-20250915145821 + version: 0.0.0-20250915145821(typescript@5.8.3)(zod@3.25.20) + '@vercel/analytics': + specifier: ^1.5.0 + version: 1.6.1(next@15.4.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vue-router@4.6.4(vue@3.5.26(typescript@5.8.3)))(vue@3.5.26(typescript@5.8.3)) + ethers: + specifier: ^6.15.0 + version: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + flag: + specifier: ^5.0.1 + version: 5.0.1(react@18.3.1) + prettier-plugin-solidity: + specifier: ^2.2.0 + version: 2.2.1(prettier@3.7.4) + vercel: + specifier: ^48.10.2 + version: 48.12.1(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3) devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@vitest/coverage-v8': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) - dotenv: - specifier: ^17.3.1 - version: 17.3.1 - fake-indexeddb: - specifier: ^6.2.5 - version: 6.2.5 - happy-dom: - specifier: ^20.8.9 - version: 20.8.9 + '@changesets/cli': + specifier: ^2.29.7 + version: 2.29.8(@types/node@24.0.1) + lefthook: + specifier: ^1.13.6 + version: 1.13.6 + prettier: + specifier: ^3.6.2 + version: 3.7.4 + rimraf: + specifier: ^6.1.0 + version: 6.1.2 + turbo: + specifier: ^2.6.1 + version: 2.7.2 typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) + specifier: 5.8.3 + version: 5.8.3 - packages/wallet/primitives: - dependencies: - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) + packages/test: devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@vitest/coverage-v8': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - packages/wallet/primitives-cli: - dependencies: - '@0xsequence/wallet-primitives': - specifier: workspace:^ - version: link:../primitives - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - yargs: - specifier: ^18.0.0 - version: 18.0.0 + '@tanstack/react-query': + specifier: 'catalog:' + version: 5.49.2(react@18.3.1) + '@tanstack/vue-query': + specifier: 'catalog:' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@testing-library/dom': + specifier: 'catalog:' + version: 10.4.0 + '@testing-library/react': + specifier: 'catalog:' + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: 'catalog:' + version: 18.3.1 + '@types/react-dom': + specifier: 'catalog:' + version: 18.3.0 + '@wagmi/core': + specifier: workspace:* + version: link:../core + '@wagmi/vue': + specifier: workspace:* + version: link:../vue + react: + specifier: 'catalog:' + version: 18.3.1 + react-dom: + specifier: 'catalog:' + version: 18.3.1(react@18.3.1) + vue: + specifier: 'catalog:' + version: 3.4.27(typescript@5.9.3) + wagmi: + specifier: workspace:* + version: link:../react + + packages/vue: + dependencies: + '@wagmi/connectors': + specifier: workspace:* + version: link:../connectors + '@wagmi/core': + specifier: workspace:* + version: link:../core + devDependencies: + '@nuxt/schema': + specifier: ^3.11.2 + version: 3.11.2(rollup@4.54.0) + '@tanstack/vue-query': + specifier: 'catalog:' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@vue/test-utils': + specifier: ^2.4.6 + version: 2.4.6 + nuxt: + specifier: ^3.19.0 + version: 3.19.0(@biomejs/biome@1.9.4)(@parcel/watcher@2.5.1)(@types/node@24.0.1)(@vercel/blob@1.0.2)(@vue/compiler-sfc@3.5.26)(bufferutil@4.1.0)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.1)(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(yaml@2.8.2) + vue: + specifier: 'catalog:' + version: 3.4.27(typescript@5.9.3) + + playgrounds/next: + dependencies: + '@next/bundle-analyzer': + specifier: ^15.2.4 + version: 15.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.5) + '@tanstack/react-query': + specifier: '>=5.45.1' + version: 5.49.2(react@18.3.1) + next: + specifier: 15.4.10 + version: 15.4.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: '>=18.3.1' + version: 18.3.1 + react-dom: + specifier: '>=18.3.1' + version: 18.3.1(react@18.3.1) + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.0.8)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + wagmi: + specifier: workspace:* + version: link:../../packages/react devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@types/yargs': - specifier: ^17.0.35 - version: 17.0.35 - concurrently: - specifier: ^9.2.1 - version: 9.2.1 - esbuild: - specifier: ^0.27.3 - version: 0.27.3 - nodemon: - specifier: ^3.1.14 - version: 3.1.14 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - packages/wallet/wdk: - dependencies: - '@0xsequence/guard': - specifier: workspace:^ - version: link:../../services/guard - '@0xsequence/identity-instrument': - specifier: workspace:^ - version: link:../../services/identity-instrument - '@0xsequence/relayer': - specifier: workspace:^ - version: link:../../services/relayer - '@0xsequence/tee-verifier': - specifier: ^0.1.2 - version: 0.1.2 - '@0xsequence/wallet-core': - specifier: workspace:^ - version: link:../core - '@0xsequence/wallet-primitives': - specifier: workspace:^ - version: link:../primitives - idb: - specifier: ^8.0.3 - version: 8.0.3 - jwt-decode: - specifier: ^4.0.0 - version: 4.0.0 - ox: - specifier: ^0.9.17 - version: 0.9.17(typescript@5.9.3)(zod@4.2.0) - uuid: + specifier: ^22.14.1 + version: 22.15.31 + '@types/react': + specifier: '>=18.3.1' + version: 18.3.1 + '@types/react-dom': + specifier: '>=18.3.0' + version: 18.3.0 + bufferutil: + specifier: ^4.0.8 + version: 4.0.8 + encoding: + specifier: ^0.1.13 + version: 0.1.13 + lokijs: + specifier: ^1.5.12 + version: 1.5.12 + pino-pretty: specifier: ^13.0.0 version: 13.0.0 + supports-color: + specifier: ^9.4.0 + version: 9.4.0 + utf-8-validate: + specifier: ^6.0.5 + version: 6.0.5 + + playgrounds/nuxt: + dependencies: + '@tanstack/vue-query': + specifier: '>=5.45.0' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@wagmi/vue': + specifier: workspace:* + version: link:../../packages/vue + nuxt: + specifier: ^3.19.0 + version: 3.19.0(@biomejs/biome@1.9.4)(@parcel/watcher@2.5.1)(@types/node@24.0.1)(@vercel/blob@1.0.2)(@vue/compiler-sfc@3.5.26)(bufferutil@4.1.0)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.1)(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(yaml@2.8.2) + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + vue: + specifier: '>=3.4.21' + version: 3.4.27(typescript@5.9.3) + vue-router: + specifier: ^4.3.2 + version: 4.3.2(vue@3.4.27(typescript@5.9.3)) + + playgrounds/vite-core: + dependencies: + '@wagmi/connectors': + specifier: workspace:* + version: link:../../packages/connectors + '@wagmi/core': + specifier: workspace:* + version: link:../../packages/core + react: + specifier: '>=18.3.1' + version: 18.3.1 + react-dom: + specifier: '>=18.3.1' + version: 18.3.1(react@18.3.1) + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../../../repo/eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../../../repo/typescript-config - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 - '@vitest/coverage-v8': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9)) - dotenv: - specifier: ^17.3.1 - version: 17.3.1 - fake-indexeddb: - specifier: ^6.2.5 - version: 6.2.5 - happy-dom: - specifier: ^20.8.9 - version: 20.8.9 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) - - repo/eslint-config: - devDependencies: - '@eslint/js': - specifier: ^9.39.2 - version: 9.39.2 - '@next/eslint-plugin-next': - specifier: ^15.5.9 - version: 15.5.9 - eslint: - specifier: ^9.39.2 - version: 9.39.2 - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2) - eslint-plugin-only-warn: - specifier: ^1.1.0 - version: 1.1.0 - eslint-plugin-react: - specifier: ^7.37.5 - version: 7.37.5(eslint@9.39.2) - eslint-plugin-react-hooks: - specifier: ^7.0.1 - version: 7.0.1(eslint@9.39.2) - eslint-plugin-turbo: - specifier: ^2.6.3 - version: 2.6.3(eslint@9.39.2)(turbo@2.8.21) - globals: - specifier: ^16.5.0 - version: 16.5.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - typescript-eslint: - specifier: ^8.49.0 - version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) - - repo/typescript-config: {} - - repo/ui: - dependencies: + '@types/react': + specifier: '>=18.3.1' + version: 18.3.1 + '@types/react-dom': + specifier: '>=18.3.0' + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.2.1(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + buffer: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^7.3.0 + version: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + + playgrounds/vite-react: + dependencies: + '@tanstack/query-sync-storage-persister': + specifier: 5.0.5 + version: 5.0.5 + '@tanstack/react-query': + specifier: '>=5.45.1' + version: 5.49.2(react@18.3.1) + '@tanstack/react-query-persist-client': + specifier: 5.0.5 + version: 5.0.5(@tanstack/react-query@5.49.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + idb-keyval: + specifier: ^6.2.1 + version: 6.2.1 react: - specifier: ^19.2.3 - version: 19.2.3 + specifier: '>=18.3.1' + version: 18.3.1 react-dom: - specifier: ^19.2.3 - version: 19.2.3(react@19.2.3) + specifier: '>=18.3.1' + version: 18.3.1(react@18.3.1) + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + wagmi: + specifier: workspace:* + version: link:../../packages/react devDependencies: - '@repo/eslint-config': - specifier: workspace:^ - version: link:../eslint-config - '@repo/typescript-config': - specifier: workspace:^ - version: link:../typescript-config - '@turbo/gen': - specifier: ^1.13.4 - version: 1.13.4(@types/node@25.3.0)(typescript@5.9.3) - '@types/node': - specifier: ^25.3.0 - version: 25.3.0 + '@tanstack/react-query-devtools': + specifier: 5.0.5 + version: 5.0.5(@tanstack/react-query@5.49.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react': - specifier: ^19.2.7 - version: 19.2.7 + specifier: '>=18.3.1' + version: 18.3.1 '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.7) - typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: '>=18.3.0' + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.2.1(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + '@wagmi/cli': + specifier: workspace:* + version: link:../../packages/cli + buffer: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^7.3.0 + version: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + + playgrounds/vite-vue: + dependencies: + '@tanstack/vue-query': + specifier: '>=5.45.0' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@wagmi/vue': + specifier: workspace:* + version: link:../../packages/vue + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + vue: + specifier: '>=3.4.21' + version: 3.4.27(typescript@5.9.3) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.4 + version: 5.0.4(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.4.27(typescript@5.9.3)) + buffer: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^7.3.0 + version: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vue-tsc: + specifier: ^2.0.6 + version: 2.0.16(typescript@5.9.3) + + site: + devDependencies: + '@shikijs/vitepress-twoslash': + specifier: 1.22.2 + version: 1.22.2(@nuxt/kit@3.20.2(magicast@0.5.1))(typescript@5.9.3) + '@tanstack/query-core': + specifier: 'catalog:' + version: 5.49.1 + '@tanstack/react-query': + specifier: 'catalog:' + version: 5.49.2(react@18.3.1) + '@tanstack/vue-query': + specifier: 'catalog:' + version: 5.49.1(vue@3.4.27(typescript@5.9.3)) + '@types/react': + specifier: 'catalog:' + version: 18.3.1 + '@wagmi/connectors': + specifier: workspace:* + version: link:../packages/connectors + '@wagmi/core': + specifier: workspace:* + version: link:../packages/core + '@wagmi/vue': + specifier: workspace:* + version: link:../packages/vue + abitype: + specifier: '*' + version: 1.0.2(typescript@5.9.3)(zod@3.25.20) + nuxt: + specifier: ^3.19.0 + version: 3.19.0(@biomejs/biome@1.9.4)(@parcel/watcher@2.5.1)(@types/node@24.0.1)(@vercel/blob@1.0.2)(@vue/compiler-sfc@3.5.26)(bufferutil@4.1.0)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.1)(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(yaml@2.8.2) + react: + specifier: 'catalog:' + version: 18.3.1 + unocss: + specifier: ^0.59.4 + version: 0.59.4(rollup@4.54.0) + viem: + specifier: 2.* + version: 2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20) + vitepress: + specifier: 1.5.0 + version: 1.5.0(@algolia/client-search@5.12.0)(@types/node@24.0.1)(@types/react@18.3.1)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.1)(jwt-decode@4.0.0)(lightningcss@1.30.2)(postcss@8.5.6)(qrcode@1.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.44.1)(typescript@5.9.3) + vue: + specifier: 'catalog:' + version: 3.4.27(typescript@5.9.3) + wagmi: + specifier: workspace:* + version: link:../packages/react packages: - '@0xsequence/tee-verifier@0.1.2': - resolution: {integrity: sha512-7sKr8/T4newknx6LAukjlRI3siGiGhBnZohz2Z3jX0zb0EBQdKUq0L//A7CPSckHFPxTg/QvQU2v8e9x9GfkDw==} + 0xsequence@2.3.39: + resolution: {integrity: sha512-P+Cu6gRjClOiQRBQzxp8EvX5X1aSbgKYM79f0lpZ76jTCwf8sbGT+A6ZZSPohEIZr2hTv4iOk9ERvaPAaiJ9qg==} + peerDependencies: + ethers: '>=6' - '@adraffy/ens-normalize@1.11.1': - resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + '@0xsequence/abi@2.3.39': + resolution: {integrity: sha512-MQxVIwwq84IYK2lRHO/lXJQRp21oP982oGyKl5mlkp1jzUE4+GGEMYuo4B3pnokdEeVmSkOJL7j/xJGVYxzlKw==} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} + '@0xsequence/account@2.3.39': + resolution: {integrity: sha512-VgsmHLm7uOdd3SuXt1kykzpnQ1E/S0gQczJskPZIQXdfwGyyA1CnE+LXqzCWy0rr48ny9VktZ+f9ZqWixDuh+Q==} + peerDependencies: + ethers: '>=6' - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} + '@0xsequence/api@2.3.39': + resolution: {integrity: sha512-Gdl6TfUANFy8S+/pUR+BjfgxyXDq2pVzDFkmS7xppa/lcxmwB4lRfrlkuehOqeTLeXgAKbERBWenOOMYctJBqg==} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} - engines: {node: '>=6.9.0'} + '@0xsequence/auth@2.3.39': + resolution: {integrity: sha512-UaLM4NTcNPbR8A+Gf4/RxAfM4thjxjJe95OUo41ABeR/BRqDFbbWUmU79h1UU6iONspe1nlz022ZtPqwXKKKdw==} + peerDependencies: + ethers: '>=6' - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} + '@0xsequence/connect@0.0.0-20250924112110': + resolution: {integrity: sha512-OssaxA1AuawEz26uMg0exl7elsHPnC6Hi15Lam6QTCdbMff4EPGSx6vmUPMP8b5XbSH+UwAbStpjvYHPTk6K1Q==} + peerDependencies: + ethers: '>=6.13.0' + react: '>= 17' + react-dom: '>= 17' + viem: ^2.33.3 + wagmi: 2.15.0 + + '@0xsequence/core@2.3.39': + resolution: {integrity: sha512-4noc1KW1b5g8telV1qac6yZbdkMD5x8gqRa5QnIvMmAVHsXC8qCYlFpy76csYKlWgFKGqxN8RWOwihnZbuyuRg==} + peerDependencies: + ethers: '>=6' - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} + '@0xsequence/dapp-client@git+https://git@github.com:0xsequence/sequence.js.git#6a3501096be6b465b65c034b648549c0b69f8990': + resolution: {commit: 6a3501096be6b465b65c034b648549c0b69f8990, repo: git@github.com:0xsequence/sequence.js.git, type: git} + version: 3.0.0-beta.1 - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} + '@0xsequence/design-system@2.1.11': + resolution: {integrity: sha512-wfuY9v2dNQxw9qAYyflwcKzuiiVqm4qqn2Jkoyw/CqxODcqQ3orUsGxjnbyvCWOwvHbH908c4LOZj/TBI6T0Fw==} + peerDependencies: + motion: '>= 12' + react: '>= 17' + react-dom: '>= 17' - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} + '@0xsequence/ethauth@1.0.0': + resolution: {integrity: sha512-piihXzbS8Sq7P670a+GyTm3igTJL3Ts6pqjJcC0Sv86yqeK6QD0pzJP4APP+/IQa5k+0s2l1SeZwMjR7gSPtCA==} + peerDependencies: + ethers: '>=6' - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} + '@0xsequence/guard@2.3.39': + resolution: {integrity: sha512-XtM90alrUXA4A3wjFRG0CBJvmtf2X4SqAdojuJLJ84uRs2MFGriwS3X6GkHYndtwOpAuLlOFBcC80/BVnF4GNw==} peerDependencies: - '@babel/core': ^7.0.0 + ethers: '>=6' - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} + '@0xsequence/guard@git+https://git@github.com:0xsequence/sequence.js.git#501693a7bd7332ad10fd0f4a72e0dd153b44af0a': + resolution: {commit: 501693a7bd7332ad10fd0f4a72e0dd153b44af0a, repo: git@github.com:0xsequence/sequence.js.git, type: git} + version: 3.0.0-beta.1 - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} + '@0xsequence/hooks@0.0.0-20250924112110': + resolution: {integrity: sha512-KMvUsP+ikXlj9sJEnlQOoX4XtBru/xBcQq4q7qncjAJ4jQAFqDWQNk/QI1M5jN1eWdwuj0edT4M5T/dPVqb/Eg==} + peerDependencies: + '@0xsequence/api': '>=2.3.23' + '@0xsequence/indexer': '>=2.3.23' + '@0xsequence/metadata': '>=2.3.23' + '@0xsequence/network': '>=2.3.23' + '@tanstack/react-query': '>= 5' + react: '>= 17' + react-dom: '>= 17' + viem: ^2.33.3 + + '@0xsequence/indexer@2.3.39': + resolution: {integrity: sha512-oiDcwNg84ZQkme7Xd9aV2RkHUHzybtqac45H887/AmEFaLFWOcnNBlhXPaUOxclofAnVENIYaVf6NscWrKEzng==} + + '@0xsequence/metadata@2.3.39': + resolution: {integrity: sha512-E+tpckzVKctCnNsskEu1PMoOzZojRg82wpiJZ9OOJxxZdLS8yj5meeLdo+76CU0zajsICTuwi7nbgHesvSPPwg==} + + '@0xsequence/migration@2.3.39': + resolution: {integrity: sha512-5qNemWMD3ZqvXhbxlgGhyKVZDev1v8XfTfBiqoib5yycVHm9fqZnaGXr9LKRsLGm8oMf55kisZjrZ3YiG8Hlig==} + peerDependencies: + ethers: '>=6' - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} + '@0xsequence/network@2.3.39': + resolution: {integrity: sha512-ePVe5UfOEAhYOouZuPRd25IF6Lfe9HVCL2Dbp3Zm+hcAHv0Bl89A7TPg9+JQH9Eih0Z+N5uSNJuGfV+Xe+jAUg==} + peerDependencies: + ethers: '>=6' - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} + '@0xsequence/provider@2.3.39': + resolution: {integrity: sha512-3YbyMqFk8oCFtyvyHaXYntgCzgYKLMNvolhq7ioUcxqEp2tzYGAB6bDhUVbVYOW2sPpVv4NuF/fpeAguQ2v8tQ==} + peerDependencies: + ethers: '>=6' - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true + '@0xsequence/relayer@2.3.39': + resolution: {integrity: sha512-loVjpXqFLR9/Lu8mbTaTF1nf3YSQBvw4E+u+btrGz80aiTPKH8Q+BIWDLGtrXF1ian+yoAn4qdpgR3lZD3oYbg==} + peerDependencies: + ethers: '>=6' - '@babel/runtime-corejs3@7.28.4': - resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} - engines: {node: '>=6.9.0'} + '@0xsequence/relayer@git+https://git@github.com:0xsequence/sequence.js.git#07221565e01c85b8cd0bcbb45ace5f9baf57f498': + resolution: {commit: 07221565e01c85b8cd0bcbb45ace5f9baf57f498, repo: git@github.com:0xsequence/sequence.js.git, type: git} + version: 3.0.0-beta.1 - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} + '@0xsequence/replacer@2.3.39': + resolution: {integrity: sha512-Z+FZu9iZRBPXfPo9KB2pT/lkG7mlDq5xDLNZ1D1E5GnKMHLCt7M8j4gvQMu6aWcmku9DNF98LrCMuKBlCgE7/w==} + peerDependencies: + ethers: '>=6' - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} + '@0xsequence/sessions@2.3.39': + resolution: {integrity: sha512-yALTne1Y5pYuUfxVQgXMzwmxRRq90yBQpJ13GX+Ha9w/w/HrHg0cNGCk/TpNsLxqYhusG9zP/2TsQEYEPQrdzw==} + peerDependencies: + ethers: '>=6' - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} + '@0xsequence/signhub@2.3.39': + resolution: {integrity: sha512-CyFWeyafgwQUo05Heq1sGX+t8QPdxQ5Q6g8E+j2VFaNFY8wxGcQxVddiGWUHfvCseIgtkZiyEzfeUkgqJtgpew==} + peerDependencies: + ethers: '>=6' - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} + '@0xsequence/utils@2.3.39': + resolution: {integrity: sha512-0pePuw890L7LjCJvC2OM0Z5NprY/gwq8y7voHPe6hvFA6AnNKeRZPSyk1ysOhM6Ty9nciRjUt9xHG+fkDkdAug==} + peerDependencies: + ethers: '>=6' - '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} + '@0xsequence/waas@2.3.39': + resolution: {integrity: sha512-koDCmWCskVrxolZT09XOK25Na/zBD5e7eV+JQqNPJENwXNNB9Z/iqHJpQGmV3MZx0z1Is/K6ppyqWwlcB1cCPQ==} + peerDependencies: + ethers: '>=6' - '@changesets/apply-release-plan@7.0.14': - resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} + '@0xsequence/wallet-contracts@3.0.1': + resolution: {integrity: sha512-ZvZdXPE1KOYVjl9J6UdN/eBqEmuYHvlO4EUxDxG7VqCgrSiVP9S8k+mEN4aUMzOiYGwKcYY/HIkD211mvxseZQ==} - '@changesets/assemble-release-plan@6.0.9': - resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + '@0xsequence/wallet-core@git+https://git@github.com:0xsequence/sequence.js.git#a623e7dad948e7b281119b34b5b6343bdf502eb8': + resolution: {commit: a623e7dad948e7b281119b34b5b6343bdf502eb8, repo: git@github.com:0xsequence/sequence.js.git, type: git} + version: 3.0.0-beta.1 - '@changesets/changelog-git@0.2.1': - resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + '@0xsequence/wallet-primitives@0.0.0-20250915145821': + resolution: {integrity: sha512-EiV2ke7XmPoamppXw3eVV8gsKWJTaYFq3zhP+vltaXMncBM+0MHhKuPRLMsdbB7V88olma9aafZtuuZhlSIwhQ==} - '@changesets/cli@2.29.8': - resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} - hasBin: true + '@0xsequence/wallet-primitives@git+https://git@github.com:0xsequence/sequence.js.git#d2967108579e84603f3b5e05685c6f331c1f3db7': + resolution: {commit: d2967108579e84603f3b5e05685c6f331c1f3db7, repo: git@github.com:0xsequence/sequence.js.git, type: git} + version: 3.0.0-beta.1 - '@changesets/config@3.1.2': - resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} + '@0xsequence/wallet@2.3.39': + resolution: {integrity: sha512-yBPdsw6HgD2txJXvYbknXSDGSTirtAKVD3d8QPZiVTyVVZIVbV60K1UGGLnVtF2HGph/gVOh9rd2SPVBHcP3DQ==} + peerDependencies: + ethers: '>=6' - '@changesets/errors@0.2.0': - resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + '@adraffy/ens-normalize@1.10.0': + resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} - '@changesets/get-dependents-graph@2.1.3': - resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + '@adraffy/ens-normalize@1.10.1': + resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} - '@changesets/get-release-plan@4.0.14': - resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} - '@changesets/get-version-range-type@0.4.0': - resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + '@algolia/autocomplete-core@1.9.3': + resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} - '@changesets/git@3.0.4': - resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + '@algolia/autocomplete-plugin-algolia-insights@1.9.3': + resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + peerDependencies: + search-insights: '>= 1 < 3' - '@changesets/logger@0.1.1': - resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + '@algolia/autocomplete-preset-algolia@1.17.6': + resolution: {integrity: sha512-Cvg5JENdSCMuClwhJ1ON1/jSuojaYMiUW2KePm18IkdCzPJj/NXojaOxw58RFtQFpJgfVW8h2E8mEoDtLlMdeA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' - '@changesets/parse@0.4.2': - resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} + '@algolia/autocomplete-shared@1.17.6': + resolution: {integrity: sha512-aq/3V9E00Tw2GC/PqgyPGXtqJUlVc17v4cn1EUhSc+O/4zd04Uwb3UmPm8KDaYQQOrkt1lwvCj2vG2wRE5IKhw==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' - '@changesets/pre@2.0.2': - resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + '@algolia/autocomplete-shared@1.9.3': + resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' - '@changesets/read@0.6.6': - resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} + '@algolia/client-abtesting@5.12.0': + resolution: {integrity: sha512-hx4eVydkm3yrFCFxmcBtSzI/ykt0cZ6sDWch+v3JTgKpD2WtosMJU3Upv1AjQ4B6COSHCOWEX3vfFxW6OoH6aA==} + engines: {node: '>= 14.0.0'} - '@changesets/should-skip-package@0.1.2': - resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + '@algolia/client-analytics@5.12.0': + resolution: {integrity: sha512-EpTsSv6IW8maCfXCDIptgT7+mQJj7pImEkcNUnxR8yUKAHzTogTXv9yGm2WXOZFVuwstd2i0sImhQ1Vz8RH/hA==} + engines: {node: '>= 14.0.0'} - '@changesets/types@4.1.0': - resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + '@algolia/client-common@5.12.0': + resolution: {integrity: sha512-od3WmO8qxyfNhKc+K3D17tvun3IMs/xMNmxCG9MiElAkYVbPPTRUYMkRneCpmJyQI0hNx2/EA4kZgzVfQjO86Q==} + engines: {node: '>= 14.0.0'} - '@changesets/types@6.1.0': - resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + '@algolia/client-insights@5.12.0': + resolution: {integrity: sha512-8alajmsYUd+7vfX5lpRNdxqv3Xx9clIHLUItyQK0Z6gwGMbVEFe6YYhgDtwslMAP0y6b0WeJEIZJMLgT7VYpRw==} + engines: {node: '>= 14.0.0'} - '@changesets/write@0.4.0': - resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@algolia/client-personalization@5.12.0': + resolution: {integrity: sha512-bUV9HtfkTBgpoVhxFrMkmVPG03ZN1Rtn51kiaEtukucdk3ggjR9Qu1YUfRSU2lFgxr9qJc8lTxwfvhjCeJRcqw==} + engines: {node: '>= 14.0.0'} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} + '@algolia/client-query-suggestions@5.12.0': + resolution: {integrity: sha512-Q5CszzGWfxbIDs9DJ/QJsL7bP6h+lJMg27KxieEnI9KGCu0Jt5iFA3GkREkgRZxRdzlHbZKkrIzhtHVbSHw/rg==} + engines: {node: '>= 14.0.0'} - '@emnapi/runtime@1.9.1': - resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@algolia/client-search@5.12.0': + resolution: {integrity: sha512-R3qzEytgVLHOGNri+bpta6NtTt7YtkvUe/QBcAmMDjW4Jk1P0eBYIPfvnzIPbINRsLxIq9fZs9uAYBgsrts4Zg==} + engines: {node: '>= 14.0.0'} - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] + '@algolia/ingestion@1.12.0': + resolution: {integrity: sha512-zpHo6qhR22tL8FsdSI4DvEraPDi/019HmMrCFB/TUX98yzh5ooAU7sNW0qPL1I7+S++VbBmNzJOEU9VI8tEC8A==} + engines: {node: '>= 14.0.0'} - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] + '@algolia/monitoring@1.12.0': + resolution: {integrity: sha512-i2AJZED/zf4uhxezAJUhMKoL5QoepCBp2ynOYol0N76+TSoohaMADdPnWCqOULF4RzOwrG8wWynAwBlXsAI1RQ==} + engines: {node: '>= 14.0.0'} - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] + '@algolia/recommend@5.12.0': + resolution: {integrity: sha512-0jmZyKvYnB/Bj5c7WKsKedOUjnr0UtXm0LVFUdQrxXfqOqvWv9n6Vpr65UjdYG4Q49kRQxhlwtal9WJYrYymXg==} + engines: {node: '>= 14.0.0'} - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] + '@algolia/requester-browser-xhr@5.12.0': + resolution: {integrity: sha512-KxwleraFuVoEGCoeW6Y1RAEbgBMS7SavqeyzWdtkJc6mXeCOJXn1iZitb8Tyn2FcpMNUKlSm0adrUTt7G47+Ow==} + engines: {node: '>= 14.0.0'} - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] + '@algolia/requester-fetch@5.12.0': + resolution: {integrity: sha512-FuDZXUGU1pAg2HCnrt8+q1VGHKChV/LhvjvZlLOT7e56GJie6p+EuLu4/hMKPOVuQQ8XXtrTHKIU3Lw+7O5/bQ==} + engines: {node: '>= 14.0.0'} - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] + '@algolia/requester-node-http@5.12.0': + resolution: {integrity: sha512-ncDDY7CxZhMs6LIoPl+vHFQceIBhYPY5EfuGF1V7beO0U38xfsCYEyutEFB2kRzf4D9Gqppn3iWX71sNtrKcuw==} + engines: {node: '>= 14.0.0'} - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] + '@ampproject/remapping@2.2.1': + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] + '@andrewbranch/untar.js@1.0.3': + resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] + '@antfu/install-pkg@0.1.1': + resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==} - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] + '@antfu/utils@0.7.7': + resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==} - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + '@arethetypeswrong/cli@0.16.4': + resolution: {integrity: sha512-qMmdVlJon5FtA+ahn0c1oAVNxiq4xW5lqFiTZ21XHIeVwAVIQ+uRz4UEivqRMsjVV1grzRgJSKqaOrq1MvlVyQ==} engines: {node: '>=18'} - cpu: [loong64] - os: [linux] + hasBin: true - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + '@arethetypeswrong/core@0.16.4': + resolution: {integrity: sha512-RI3HXgSuKTfcBf1hSEg1P9/cOvmI0flsMm6/QL3L3wju4AlHDqd55JFPfXs4pzgEAgy5L9pul4/HPPz99x2GvA==} engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] + '@aws-sdk/client-cognito-identity-provider@3.958.0': + resolution: {integrity: sha512-EmgRcPfRQ6YWekZRI8Khg/lhu+/mmfplxNIJ4rGrFxUvw7yrTIQaynIiQ0/H30MACQHepnC0YXoIfwnr1Ucxtw==} + engines: {node: '>=18.0.0'} - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] + '@aws-sdk/client-sso@3.958.0': + resolution: {integrity: sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==} + engines: {node: '>=18.0.0'} - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] + '@aws-sdk/core@3.957.0': + resolution: {integrity: sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==} + engines: {node: '>=18.0.0'} - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] + '@aws-sdk/credential-provider-env@3.957.0': + resolution: {integrity: sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==} + engines: {node: '>=18.0.0'} - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + '@aws-sdk/credential-provider-http@3.957.0': + resolution: {integrity: sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==} + engines: {node: '>=18.0.0'} - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] + '@aws-sdk/credential-provider-ini@3.958.0': + resolution: {integrity: sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==} + engines: {node: '>=18.0.0'} - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] + '@aws-sdk/credential-provider-login@3.958.0': + resolution: {integrity: sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==} + engines: {node: '>=18.0.0'} - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] + '@aws-sdk/credential-provider-node@3.958.0': + resolution: {integrity: sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==} + engines: {node: '>=18.0.0'} - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] + '@aws-sdk/credential-provider-process@3.957.0': + resolution: {integrity: sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==} + engines: {node: '>=18.0.0'} - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@aws-sdk/credential-provider-sso@3.958.0': + resolution: {integrity: sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==} + engines: {node: '>=18.0.0'} - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@aws-sdk/credential-provider-web-identity@3.958.0': + resolution: {integrity: sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==} + engines: {node: '>=18.0.0'} - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/middleware-host-header@3.957.0': + resolution: {integrity: sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==} + engines: {node: '>=18.0.0'} - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/middleware-logger@3.957.0': + resolution: {integrity: sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==} + engines: {node: '>=18.0.0'} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/middleware-recursion-detection@3.957.0': + resolution: {integrity: sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==} + engines: {node: '>=18.0.0'} - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/middleware-user-agent@3.957.0': + resolution: {integrity: sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==} + engines: {node: '>=18.0.0'} - '@eslint/js@9.39.2': - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/nested-clients@3.958.0': + resolution: {integrity: sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==} + engines: {node: '>=18.0.0'} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/region-config-resolver@3.957.0': + resolution: {integrity: sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==} + engines: {node: '>=18.0.0'} - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@aws-sdk/token-providers@3.958.0': + resolution: {integrity: sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==} + engines: {node: '>=18.0.0'} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} + '@aws-sdk/types@3.957.0': + resolution: {integrity: sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==} + engines: {node: '>=18.0.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} - engines: {node: '>=18.18.0'} + '@aws-sdk/util-endpoints@3.957.0': + resolution: {integrity: sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==} + engines: {node: '>=18.0.0'} - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} + '@aws-sdk/util-locate-window@3.957.0': + resolution: {integrity: sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==} + engines: {node: '>=18.0.0'} - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} + '@aws-sdk/util-user-agent-browser@3.957.0': + resolution: {integrity: sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==} - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} + '@aws-sdk/util-user-agent-node@3.957.0': + resolution: {integrity: sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] + '@aws-sdk/xml-builder@3.957.0': + resolution: {integrity: sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==} + engines: {node: '>=18.0.0'} - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] + '@aws/lambda-invoke-store@0.2.2': + resolution: {integrity: sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==} + engines: {node: '>=18.0.0'} - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] + '@babel/code-frame@7.24.2': + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] + '@babel/compat-data@7.24.4': + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] + '@babel/core@7.24.5': + resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] + '@babel/generator@7.24.5': + resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] + '@babel/helper-annotate-as-pure@7.22.5': + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] + '@babel/helper-compilation-targets@7.23.6': + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] + '@babel/helper-create-class-features-plugin@7.24.5': + resolution: {integrity: sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] + '@babel/helper-environment-visitor@7.22.20': + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] + '@babel/helper-function-name@7.23.0': + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] + '@babel/helper-hoist-variables@7.22.5': + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] + '@babel/helper-member-expression-to-functions@7.24.5': + resolution: {integrity: sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==} + engines: {node: '>=6.9.0'} - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] + '@babel/helper-module-imports@7.24.3': + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} + '@babel/helper-module-transforms@7.24.5': + resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} + engines: {node: '>=6.9.0'} peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + '@babel/core': ^7.0.0 - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@babel/helper-optimise-call-expression@7.22.5': + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} + engines: {node: '>=6.9.0'} - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@babel/helper-plugin-utils@7.24.5': + resolution: {integrity: sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==} + engines: {node: '>=6.9.0'} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@babel/helper-replace-supers@7.24.1': + resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@babel/helper-simple-access@7.24.5': + resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} + engines: {node: '>=6.9.0'} - '@next/env@15.5.14': - resolution: {integrity: sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==} + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} - '@next/eslint-plugin-next@15.5.9': - resolution: {integrity: sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==} + '@babel/helper-split-export-declaration@7.24.5': + resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} + engines: {node: '>=6.9.0'} - '@next/swc-darwin-arm64@15.5.14': - resolution: {integrity: sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} - '@next/swc-darwin-x64@15.5.14': - resolution: {integrity: sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} - '@next/swc-linux-arm64-gnu@15.5.14': - resolution: {integrity: sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} - '@next/swc-linux-arm64-musl@15.5.14': - resolution: {integrity: sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] + '@babel/helper-validator-identifier@7.24.5': + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + engines: {node: '>=6.9.0'} - '@next/swc-linux-x64-gnu@15.5.14': - resolution: {integrity: sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} - '@next/swc-linux-x64-musl@15.5.14': - resolution: {integrity: sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} - '@next/swc-win32-arm64-msvc@15.5.14': - resolution: {integrity: sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} - '@next/swc-win32-x64-msvc@15.5.14': - resolution: {integrity: sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} - '@noble/curves@1.9.1': - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} - engines: {node: ^14.21.3 || >=16} + '@babel/helpers@7.24.5': + resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} + engines: {node: '>=6.9.0'} - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} + '@babel/highlight@7.24.5': + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@babel/parser@7.24.5': + resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + engines: {node: '>=6.0.0'} + hasBin: true - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true - '@rollup/rollup-android-arm-eabi@4.53.4': - resolution: {integrity: sha512-PWU3Y92H4DD0bOqorEPp1Y0tbzwAurFmIYpjcObv5axGVOtcTlB0b2UKMd2echo08MgN7jO8WQZSSysvfisFSQ==} - cpu: [arm] - os: [android] + '@babel/plugin-syntax-jsx@7.24.1': + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-android-arm64@4.53.4': - resolution: {integrity: sha512-Gw0/DuVm3rGsqhMGYkSOXXIx20cC3kTlivZeuaGt4gEgILivykNyBWxeUV5Cf2tDA2nPLah26vq3emlRrWVbng==} - cpu: [arm64] - os: [android] + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-darwin-arm64@4.53.4': - resolution: {integrity: sha512-+w06QvXsgzKwdVg5qRLZpTHh1bigHZIqoIUPtiqh05ZiJVUQ6ymOxaPkXTvRPRLH88575ZCRSRM3PwIoNma01Q==} - cpu: [arm64] - os: [darwin] + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-darwin-x64@4.53.4': - resolution: {integrity: sha512-EB4Na9G2GsrRNRNFPuxfwvDRDUwQEzJPpiK1vo2zMVhEeufZ1k7J1bKnT0JYDfnPC7RNZ2H5YNQhW6/p2QKATw==} - cpu: [x64] - os: [darwin] + '@babel/plugin-transform-modules-commonjs@7.24.1': + resolution: {integrity: sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-freebsd-arm64@4.53.4': - resolution: {integrity: sha512-bldA8XEqPcs6OYdknoTMaGhjytnwQ0NClSPpWpmufOuGPN5dDmvIa32FygC2gneKK4A1oSx86V1l55hyUWUYFQ==} - cpu: [arm64] - os: [freebsd] + '@babel/plugin-transform-react-jsx-self@7.24.5': + resolution: {integrity: sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-freebsd-x64@4.53.4': - resolution: {integrity: sha512-3T8GPjH6mixCd0YPn0bXtcuSXi1Lj+15Ujw2CEb7dd24j9thcKscCf88IV7n76WaAdorOzAgSSbuVRg4C8V8Qw==} - cpu: [x64] - os: [freebsd] + '@babel/plugin-transform-react-jsx-source@7.24.1': + resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-linux-arm-gnueabihf@4.53.4': - resolution: {integrity: sha512-UPMMNeC4LXW7ZSHxeP3Edv09aLsFUMaD1TSVW6n1CWMECnUIJMFFB7+XC2lZTdPtvB36tYC0cJWc86mzSsaviw==} - cpu: [arm] - os: [linux] + '@babel/plugin-transform-typescript@7.24.5': + resolution: {integrity: sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-linux-arm-musleabihf@4.53.4': - resolution: {integrity: sha512-H8uwlV0otHs5Q7WAMSoyvjV9DJPiy5nJ/xnHolY0QptLPjaSsuX7tw+SPIfiYH6cnVx3fe4EWFafo6gH6ekZKA==} - cpu: [arm] - os: [linux] + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-linux-arm64-gnu@4.53.4': - resolution: {integrity: sha512-BLRwSRwICXz0TXkbIbqJ1ibK+/dSBpTJqDClF61GWIrxTXZWQE78ROeIhgl5MjVs4B4gSLPCFeD4xML9vbzvCQ==} - cpu: [arm64] - os: [linux] + '@babel/preset-typescript@7.24.1': + resolution: {integrity: sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@rollup/rollup-linux-arm64-musl@4.53.4': - resolution: {integrity: sha512-6bySEjOTbmVcPJAywjpGLckK793A0TJWSbIa0sVwtVGfe/Nz6gOWHOwkshUIAp9j7wg2WKcA4Snu7Y1nUZyQew==} - cpu: [arm64] - os: [linux] + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-loong64-gnu@4.53.4': - resolution: {integrity: sha512-U0ow3bXYJZ5MIbchVusxEycBw7bO6C2u5UvD31i5IMTrnt2p4Fh4ZbHSdc/31TScIJQYHwxbj05BpevB3201ug==} - cpu: [loong64] - os: [linux] + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-ppc64-gnu@4.53.4': - resolution: {integrity: sha512-iujDk07ZNwGLVn0YIWM80SFN039bHZHCdCCuX9nyx3Jsa2d9V/0Y32F+YadzwbvDxhSeVo9zefkoPnXEImnM5w==} - cpu: [ppc64] - os: [linux] + '@babel/standalone@7.24.5': + resolution: {integrity: sha512-Sl8oN9bGfRlNUA2jzfzoHEZxFBDliBlwi5mPVCAWKSlBNkXXJOHpu7SDOqjF6mRoTa6GNX/1kAWG3Tr+YQ3N7A==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-riscv64-gnu@4.53.4': - resolution: {integrity: sha512-MUtAktiOUSu+AXBpx1fkuG/Bi5rhlorGs3lw5QeJ2X3ziEGAq7vFNdWVde6XGaVqi0LGSvugwjoxSNJfHFTC0g==} - cpu: [riscv64] - os: [linux] + '@babel/template@7.24.0': + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-riscv64-musl@4.53.4': - resolution: {integrity: sha512-btm35eAbDfPtcFEgaXCI5l3c2WXyzwiE8pArhd66SDtoLWmgK5/M7CUxmUglkwtniPzwvWioBKKl6IXLbPf2sQ==} - cpu: [riscv64] - os: [linux] + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-s390x-gnu@4.53.4': - resolution: {integrity: sha512-uJlhKE9ccUTCUlK+HUz/80cVtx2RayadC5ldDrrDUFaJK0SNb8/cCmC9RhBhIWuZ71Nqj4Uoa9+xljKWRogdhA==} - cpu: [s390x] - os: [linux] + '@babel/traverse@7.24.5': + resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-x64-gnu@4.53.4': - resolution: {integrity: sha512-jjEMkzvASQBbzzlzf4os7nzSBd/cvPrpqXCUOqoeCh1dQ4BP3RZCJk8XBeik4MUln3m+8LeTJcY54C/u8wb3DQ==} - cpu: [x64] - os: [linux] + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.5': + resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} - '@rollup/rollup-linux-x64-musl@4.53.4': - resolution: {integrity: sha512-lu90KG06NNH19shC5rBPkrh6mrTpq5kviFylPBXQVpdEu0yzb0mDgyxLr6XdcGdBIQTH/UAhDJnL+APZTBu1aQ==} + '@babel/types@7.27.1': + resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} cpu: [x64] - os: [linux] + os: [darwin] - '@rollup/rollup-openharmony-arm64@4.53.4': - resolution: {integrity: sha512-dFDcmLwsUzhAm/dn0+dMOQZoONVYBtgik0VuY/d5IJUUb787L3Ko/ibvTvddqhb3RaB7vFEozYevHN4ox22R/w==} + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} cpu: [arm64] - os: [openharmony] + os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.53.4': - resolution: {integrity: sha512-WvUpUAWmUxZKtRnQWpRKnLW2DEO8HB/l8z6oFFMNuHndMzFTJEXzaYJ5ZAmzNw0L21QQJZsUQFt2oPf3ykAD/w==} + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} cpu: [arm64] - os: [win32] + os: [linux] - '@rollup/rollup-win32-ia32-msvc@4.53.4': - resolution: {integrity: sha512-JGbeF2/FDU0x2OLySw/jgvkwWUo05BSiJK0dtuI4LyuXbz3wKiC1xHhLB1Tqm5VU6ZZDmAorj45r/IgWNWku5g==} - cpu: [ia32] - os: [win32] + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] - '@rollup/rollup-win32-x64-gnu@4.53.4': - resolution: {integrity: sha512-zuuC7AyxLWLubP+mlUwEyR8M1ixW1ERNPHJfXm8x7eQNP4Pzkd7hS3qBuKBR70VRiQ04Kw8FNfRMF5TNxuZq2g==} + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.4': - resolution: {integrity: sha512-Sbx45u/Lbb5RyptSbX7/3deP+/lzEmZ0BTSHxwxN/IMOZDZf8S0AGo0hJD5n/LQssxb5Z3B4og4P2X6Dd8acCA==} + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/bip32@1.7.0': - resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - - '@scure/bip39@1.6.0': - resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@bomb.sh/tab@0.0.10': + resolution: {integrity: sha512-6ALS2rh/4LKn0Yxwm35V6LcgQuSiECHbqQo7+9g4rkgGyXZ0siOc8K+IuWIq/4u0Zkv2mevP9QSqgKhGIvLJMw==} + hasBin: true + peerDependencies: + cac: ^6.7.14 + citty: ^0.1.6 + commander: ^13.1.0 + peerDependenciesMeta: + cac: + optional: true + citty: + optional: true + commander: + optional: true - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@bundled-es-modules/cookie@2.0.0': + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + '@bytecodealliance/preview2-shim@0.17.6': + resolution: {integrity: sha512-n3cM88gTen5980UOBAD6xDcNNL3ocTK8keab21bpx1ONdA+ARj7uD1qoFxOWCyKlkpSi195FH+GeAut7Oc6zZw==} - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + '@changesets/apply-release-plan@7.0.14': + resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + '@changesets/apply-release-plan@7.0.5': + resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@changesets/assemble-release-plan@6.0.4': + resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} - '@turbo/darwin-64@2.8.21': - resolution: {integrity: sha512-kfGoM0Iw8ZNZpbds+4IzOe0hjvHldqJwUPRAjXJi3KBxg/QOZL95N893SRoMtf2aJ+jJ3dk32yPkp8rvcIjP9g==} - cpu: [x64] - os: [darwin] + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} - '@turbo/darwin-arm64@2.8.21': - resolution: {integrity: sha512-o9HEflxUEyr987x0cTUzZBhDOyL6u95JmdmlkH2VyxAw7zq2sdtM5e72y9ufv2N5SIoOBw1fVn9UES5VY5H6vQ==} - cpu: [arm64] - os: [darwin] + '@changesets/changelog-git@0.2.0': + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@turbo/gen@1.13.4': - resolution: {integrity: sha512-PK38N1fHhDUyjLi0mUjv0RbX0xXGwDLQeRSGsIlLcVpP1B5fwodSIwIYXc9vJok26Yne94BX5AGjueYsUT3uUw==} - hasBin: true + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@turbo/linux-64@2.8.21': - resolution: {integrity: sha512-uTxlCcXWy5h1fSSymP8XSJ+AudzEHMDV3IDfKX7+DGB8kgJ+SLoTUAH7z4OFA7I/l2sznz0upPdbNNZs91YMag==} - cpu: [x64] - os: [linux] + '@changesets/changelog-github@0.4.6': + resolution: {integrity: sha512-ahR/+o3OPodzfG9kucEMU/tEtBgwy6QoJiWi1sDBPme8n3WjT6pBlbhqNYpWAJKilomwfjBGY0MTUTs6r9d1RQ==} - '@turbo/linux-arm64@2.8.21': - resolution: {integrity: sha512-cdHIcxNcihHHkCHp0Y4Zb60K4Qz+CK4xw1gb6s/t/9o4SMeMj+hTBCtoW6QpPnl9xPYmxuTou8Zw6+cylTnREg==} - cpu: [arm64] - os: [linux] + '@changesets/cli@2.27.8': + resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} + hasBin: true - '@turbo/windows-64@2.8.21': - resolution: {integrity: sha512-/iBj4OzbqEY8CX+eaeKbBTMZv2CLXNrt0692F7HnK7LcyYwyDecaAiSET6ZzL4opT7sbwkKvzAC/fhqT3Quu1A==} - cpu: [x64] - os: [win32] + '@changesets/cli@2.29.8': + resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} + hasBin: true - '@turbo/windows-arm64@2.8.21': - resolution: {integrity: sha512-95tMA/ZbIidJFUUtkmqioQ1gf3n3I1YbRP3ZgVdWTVn2qVbkodcIdGXBKRHHrIbRsLRl99SiHi/L7IxhpZDagQ==} - cpu: [arm64] - os: [win32] + '@changesets/config@3.0.3': + resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} - '@turbo/workspaces@1.13.4': - resolution: {integrity: sha512-3uYg2b5TWCiupetbDFMbBFMHl33xQTvp5DNg0fZSYal73Z9AlFH9yWabHWMYw6ywmwM1evkYRpTVA2n7GgqT5A==} - hasBin: true + '@changesets/config@3.1.2': + resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} - '@types/glob@7.2.0': - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + '@changesets/get-github-info@0.5.2': + resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} - '@types/inquirer@6.5.0': - resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==} + '@changesets/get-release-plan@4.0.14': + resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@changesets/get-release-plan@4.0.4': + resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} - '@types/minimatch@6.0.0': - resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} - deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@changesets/git@3.0.1': + resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} - '@types/node@25.3.0': - resolution: {integrity: sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==} + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} - '@types/react@19.2.7': - resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@changesets/parse@0.4.0': + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + '@changesets/parse@0.4.2': + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} - '@types/tinycolor2@1.4.6': - resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} - '@types/whatwg-mimetype@3.0.2': - resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@changesets/read@0.6.1': + resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + '@changesets/read@0.6.6': + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} - '@typescript-eslint/eslint-plugin@8.50.0': - resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.50.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} - '@typescript-eslint/parser@8.50.0': - resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - '@typescript-eslint/project-service@8.50.0': - resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@changesets/types@5.2.1': + resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} - '@typescript-eslint/scope-manager@8.50.0': - resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@changesets/types@6.0.0': + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - '@typescript-eslint/tsconfig-utils@8.50.0': - resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} - '@typescript-eslint/type-utils@8.50.0': - resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} - '@typescript-eslint/types@8.50.0': - resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@typescript-eslint/typescript-estree@8.50.0': - resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@clack/core@1.0.0-alpha.7': + resolution: {integrity: sha512-3vdh6Ar09D14rVxJZIm3VQJkU+ZOKKT5I5cC0cOVazy70CNyYYjiwRj9unwalhESndgxx6bGc/m6Hhs4EKF5XQ==} - '@typescript-eslint/utils@8.50.0': - resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@clack/prompts@1.0.0-alpha.8': + resolution: {integrity: sha512-YZGC4BmTKSF5OturNKEz/y4xNjYGmGk6NI785CQucJ7OEdX0qbMmL/zok+9bL6c7qE3WSYffyK5grh2RnkGNtQ==} - '@typescript-eslint/visitor-keys@8.50.0': - resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@cloudflare/kv-asset-handler@0.4.1': + resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} + engines: {node: '>=18.0.0'} - '@vitest/coverage-v8@4.0.18': - resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} - peerDependencies: - '@vitest/browser': 4.0.18 - vitest: 4.0.18 - peerDependenciesMeta: - '@vitest/browser': - optional: true + '@coinbase/wallet-sdk@3.9.3': + resolution: {integrity: sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==} - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@coinbase/wallet-sdk@4.3.0': + resolution: {integrity: sha512-T3+SNmiCw4HzDm4we9wCHCxlP0pqCiwKe4sOwPH3YAK2KSKjxPRydKu6UQJrdONFVLG7ujXvbd/6ZqmvJb8rkw==} - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@databeat/tracker@0.9.3': + resolution: {integrity: sha512-eGsiNU/CRFujcNtUUqvBiqveCs6S6SiAhalXPDodbk74d3FzvLqHDn5k6WfOEJIhrP3CbYgfMXL0nk51s/rQsg==} - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@docsearch/css@3.6.3': + resolution: {integrity: sha512-3uvbg8E7rhqE1C4oBAK3tGlS2qfhi9zpfZgH/yjDPF73vd9B41urVIKujF4rczcF4E3qs34SedhehiDJ4UdNBA==} - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@docsearch/js@3.6.3': + resolution: {integrity: sha512-2mBFomaN6VijyQQGwieERDu9GeE0hlv9TQRZBTOYsPQW7/vqtd4hnHEkbBbaBRiS4PYcy+UhikbMuDExJs63UA==} - abitype@1.1.0: - resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} + '@docsearch/react@3.6.3': + resolution: {integrity: sha512-2munr4uBuZq1PG+Ge+F+ldIdxb3Wi8OmEIv2tQQb4RvEvvph+xtQkxwHzVIEnt5s+HecwucuXwB+3JhcZboFLg==} peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' peerDependenciesMeta: - typescript: + '@types/react': optional: true - zod: + react: optional: true - - abitype@1.2.2: - resolution: {integrity: sha512-4DOIMWscIB3j8hboLAUjLZCE8TMLdgecBpHFumfU4PdO/C1SBCVx4Nu1wPYXaL2iK8B0Jk3tiwnDLCpUtm3fZg==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: + react-dom: optional: true - zod: + search-insights: optional: true - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + '@ecies/ciphers@0.2.5': + resolution: {integrity: sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@noble/ciphers': ^1.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} + '@edge-runtime/format@2.2.1': + resolution: {integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==} + engines: {node: '>=16'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true + '@edge-runtime/node-utils@2.3.0': + resolution: {integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==} + engines: {node: '>=16'} - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} + '@edge-runtime/ponyfill@2.4.2': + resolution: {integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==} + engines: {node: '>=16'} - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} + '@edge-runtime/primitives@4.1.0': + resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + engines: {node: '>=16'} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + '@edge-runtime/vm@3.2.0': + resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + engines: {node: '>=16'} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] - asn1js@3.0.7: - resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} - engines: {node: '>=12.0.0'} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - - ast-v8-to-istanbul@0.3.11: - resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] - baseline-browser-mapping@2.9.7: - resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==} - hasBin: true + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - deprecated: Security vulnerability fixed in 5.2.0, please upgrade + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] - better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] - brace-expansion@5.0.3: - resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} - engines: {node: 18 || 20 || >=22} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] - bytestreamjs@2.0.1: - resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} - engines: {node: '>=6.0.0'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] - camel-case@3.0.0: - resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] - caniuse-lite@1.0.30001781: - resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] - cbor2@1.12.0: - resolution: {integrity: sha512-3Cco8XQhi27DogSp9Ri6LYNZLi/TBY/JVnDe+mj06NkBjW/ZYOtekaEU4wZ4xcRMNrFkDv8KNtOAqHyDfz3lYg==} - engines: {node: '>=18.7'} + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] - chai@6.2.1: - resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} + cpu: [ia32] + os: [linux] - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] - change-case@3.1.0: - resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} - hasBin: true + cpu: [x64] + os: [linux] - constant-case@2.0.0: - resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] - core-js-pure@3.47.0: - resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] - del@5.1.0: - resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} - engines: {node: '>=8'} + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] - dot-case@2.1.1: - resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] - dotenv@16.0.3: - resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] - dotenv@17.3.1: - resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} + cpu: [ia32] + os: [win32] - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} - engines: {node: '>= 0.4'} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} + '@ethereumjs/common@3.2.0': + resolution: {integrity: sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==} - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} + '@ethereumjs/rlp@4.0.1': + resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} + engines: {node: '>=14'} + hasBin: true - es-iterator-helpers@1.2.2: - resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} - engines: {node: '>= 0.4'} + '@ethereumjs/tx@4.2.0': + resolution: {integrity: sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==} + engines: {node: '>=14'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + '@ethereumjs/util@8.1.0': + resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} + engines: {node: '>=14'} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} + '@ethersproject/abi@5.7.0': + resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} + '@ethersproject/abstract-provider@5.7.0': + resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} + '@ethersproject/abstract-signer@5.7.0': + resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} + '@ethersproject/address@5.7.0': + resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true + '@ethersproject/base64@5.7.0': + resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} + '@ethersproject/bignumber@5.7.0': + resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + '@ethersproject/bytes@5.7.0': + resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + '@ethersproject/constants@5.7.0': + resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true + '@ethersproject/hash@5.7.0': + resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' + '@ethersproject/keccak256@5.7.0': + resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} - eslint-plugin-only-warn@1.1.0: - resolution: {integrity: sha512-2tktqUAT+Q3hCAU0iSf4xAN1k9zOpjK5WO8104mB0rT/dGhOa09582HN5HlbxNbPRZ0THV7nLGvzugcNOSjzfA==} - engines: {node: '>=6'} + '@ethersproject/logger@5.7.0': + resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + '@ethersproject/networks@5.7.1': + resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + '@ethersproject/properties@5.7.0': + resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} - eslint-plugin-turbo@2.6.3: - resolution: {integrity: sha512-91WZ+suhT/pk+qNS0/rqT43xLUlUblsa3a8jKmAStGhkJCmR2uX0oWo/e0Edb+It8MdnteXuYpCkvsK4Vw8FtA==} - peerDependencies: - eslint: '>6.6.0' - turbo: '>2.0.0' + '@ethersproject/rlp@5.7.0': + resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ethersproject/signing-key@5.7.0': + resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@ethersproject/strings@5.7.0': + resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ethersproject/transactions@5.7.0': + resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} - eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true + '@ethersproject/web@5.7.1': + resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@2.1.0': + resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} + engines: {node: '>=14'} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} + '@floating-ui/core@1.6.1': + resolution: {integrity: sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==} - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + '@floating-ui/dom@1.1.1': + resolution: {integrity: sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw==} - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + '@floating-ui/utils@0.2.2': + resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + '@iconify-json/simple-icons@1.2.10': + resolution: {integrity: sha512-9OK1dsSjXlH36lhu5n+BlSoXuqFjHUErGLtNdzHpq0vHq4YFBuGYWtZ+vZTHLreRQ8ijPRv/6EsgkV+nf6AReQ==} - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - fake-indexeddb@6.2.5: - resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} + '@iconify/utils@2.1.23': + resolution: {integrity: sha512-YGNbHKM5tyDvdWZ92y2mIkrfvm5Fvhe6WJSkWu7vvOFhMtYDP0casZpoRz0XEHZCrYsR4stdGT3cZ52yp5qZdQ==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + '@inquirer/confirm@3.1.6': + resolution: {integrity: sha512-Mj4TU29g6Uy+37UtpA8UpEOI2icBfpCwSW1QDtfx60wRhUy90s/kHPif2OXSSvuwDQT1lhAYRWUfkNf9Tecxvg==} engines: {node: '>=18'} - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} + '@inquirer/core@8.1.0': + resolution: {integrity: sha512-kfx0SU9nWgGe1f03ao/uXc85SFH1v2w3vQVH7QDGjKxdtJz+7vPitFtG++BTyJMYyYgH8MpXigutcXJeiQwVRw==} + engines: {node: '>=18'} - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} + '@inquirer/figures@1.0.1': + resolution: {integrity: sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==} + engines: {node: '>=18'} - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} + '@inquirer/type@1.3.1': + resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} + engines: {node: '>=18'} - get-uri@6.0.5: - resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} - engines: {node: '>= 14'} + '@ioredis/commands@1.4.0': + resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} - engines: {node: '>=18'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} + '@jridgewell/gen-mapping@0.3.3': + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} - globby@10.0.2: - resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} - engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + '@jridgewell/resolve-uri@3.1.0': + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - gradient-string@2.0.2: - resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} - engines: {node: '>=10'} + '@jridgewell/set-array@1.1.2': + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} - happy-dom@20.8.9: - resolution: {integrity: sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==} - engines: {node: '>=20.0.0'} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} + '@jridgewell/sourcemap-codec@1.4.14': + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} + '@jridgewell/trace-mapping@0.3.17': + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - header-case@1.0.1: - resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + '@lit-labs/ssr-dom-shim@1.3.0': + resolution: {integrity: sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + '@lit/reactive-element@2.1.0': + resolution: {integrity: sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - human-id@4.1.3: - resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} hasBin: true - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + '@metamask/eth-json-rpc-provider@1.0.1': + resolution: {integrity: sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA==} + engines: {node: '>=14.0.0'} - iconv-lite@0.7.1: - resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} - engines: {node: '>=0.10.0'} + '@metamask/eth-sig-util@4.0.1': + resolution: {integrity: sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==} + engines: {node: '>=12.0.0'} - idb@8.0.3: - resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} + '@metamask/json-rpc-engine@7.3.3': + resolution: {integrity: sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==} + engines: {node: '>=16.0.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + '@metamask/json-rpc-engine@8.0.2': + resolution: {integrity: sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==} + engines: {node: '>=16.0.0'} - ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + '@metamask/json-rpc-middleware-stream@7.0.2': + resolution: {integrity: sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==} + engines: {node: '>=16.0.0'} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} + '@metamask/object-multiplex@2.1.0': + resolution: {integrity: sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==} + engines: {node: ^16.20 || ^18.16 || >=20} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} + '@metamask/onboarding@1.0.1': + resolution: {integrity: sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} + '@metamask/providers@16.1.0': + resolution: {integrity: sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==} + engines: {node: ^18.18 || >=20} - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + '@metamask/rpc-errors@6.4.0': + resolution: {integrity: sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==} + engines: {node: '>=16.0.0'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} + '@metamask/safe-event-emitter@2.0.0': + resolution: {integrity: sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + '@metamask/safe-event-emitter@3.0.0': + resolution: {integrity: sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ==} + engines: {node: '>=12.0.0'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + '@metamask/safe-event-emitter@3.1.2': + resolution: {integrity: sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==} + engines: {node: '>=12.0.0'} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + '@metamask/sdk-analytics@0.0.5': + resolution: {integrity: sha512-fDah+keS1RjSUlC8GmYXvx6Y26s3Ax1U9hGpWb6GSY5SAdmTSIqp2CvYy6yW0WgLhnYhW+6xERuD0eVqV63QIQ==} - inquirer@7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} + '@metamask/sdk-communication-layer@0.33.1': + resolution: {integrity: sha512-0bI9hkysxcfbZ/lk0T2+aKVo1j0ynQVTuB3sJ5ssPWlz+Z3VwveCkP1O7EVu1tsVVCb0YV5WxK9zmURu2FIiaA==} + peerDependencies: + cross-fetch: ^4.0.0 + eciesjs: '*' + eventemitter2: ^6.4.9 + readable-stream: ^3.6.2 + socket.io-client: ^4.5.1 - inquirer@8.2.7: - resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} - engines: {node: '>=12.0.0'} + '@metamask/sdk-install-modal-web@0.32.1': + resolution: {integrity: sha512-MGmAo6qSjf1tuYXhCu2EZLftq+DSt5Z7fsIKr2P+lDgdTPWgLfZB1tJKzNcwKKOdf6q9Qmmxn7lJuI/gq5LrKw==} - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} + '@metamask/sdk@0.33.1': + resolution: {integrity: sha512-1mcOQVGr9rSrVcbKPNVzbZ8eCl1K0FATsYH3WJ/MH4WcZDWGECWrXJPNMZoEAkLxWiMe8jOQBumg2pmcDa9zpQ==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} - engines: {node: '>= 12'} + '@metamask/superstruct@3.2.1': + resolution: {integrity: sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==} + engines: {node: '>=16.0.0'} - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} + '@metamask/utils@5.0.2': + resolution: {integrity: sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==} + engines: {node: '>=14.0.0'} - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} + '@metamask/utils@8.5.0': + resolution: {integrity: sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==} + engines: {node: '>=16.0.0'} - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} + '@metamask/utils@9.3.0': + resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} + engines: {node: '>=16.0.0'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + '@mswjs/interceptors@0.35.9': + resolution: {integrity: sha512-SSnyl/4ni/2ViHKkiZb8eajA/eN1DNFaHjhGiLUdZvDz6PKF4COSf/17xqSz64nOo2Ia29SA6B2KNCsyCbVmaQ==} + engines: {node: '>=18'} - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} + '@next/bundle-analyzer@15.3.3': + resolution: {integrity: sha512-9gddnjACK6yOa5IkmeFyzcwZh2rscsb6ZspTd7tymPYKQM96fJuKjn9HrRtPNKiMm7ExKNadAJqREmHdBgHZ9A==} - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} + '@next/env@15.4.10': + resolution: {integrity: sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==} - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} + '@next/swc-darwin-arm64@15.4.8': + resolution: {integrity: sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + '@next/swc-darwin-x64@15.4.8': + resolution: {integrity: sha512-xla6AOfz68a6kq3gRQccWEvFC/VRGJmA/QuSLENSO7CZX5WIEkSz7r1FdXUjtGCQ1c2M+ndUAH7opdfLK1PQbw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} + '@next/swc-linux-arm64-gnu@15.4.8': + resolution: {integrity: sha512-y3fmp+1Px/SJD+5ntve5QLZnGLycsxsVPkTzAc3zUiXYSOlTPqT8ynfmt6tt4fSo1tAhDPmryXpYKEAcoAPDJw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + '@next/swc-linux-arm64-musl@15.4.8': + resolution: {integrity: sha512-DX/L8VHzrr1CfwaVjBQr3GWCqNNFgyWJbeQ10Lx/phzbQo3JNAxUok1DZ8JHRGcL6PgMRgj6HylnLNndxn4Z6A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - - is-lower-case@1.1.3: - resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + '@next/swc-linux-x64-gnu@15.4.8': + resolution: {integrity: sha512-9fLAAXKAL3xEIFdKdzG5rUSvSiZTLLTCc6JKq1z04DR4zY7DbAPcRvNm3K1inVhTiQCs19ZRAgUerHiVKMZZIA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] - is-path-cwd@2.2.0: - resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} - engines: {node: '>=6'} + '@next/swc-linux-x64-musl@15.4.8': + resolution: {integrity: sha512-s45V7nfb5g7dbS7JK6XZDcapicVrMMvX2uYgOHP16QuKH/JA285oy6HcxlKqwUNaFY/UC6EvQ8QZUOo19cBKSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + '@next/swc-win32-arm64-msvc@15.4.8': + resolution: {integrity: sha512-KjgeQyOAq7t/HzAJcWPGA8X+4WY03uSCZ2Ekk98S9OgCFsb6lfBE3dbUzUuEQAN2THbwYgFfxX2yFTCMm8Kehw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} + '@next/swc-win32-x64-msvc@15.4.8': + resolution: {integrity: sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} + engines: {node: ^14.21.3 || >=16} - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + '@noble/curves@1.2.0': + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} + '@noble/curves@1.4.0': + resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} - is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} + '@noble/curves@1.8.0': + resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} + engines: {node: ^14.21.3 || >=16} - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} + engines: {node: ^14.21.3 || >=16} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} - is-upper-case@1.1.2: - resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==} + '@noble/curves@1.9.2': + resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==} + engines: {node: ^14.21.3 || >=16} - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} + '@noble/hashes@1.2.0': + resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} + '@noble/hashes@1.3.2': + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} - isbinaryfile@4.0.10: - resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} - engines: {node: '>= 8.0.0'} + '@noble/hashes@1.7.0': + resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} + engines: {node: ^14.21.3 || >=16} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + engines: {node: ^14.21.3 || >=16} - isows@1.0.7: - resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} - peerDependencies: - ws: '*' + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} + '@noble/secp256k1@1.7.1': + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} - js-tokens@10.0.0: - resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + '@nomicfoundation/edr-darwin-arm64@0.3.7': + resolution: {integrity: sha512-6tK9Lv/lSfyBvpEQ4nsTfgxyDT1y1Uv/x8Wa+aB+E8qGo3ToexQ1BMVjxJk6PChXCDOWxB3B4KhqaZFjdhl3Ow==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + '@nomicfoundation/edr-darwin-x64@0.3.7': + resolution: {integrity: sha512-1RrQ/1JPwxrYO69e0tglFv5H+ggour5Ii3bb727+yBpBShrxtOTQ7fZyfxA5h62LCN+0Z9wYOPeQ7XFcVurMaQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [darwin] - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true + '@nomicfoundation/edr-linux-arm64-gnu@0.3.7': + resolution: {integrity: sha512-ds/CKlBoVXIihjhflhgPn13EdKWed6r5bgvMs/YwRqT5wldQAQJZWAfA2+nYm0Yi2gMGh1RUpBcfkyl4pq7G+g==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true + '@nomicfoundation/edr-linux-arm64-musl@0.3.7': + resolution: {integrity: sha512-e29udiRaPujhLkM3+R6ju7QISrcyOqpcaxb2FsDWBkuD7H8uU9JPZEyyUIpEp5uIY0Jh1eEJPKZKIXQmQAEAuw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true + '@nomicfoundation/edr-linux-x64-gnu@0.3.7': + resolution: {integrity: sha512-/xkjmTyv+bbJ4akBCW0qzFKxPOV4AqLOmqurov+s9umHb16oOv72osSa3SdzJED2gHDaKmpMITT4crxbar4Axg==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + '@nomicfoundation/edr-linux-x64-musl@0.3.7': + resolution: {integrity: sha512-QwBP9xlmsbf/ldZDGLcE4QiAb8Zt46E/+WLpxHBATFhGa7MrpJh6Zse+h2VlrT/SYLPbh2cpHgSmoSlqVxWG9g==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] - json-canonicalize@2.0.0: - resolution: {integrity: sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg==} + '@nomicfoundation/edr-win32-x64-msvc@0.3.7': + resolution: {integrity: sha512-j/80DEnkxrF2ewdbk/gQ2EOPvgF0XSsg8D0o4+6cKhUVAW6XwtWKzIphNL6dyD2YaWEPgIrNvqiJK/aln0ww4Q==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + '@nomicfoundation/edr@0.3.7': + resolution: {integrity: sha512-v2JFWnFKRsnOa6PDUrD+sr8amcdhxnG/YbL7LzmgRGU1odWEyOF4/EwNeUajQr4ZNKVWrYnJ6XjydXtUge5OBQ==} + engines: {node: '>= 18'} - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + '@nomicfoundation/ethereumjs-common@4.0.4': + resolution: {integrity: sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} + '@nomicfoundation/ethereumjs-rlp@5.0.4': + resolution: {integrity: sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==} + engines: {node: '>=18'} hasBin: true - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} + '@nomicfoundation/ethereumjs-tx@5.0.4': + resolution: {integrity: sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==} + engines: {node: '>=18'} + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true - jwt-decode@4.0.0: - resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + '@nomicfoundation/ethereumjs-util@9.0.4': + resolution: {integrity: sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==} engines: {node: '>=18'} + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + '@nomicfoundation/slang@1.3.1': + resolution: {integrity: sha512-gh0+JDjazmevEYCcwVgtuyfBJcV1209gIORZNRjUxbGzbQN0MOhQO9T0ptkzHKCf854gUy27SMxPbAyAu63fvQ==} - lefthook-darwin-arm64@2.1.1: - resolution: {integrity: sha512-O/RS1j03/Fnq5zCzEb2r7UOBsqPeBuf1C5pMkIJcO4TSE6hf3rhLUkcorKc2M5ni/n5zLGtzQUXHV08/fSAT3Q==} + '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1': + resolution: {integrity: sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@2.1.1: - resolution: {integrity: sha512-mm/kdKl81ROPoYnj9XYk5JDqj+/6Al8w/SSPDfhItkLJyl4pqS+hWUOP6gDGrnuRk8S0DvJ2+hzhnDsQnZohWQ==} + '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.1': + resolution: {integrity: sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@2.1.1: - resolution: {integrity: sha512-F7JXlKmjxGqGbCWPLND0bVB4DMQezIe48pEwTlUQZbxh450c2gP5Q8FdttMZKOT163kBGGTqJAJSEC6zW+QSxA==} - cpu: [arm64] - os: [freebsd] - - lefthook-freebsd-x64@2.1.1: - resolution: {integrity: sha512-Po8/lJMqNzKSZPuEI46dLuWoBoXtAxCuRpeOh6DAV/M4RhBynaCu8rLMZ9BqF7cVbZEWoplOmYo6HdOuiYpCkQ==} + '@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.1': + resolution: {integrity: sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==} + engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@2.1.1: - resolution: {integrity: sha512-mI2ljFgPEqHxI8vrN9nKgnVu63Rz1KisDbPwlvs7BTYNwq3sncdK5ukpGR4zzWdh6saNJ5tCtHEtep5GQI11nw==} + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.1': + resolution: {integrity: sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - lefthook-linux-x64@2.1.1: - resolution: {integrity: sha512-m3G/FaxC+crxeg9XeaUuHfEoL+i9gbkg2Hp2KD2IcVVIxprqlyqf0Hb8zbLV2NMXuo5RSGokJu44oAoTO3Ou2g==} - cpu: [x64] + '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1': + resolution: {integrity: sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==} + engines: {node: '>= 10'} + cpu: [arm64] os: [linux] - lefthook-openbsd-arm64@2.1.1: - resolution: {integrity: sha512-gz/8FJPvhjOdOFt1GmFvuvDOe+W+BBRjoeAT1/mTgkN7HCXMXgqNjjvakQKQeGz1I1v08wXG1ZNf5y+T9XBCDQ==} - cpu: [arm64] - os: [openbsd] + '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1': + resolution: {integrity: sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] - lefthook-openbsd-x64@2.1.1: - resolution: {integrity: sha512-ch3lyMUtbmtWUufaQVn4IoEs/2hjK51XqaCdY1mh5ca//VctR1peknIwQ5feHu+vATCDviWQ7HsdNDewm3HMPg==} + '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1': + resolution: {integrity: sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==} + engines: {node: '>= 10'} cpu: [x64] - os: [openbsd] + os: [linux] - lefthook-windows-arm64@2.1.1: - resolution: {integrity: sha512-mm3PZhKDs9FE/jQDimkfWxtoj9xQ2k8uw2MdhtC825bhvIh+MEi0WFj/MOW+ug0RBg0I55tGYzZ5aVuozAWpTQ==} + '@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1': + resolution: {integrity: sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - lefthook-windows-x64@2.1.1: - resolution: {integrity: sha512-1L2oGIzmhfOTxfwbe5mpSQ+m3ilpvGNymwIhn4UHq6hwHsUL6HEhODqx02GfBn6OXpVIr56bvdBAusjL/SVYGQ==} + '@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.1': + resolution: {integrity: sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.1': + resolution: {integrity: sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - lefthook@2.1.1: - resolution: {integrity: sha512-Tl9h9c+sG3ShzTHKuR3LAIblnnh+Mgxnm2Ul7yu9cu260Z27LEbO3V6Zw4YZFP59/2rlD42pt/llYsQCkkCFzw==} - hasBin: true + '@nomicfoundation/solidity-analyzer@0.1.1': + resolution: {integrity: sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==} + engines: {node: '>= 12'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + '@nuxt/cli@3.31.3': + resolution: {integrity: sha512-K0T1ZpBXnlb41NU/RWf1F0U0C14KzlEXCoaSgD2y8BiLoCBWcgQ1UAlRtx4cThqWbJmIxaNZZTDL0NZ9d1U7ag==} + engines: {node: ^16.10.0 || >=18.0.0} + hasBin: true - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + '@nuxt/devalue@2.0.2': + resolution: {integrity: sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + '@nuxt/devtools-kit@2.7.0': + resolution: {integrity: sha512-MIJdah6CF6YOW2GhfKnb8Sivu6HpcQheqdjOlZqShBr+1DyjtKQbAKSCAyKPaoIzZP4QOo2SmTFV6aN8jBeEIQ==} + peerDependencies: + vite: '>=6.0' - lodash.get@4.4.2: - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + '@nuxt/devtools-wizard@2.7.0': + resolution: {integrity: sha512-iWuWR0U6BRpF7D6xrgq9ZkQ6ajsw2EA/gVmbU9V5JPKRUtV6DVpCPi+h34VFNeQ104Sf531XgvT0sl3h93AjXA==} + hasBin: true - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + '@nuxt/devtools@2.7.0': + resolution: {integrity: sha512-BtIklVYny14Ykek4SHeexAHoa28MEV9kz223ZzvoNYqE0f+YVV+cJP69ovZHf+HUVpxaAMJfWKLHXinWXiCZ4Q==} + hasBin: true + peerDependencies: + vite: '>=6.0' - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + '@nuxt/kit@3.19.0': + resolution: {integrity: sha512-kecjqWORKdy7xnsOf/X4NtsFMbn+hNiiYhsA+INYVbgoyhCdx1rpEURCJdQITzHehXy5QWINTqmjNGp//PO4CQ==} + engines: {node: '>=18.12.0'} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + '@nuxt/kit@3.20.2': + resolution: {integrity: sha512-laqfmMcWWNV1FsVmm1+RQUoGY8NIJvCRl0z0K8ikqPukoEry0LXMqlQ+xaf8xJRvoH2/78OhZmsEEsUBTXipcw==} + engines: {node: '>=18.12.0'} - log-symbols@3.0.0: - resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==} - engines: {node: '>=8'} + '@nuxt/schema@3.11.2': + resolution: {integrity: sha512-Z0bx7N08itD5edtpkstImLctWMNvxTArsKXzS35ZuqyAyKBPcRjO1CU01slH0ahO30Gg9kbck3/RKNZPwfOjJg==} + engines: {node: ^14.18.0 || >=16.10.0} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + '@nuxt/schema@3.19.0': + resolution: {integrity: sha512-V3vgqXFruyOe7s8nBt+6OD+yQ18tqq5aLF7KPNJ5OaBvdWy24S5s4SbhUfgCNJmVmmsXsbxgHi9amz5uRFiaIw==} + engines: {node: ^14.18.0 || >=16.10.0} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + '@nuxt/telemetry@2.6.6': + resolution: {integrity: sha512-Zh4HJLjzvm3Cq9w6sfzIFyH9ozK5ePYVfCUzzUQNiZojFsI2k1QkSBrVI9BGc6ArKXj/O6rkI6w7qQ+ouL8Cag==} + engines: {node: '>=18.12.0'} hasBin: true - lower-case-first@1.0.2: - resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==} + '@nuxt/ui-templates@1.3.3': + resolution: {integrity: sha512-3BG5doAREcD50dbKyXgmjD4b1GzY8CUy3T41jMhHZXNDdaNwOd31IBq+D6dV00OSrDVhzrTVj0IxsUsnMyHvIQ==} - lower-case@1.1.4: - resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} - - lru-cache@11.2.4: - resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} - engines: {node: 20 || >=22} + '@nuxt/vite-builder@3.19.0': + resolution: {integrity: sha512-a7Zn4+smxMhZqXlDLE2fhHZfYBeOVYtjeg1s/yK/1T5LWOqHu9NGtNhJT7notYM491Mrp5eY9UaaiHlmTnI0dA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vue: ^3.3.4 - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - magicast@0.5.1: - resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + '@oven/bun-darwin-aarch64@1.1.30': + resolution: {integrity: sha512-D07QioP+QXlouvIqQIS+7r2zq4lTNd6he79rhKsRQRZGFf9i3NPu87zspUpCaFEu//DZ35DYTt+5anQpAzpoxA==} + cpu: [arm64] + os: [darwin] - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + '@oven/bun-darwin-x64-baseline@1.1.30': + resolution: {integrity: sha512-1kFUCxHx7WuEbLDmqm0m2UKBd3S4Ln6qKQ4gxU4umMLFkmvDJn6PszDruFInxGKFLoTAmbXNYNVWkkG/ekt/Lg==} + cpu: [x64] + os: [darwin] - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} + '@oven/bun-darwin-x64@1.1.30': + resolution: {integrity: sha512-xZ4gTehS6QwN6bsJfDycCNneKoUMaFUQhQg24bJzXS4JPDxeKg1W7PS5AE+U9apz5Dx6//+D4RwVpAPG2LXt0w==} + cpu: [x64] + os: [darwin] - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + '@oven/bun-linux-aarch64@1.1.30': + resolution: {integrity: sha512-SfHHLlph6fptDXyyChcUkeDbEZr2ww1p2BucV6OrvzwTOPi8pVmXA4360YT8ggR/3AHPp4GO36VaD+FU2Ocbxw==} + cpu: [arm64] + os: [linux] - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + '@oven/bun-linux-x64-baseline@1.1.30': + resolution: {integrity: sha512-/b/VuNOaAYmsVk9MvfwKcCYARJPUg78hebxNyD5DSajAf3dqtUSnf7QYcq/3mxWH++N+gM7uRTrGksGS63+ZUw==} + cpu: [x64] + os: [linux] - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + '@oven/bun-linux-x64@1.1.30': + resolution: {integrity: sha512-1mC39jQSaECytEKAZdCZmv3ZreMsp7aoxnBwmJtVd2Z7urnw17PKi4dKkZd/R+AubsNYtXtW4jeM8SEa5sUJRw==} + cpu: [x64] + os: [linux] - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} + '@oven/bun-windows-x64-baseline@1.1.30': + resolution: {integrity: sha512-ERQ4/ogzbFvHjpyHcnruc8bnryvDvUoiWi6vczfQ4M/idJc+Kg5VSEJiF5k7946rIZGamG6QWgRxtpIglD4/Zw==} + cpu: [x64] + os: [win32] - minimatch@10.2.2: - resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} - engines: {node: 18 || 20 || >=22} + '@oven/bun-windows-x64@1.1.30': + resolution: {integrity: sha512-mdRjNtD9NIA8CiH6N1zrIVE6oAtDko/c29H1s00UA+5O/WhXhg95G8IyInD8hN3vAEz8H2lGBgLG2EGfSFxnGg==} + cpu: [x64] + os: [win32] - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + '@oxc-minify/binding-android-arm64@0.86.0': + resolution: {integrity: sha512-jOgbDgp6A1ax9sxHPRHBxUpxIzp2VTgbZ/6HPKIVUJ7IQqKVsELKFXIOEbCDlb1rUhZZtGf53MFypXf72kR5eQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + '@oxc-minify/binding-darwin-arm64@0.86.0': + resolution: {integrity: sha512-LQkjIHhIzxVYnxfC2QV7MMe4hgqIbwK07j+zzEsNWWfdmWABw11Aa6FP0uIvERmoxstzsDT77F8c/+xhxswKiw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} + '@oxc-minify/binding-darwin-x64@0.86.0': + resolution: {integrity: sha512-AuLkeXIvJ535qOhFzZfHBkcEZA59SN1vKUblW2oN+6ClZfIMru0I2wr0cCHA9QDxIVDkI7swDu29qcn2AqKdrg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] - mipd@0.0.7: - resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true + '@oxc-minify/binding-freebsd-x64@0.86.0': + resolution: {integrity: sha512-UcXLcM8+iHW1EL+peHHV1HDBFUVdoxFMJC7HBc2U83q9oiF/K73TnAEgW/xteR+IvbV/9HD+cQsH+DX6oBXoQg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [freebsd] - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true + '@oxc-minify/binding-linux-arm-gnueabihf@0.86.0': + resolution: {integrity: sha512-UtSplQY10Idp//cLS5i2rFaunS71padZFavHLHygNAxJBt+37DPKDl/4kddpV6Kv2Mr6bhw2KpXGAVs0C3dIOw==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} + '@oxc-minify/binding-linux-arm-musleabihf@0.86.0': + resolution: {integrity: sha512-P5efCOl9QiwqqJHrw1Q+4ssexvOz+MAmgTmBorbdEM3WJdIHR1CWGDj4GqcvKBlwpBqt4XilOuoN0QD8dfl85A==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + '@oxc-minify/binding-linux-arm64-gnu@0.86.0': + resolution: {integrity: sha512-hwHahfs//g9iZLQmKldjQPmnpzq76eyHvfkmdnXXmPtwTHnwXL1hPlNbTIqakUirAsroBeQwXqzHm3I040R+mg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + '@oxc-minify/binding-linux-arm64-musl@0.86.0': + resolution: {integrity: sha512-S2dL24nxWqDCwrq48xlZBvhSIBcEWOu3aDOiaccP4q73PiTLrf6rm1M11J7vQNSRiH6ao9UKr7ZMsepCZcOyfA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + '@oxc-minify/binding-linux-riscv64-gnu@0.86.0': + resolution: {integrity: sha512-itZ24A1a5NOw0ibbt6EYOHdBojfV4vbiC209d06Dwv5WLXtntHCjc8P4yfrCsC22uDmMPNkVa+UL+OM4mkUrwg==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + '@oxc-minify/binding-linux-s390x-gnu@0.86.0': + resolution: {integrity: sha512-/nJAwS/uit19qXNpaOybf7GYJI7modbXYVZ8q1pIFdxs6HkhZLxS1ZvcIzY3W75+37u+uKeZ4MbygawAN8kQpQ==} + engines: {node: '>=14.0.0'} + cpu: [s390x] + os: [linux] - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + '@oxc-minify/binding-linux-x64-gnu@0.86.0': + resolution: {integrity: sha512-3qnWZB2cOj5Em/uEJqJ1qP/8lxtoi/Rf1U8fmdLzPW5zIaiTRUr/LklB4aJ+Vc/GU5g3HX5nFPQG3ZnEV3Ktzg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} + '@oxc-minify/binding-linux-x64-musl@0.86.0': + resolution: {integrity: sha512-+ZqYG8IQSRq9dR2djrnyzGHlmwGRKdueVjHYbEOwngb/4h/+FxAOaNUbsoUsCthAfXTrZHVXiQMTKJ32r7j2Bg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] - next@15.5.14: - resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true + '@oxc-minify/binding-wasm32-wasi@0.86.0': + resolution: {integrity: sha512-ixeSZW7jzd3g9fh8MoR9AzGLQxMCo//Q2mVpO2S/4NmcPtMaJEog85KzHULgUvbs70RqxTHEUqtVgpnc/5lMWA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] - no-case@2.3.2: - resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} + '@oxc-minify/binding-win32-arm64-msvc@0.86.0': + resolution: {integrity: sha512-cN309CnFVG8jeSRd+lQGnoMpZAVmz4bzH4fgqJM0NsMXVnFPGFceG/XiToLoBA1FigGQvkV0PJ7MQKWxBHPoUA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] - node-plop@0.26.3: - resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} - engines: {node: '>=8.9.4'} + '@oxc-minify/binding-win32-x64-msvc@0.86.0': + resolution: {integrity: sha512-YAqCKtZ9KKhSW73d/Oa9Uut0myYnCEUL2D0buMjJ4p0PuK1PQsMCJsmX4ku0PgK31snanZneRwtEjjNFYNdX2A==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + '@oxc-parser/binding-android-arm64@0.86.0': + resolution: {integrity: sha512-BfNFEWpRo4gqLHKvRuQmhbPGeJqB1Ka/hsPhKf1imAojwUcf/Dr/yRkZBuEi2yc1LWBjApKYJEqpsBUmtqSY1Q==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [android] - nodemon@3.1.14: - resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} - engines: {node: '>=10'} - hasBin: true + '@oxc-parser/binding-darwin-arm64@0.86.0': + resolution: {integrity: sha512-gRSnEHcyNEfLdNj6v8XKcuHUaZnRpH2lOZFztuGEi23ENydPOQVEtiZYexuHOTeaLGgzw+93TgB4n/YkjYodug==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + '@oxc-parser/binding-darwin-x64@0.86.0': + resolution: {integrity: sha512-6mdymm8i+VpLTJP19D3PSFumMmAyfhhhIRWcRHsc0bL7CSZjCWbvRb00ActKrGKWtsol/A/KKgqglJwpvjlzOA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + '@oxc-parser/binding-freebsd-x64@0.86.0': + resolution: {integrity: sha512-mc2xYRPxhzFg4NX1iqfIWP+8ORtXiNpAkaomNDepegQFlIFUmrESa3IJrKJ/4vg77Tbti7omHbraOqwdTk849g==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [freebsd] - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + '@oxc-parser/binding-linux-arm-gnueabihf@0.86.0': + resolution: {integrity: sha512-LZzapjFhwGQMKefcFsn3lJc/mTY37fBlm0jjEvETgNCyd5pH4gDwOcrp/wZHAz2qw5uLWOHaa69I6ci5lBjJgA==} + engines: {node: '>=20.0.0'} + cpu: [arm] + os: [linux] - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-arm-musleabihf@0.86.0': + resolution: {integrity: sha512-/rhJMpng7/Qgn8hE4sigxTRb04+zdO0K1kfAMZ3nONphk5r2Yk2RjyEpLLz17adysCyQw/KndaMHNv8GR8VMNg==} + engines: {node: '>=20.0.0'} + cpu: [arm] + os: [linux] - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-arm64-gnu@0.86.0': + resolution: {integrity: sha512-IXEZnk6O0zJg5gDn1Zvt5Qx62Z3E+ewrKwPgMfExqnNCLq+Ix2g7hQypevm/S6qxVgyz5HbiW+a/5ziMFXTCJQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-arm64-musl@0.86.0': + resolution: {integrity: sha512-QG7DUVZ/AtBaUGMhgToB4glOdq0MGAEYU1MJQpNB5HqiEcOpteF9Pd+oPfscj2zrGPd47KNyljtJRBKJr6Ut0w==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-riscv64-gnu@0.86.0': + resolution: {integrity: sha512-smz+J6riX2du2lp0IKeZSaOBIhhoE2N/L1IQdOLCpzB0ikjCDBoyNKdDM7te8ZDq3KDnRmJChmhQGd8P1/LGBQ==} + engines: {node: '>=20.0.0'} + cpu: [riscv64] + os: [linux] - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-s390x-gnu@0.86.0': + resolution: {integrity: sha512-vas1BOMWVdicuimmi5Y+xPj3csaYQquVA45Im9a/DtVsypVeh8RWYXBMO1qJNM5Fg5HD0QvYNqxvftx3c+f5pg==} + engines: {node: '>=20.0.0'} + cpu: [s390x] + os: [linux] - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} + '@oxc-parser/binding-linux-x64-gnu@0.86.0': + resolution: {integrity: sha512-3Fsi+JA3NwdZdrpC6AieOP48cuBrq0q59JgnR0mfoWfr9wHrbn2lt8EEubrj6EXpBUmu1Zii7S9NNRC6fl/d+w==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + '@oxc-parser/binding-linux-x64-musl@0.86.0': + resolution: {integrity: sha512-89/d43EW76wJagz8u5zcKW8itB2rnS/uN7un5APb8Ebme8TePBwDyxo64J6oY5rcJYkfJ6lEszSF/ovicsNVPw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + '@oxc-parser/binding-wasm32-wasi@0.86.0': + resolution: {integrity: sha512-gRrGmE2L27stNMeiAucy/ffHF9VjYr84MizuJzSYnnKmd5WXf3HelNdd0UYSJnpb7APBuyFSN2Oato+Qb6yAFw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + '@oxc-parser/binding-win32-arm64-msvc@0.86.0': + resolution: {integrity: sha512-parTnpNviJYR3JIFLseDGip1KkYbhWLeuZG9OMek62gr6Omflddoytvb17s+qODoZqFAVjvuOmVipDdjTl9q3Q==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} + '@oxc-parser/binding-win32-x64-msvc@0.86.0': + resolution: {integrity: sha512-FTso24eQh3vPTe/SOTf0/RXfjJ13tsk5fw728fm+z5y6Rb+mmEBfyVT6XxyGhEwtdfnRSZawheX74/9caI1etw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] - ora@4.1.1: - resolution: {integrity: sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==} - engines: {node: '>=8'} + '@oxc-project/runtime@0.82.3': + resolution: {integrity: sha512-LNh5GlJvYHAnMurO+EyA8jJwN1rki7l3PSHuosDh2I7h00T6/u9rCkUjg/SvPmT1CZzvhuW0y+gf7jcqUy/Usg==} + engines: {node: '>=6.9.0'} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} + '@oxc-project/types@0.82.3': + resolution: {integrity: sha512-6nCUxBnGX0c6qfZW5MaF6/fmu5dHJDMiMPaioKHKs5mi5+8/FHQ7WGjgQIz1zxpmceMYfdIXkOaLYE+ejbuOtA==} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + '@oxc-project/types@0.86.0': + resolution: {integrity: sha512-bJ57vWNQnOnUe5ZxUkrWpLyExxqb0BoyQ+IRmI/V1uxHbBNBzFGMIjKIf5ECFsgS0KgUUl8TM3a4xpeAtAnvIA==} - outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + '@oxc-transform/binding-android-arm64@0.86.0': + resolution: {integrity: sha512-025JJoCWi04alNef6WvLnGCbx2MH9Ld2xvr0168bpOcpBjxt8sOZawu0MPrZQhnNWWiX8rrwrhuUDasWCWHxFw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} + '@oxc-transform/binding-darwin-arm64@0.86.0': + resolution: {integrity: sha512-dJls3eCO1Y2dc4zAdA+fiRbQwlvFFDmfRHRpGOllwS1FtvKQ7dMkRFKsHODEdxWakxISLvyabUmkGOhcJ47Dog==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] - ox@0.9.17: - resolution: {integrity: sha512-rKAnhzhRU3Xh3hiko+i1ZxywZ55eWQzeS/Q4HRKLx2PqfHOolisZHErSsJVipGlmQKHW5qwOED/GighEw9dbLg==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true + '@oxc-transform/binding-darwin-x64@0.86.0': + resolution: {integrity: sha512-udMZFZn6FEy36tVMs/yrczEqWyCJc+l/lqIMS4xYWsm/6qVafUWDSAZJLgcPilng16IdMnHINkc8NSz7Pp1EVw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] - p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} + '@oxc-transform/binding-freebsd-x64@0.86.0': + resolution: {integrity: sha512-41J5qSlypbE0HCOd+4poFD96+ZKoR8sfDn5qdaU0Hc5bT5Drwat/wv06s9Y5Lu86uXYTwPPj6kbbxHHsiV2irw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [freebsd] - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + '@oxc-transform/binding-linux-arm-gnueabihf@0.86.0': + resolution: {integrity: sha512-mrI+nKgwRsr4FYjb0pECrNTVnNvHAflukS3SFqFHI8n+3LJgrCYDcnbrFD/4VWKp2EUrkIZ//RhwgGsTiSXbng==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + '@oxc-transform/binding-linux-arm-musleabihf@0.86.0': + resolution: {integrity: sha512-FXWyvpxiEXBewA3L6HGFtEribqFjGOiounD8ke/4C1F5134+rH5rNrgK6vY116P4MtWKfZolMRdvlzaD3TaX0A==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + '@oxc-transform/binding-linux-arm64-gnu@0.86.0': + resolution: {integrity: sha512-gktU/9WLAc0d2hAq8yRi3K92xwkWoDt1gJmokMOfb1FU4fyDbzbt13jdZEd6KVn2xLaiQeaFTTfFTghFsJUM3A==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + '@oxc-transform/binding-linux-arm64-musl@0.86.0': + resolution: {integrity: sha512-2w5e5qiTBYQ0xc1aSY1GNyAOP9BQFEjN43FI3OhrRWZXHOj3inqcVSlptO/hHGK3Q2bG26kWLfSNFOEylTX39A==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} + '@oxc-transform/binding-linux-riscv64-gnu@0.86.0': + resolution: {integrity: sha512-PfnTYm+vQ9X5VNXqs0Z3S67Xp2FoZj5RteYKUNwL+j/sxGi05eps+EWLVrcGsuN9x2GHFpTiqBz3lzERCn2USg==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] - p-map@3.0.0: - resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} - engines: {node: '>=8'} + '@oxc-transform/binding-linux-s390x-gnu@0.86.0': + resolution: {integrity: sha512-uHgGN0rFfqDcdkLUITshqrpV34PRKAiRwsw6Jgkg7CRcRGIU8rOJc568EU0jfhTZ1zO5MJKt/S8D6cgIFJwe0A==} + engines: {node: '>=14.0.0'} + cpu: [s390x] + os: [linux] - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + '@oxc-transform/binding-linux-x64-gnu@0.86.0': + resolution: {integrity: sha512-MtrvfU2RkSD+oTnzG4Xle3jK8FXJPQa1MhYQm0ivcAMf0tUQDojTaqBtM/9E0iFr/4l1xZODJOHCGjLktdpykg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} + '@oxc-transform/binding-linux-x64-musl@0.86.0': + resolution: {integrity: sha512-wTTTIPcnoS04SRJ7HuOL/VxIu1QzUtv2n6Mx0wPIEQobj2qPGum0qYGnFEMU0Njltp+8FAUg5EfX6u3udRQBbQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} + '@oxc-transform/binding-wasm32-wasi@0.86.0': + resolution: {integrity: sha512-g+0bf+ZA2DvBHQ+0u8TvEY8ERo86Brqvdghfv06Wph2qGTlhzSmrE0c0Zurr7yhtqI5yZjMaBr2HbqwW1kHFng==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + '@oxc-transform/binding-win32-arm64-msvc@0.86.0': + resolution: {integrity: sha512-dgBeU4qBEag0rhW3OT9YHgj4cvW51KZzrxhDQ1gAVX2fqgl+CeJnu0a9q+DMhefHrO3c8Yxwbt7NxUDmWGkEtg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] - package-manager-detector@0.2.11: - resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + '@oxc-transform/binding-win32-x64-msvc@0.86.0': + resolution: {integrity: sha512-M1eCl8xz7MmEatuqWdr+VdvNCUJ+d4ECF+HND39PqRCVkaH+Vl1rcyP5pLILb2CB/wTb2DMvZmb9RCt5+8S5TQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] - param-case@2.1.1: - resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] - pascal-case@2.0.1: - resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==} + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] - path-case@2.1.1: - resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==} + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] - path-key@3.1.1: + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-wasm@2.4.1': + resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==} + engines: {node: '>= 10.0.0'} + bundledDependencies: + - napi-wasm + + '@parcel/watcher-wasm@2.5.1': + resolution: {integrity: sha512-RJxlQQLkaMMIuWRozy+z2vEqbaQlCuaCgVZIUCzQLYggY22LZbP5Y1+ia+FD724Ids9e+XIyOLXLrLgQSHIthw==} + engines: {node: '>= 10.0.0'} + bundledDependencies: + - napi-wasm + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@paulmillr/qr@0.2.1': + resolution: {integrity: sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==} + deprecated: 'The package is now available as "qr": npm install qr' + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + + '@publint/pack@0.1.2': + resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} + engines: {node: '>=18'} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + 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-aspect-ratio@1.1.8': + resolution: {integrity: sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==} + 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-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + 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-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + 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-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + 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-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + 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-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + 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-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + 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-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + 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-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==} + 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==} + 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-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + 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-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + 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-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + 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-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + 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-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + 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-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + 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-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + 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-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + 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-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==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + 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-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + 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-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + 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-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + 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-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==} + 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-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + 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-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + 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-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + 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-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + 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-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + 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-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + 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==} + 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/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@react-oauth/google@0.11.1': + resolution: {integrity: sha512-tywZisXbsdaRBVbEu0VX6dRbOSL2I6DgY97woq5NMOOOz+xtDsm418vqq+Vx10KMtra3kdHMRMf0hXLWrk2RMg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@reown/appkit-common@1.7.3': + resolution: {integrity: sha512-wKTr6N3z8ly17cc51xBEVkZK4zAd8J1m7RubgsdQ1olFY9YJGe61RYoNv9yFjt6tUVeYT+z7iMUwPhX2PziefQ==} + + '@reown/appkit-controllers@1.7.3': + resolution: {integrity: sha512-aqAcX/nZe0gwqjncyCkVrAk3lEw0qZ9xGrdLOmA207RreO4J0Vxu8OJXCBn4C2AUI2OpBxCPah+vyuKTUJTeHQ==} + + '@reown/appkit-polyfills@1.7.3': + resolution: {integrity: sha512-vQUiAyI7WiNTUV4iNwv27iigdeg8JJTEo6ftUowIrKZ2/gtE2YdMtGpavuztT/qrXhrIlTjDGp5CIyv9WOTu4g==} + + '@reown/appkit-scaffold-ui@1.7.3': + resolution: {integrity: sha512-ssB15fcjmoKQ+VfoCo7JIIK66a4SXFpCH8uK1CsMmXmKIKqPN54ohLo291fniV6mKtnJxh5Xm68slGtGrO3bmA==} + + '@reown/appkit-ui@1.7.3': + resolution: {integrity: sha512-zKmFIjLp0X24pF9KtPtSHmdsh/RjEWIvz+faIbPGm4tQbwcxdg9A35HeoP0rMgKYx49SX51LgPwVXne2gYacqQ==} + + '@reown/appkit-utils@1.7.3': + resolution: {integrity: sha512-8/MNhmfri+2uu8WzBhZ5jm5llofOIa1dyXDXRC/hfrmGmCFJdrQKPpuqOFYoimo2s2g70pK4PYefvOKgZOWzgg==} + + '@reown/appkit-wallet@1.7.3': + resolution: {integrity: sha512-D0pExd0QUE71ursQPp3pq/0iFrz2oz87tOyFifrPANvH5X0RQCYn/34/kXr+BFVQzNFfCBDlYP+CniNA/S0KiQ==} + + '@reown/appkit@1.7.3': + resolution: {integrity: sha512-aA/UIwi/dVzxEB62xlw3qxHa3RK1YcPMjNxoGj/fHNCqL2qWmbcOXT7coCUa9RG7/Bh26FZ3vdVT2v71j6hebQ==} + + '@rolldown/binding-android-arm64@1.0.0-beta.35': + resolution: {integrity: sha512-zVTg0544Ib1ldJSWwjy8URWYHlLFJ98rLnj+2FIj5fRs4KqGKP4VgH/pVUbXNGxeLFjItie6NSK1Un7nJixneQ==} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.35': + resolution: {integrity: sha512-WPy0qx22CABTKDldEExfpYHWHulRoPo+m/YpyxP+6ODUPTQexWl8Wp12fn1CVP0xi0rOBj7ugs6+kKMAJW56wQ==} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.35': + resolution: {integrity: sha512-3k1TabJafF/GgNubXMkfp93d5p30SfIMOmQ5gm1tFwO+baMxxVPwDs3FDvSl+feCWwXxBA+bzemgkaDlInmp1Q==} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.35': + resolution: {integrity: sha512-GAiapN5YyIocnBVNEiOxMfWO9NqIeEKKWohj1sPLGc61P+9N1meXOOCiAPbLU+adXq0grtbYySid+Or7f2q+Mg==} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.35': + resolution: {integrity: sha512-okPKKIE73qkUMvq7dxDyzD0VIysdV4AirHqjf8tGTjuNoddUAl3WAtMYbuZCEKJwUyI67UINKO1peFVlYEb+8w==} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.35': + resolution: {integrity: sha512-Nky8Q2cxyKVkEETntrvcmlzNir5khQbDfX3PflHPbZY7XVZalllRqw7+MW5vn+jTsk5BfKVeLsvrF4344IU55g==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.35': + resolution: {integrity: sha512-8aHpWVSfZl3Dy2VNFG9ywmlCPAJx45g0z+qdOeqmYceY7PBAT4QGzii9ig1hPb1pY8K45TXH44UzQwr2fx352Q==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.35': + resolution: {integrity: sha512-1r1Ac/vTcm1q4kRiX/NB6qtorF95PhjdCxKH3Z5pb+bWMDZnmcz18fzFlT/3C6Qpj/ZqUF+EUrG4QEDXtVXGgg==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.35': + resolution: {integrity: sha512-AFl1LnuhUBDfX2j+cE6DlVGROv4qG7GCPDhR1kJqi2+OuXGDkeEjqRvRQOFErhKz1ckkP/YakvN7JheLJ2PKHQ==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.35': + resolution: {integrity: sha512-Tuwb8vPs+TVJlHhyLik+nwln/burvIgaPDgg6wjNZ23F1ttjZi0w0rQSZfAgsX4jaUbylwCETXQmTp3w6vcJMw==} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.35': + resolution: {integrity: sha512-rG0OozgqNUYcpu50MpICMlJflexRVtQfjlN9QYf6hoel46VvY0FbKGwBKoeUp2K5D4i8lV04DpEMfTZlzRjeiA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.35': + resolution: {integrity: sha512-WeOfAZrycFo9+ZqTDp3YDCAOLolymtKGwImrr9n+OW0lpwI2UKyKXbAwGXRhydAYbfrNmuqWyfyoAnLh3X9Hjg==} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.35': + resolution: {integrity: sha512-XkLT7ikKGiUDvLh7qtJHRukbyyP1BIrD1xb7A+w4PjIiOKeOH8NqZ+PBaO4plT7JJnLxx+j9g/3B7iylR1nTFQ==} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.35': + resolution: {integrity: sha512-rftASFKVzjbcQHTCYHaBIDrnQFzbeV50tm4hVugG3tPjd435RHZC2pbeGV5IPdKEqyJSuurM/GfbV3kLQ3LY/A==} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.35': + resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + + '@rolldown/pluginutils@1.0.0-beta.57': + resolution: {integrity: sha512-aQNelgx14tGA+n2tNSa9x6/jeoCL9fkDeCei7nOKnHx0fEFRRMu5ReiITo+zZD5TzWDGGRjbSYCs93IfRIyTuQ==} + + '@rollup/plugin-alias@5.1.1': + resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-commonjs@28.0.9': + resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-inject@5.0.5': + resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@6.0.3': + resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-terser@0.4.4': + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + + '@safe-global/safe-apps-provider@0.18.6': + resolution: {integrity: sha512-4LhMmjPWlIO8TTDC2AwLk44XKXaK6hfBTWyljDm0HQ6TWlOEijVWNrt2s3OCVMSxlXAcEzYfqyu1daHZooTC2Q==} + + '@safe-global/safe-apps-sdk@9.1.0': + resolution: {integrity: sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==} + + '@safe-global/safe-gateway-typescript-sdk@3.8.0': + resolution: {integrity: sha512-CiGWIHgIaOdICpDxp05Jw3OPslWTu8AnL0PhrCT1xZgIO86NlMMLzkGbeycJ4FHpTjA999O791Oxp4bZPIjgHA==} + + '@scure/base@1.1.3': + resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} + + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.1.5': + resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} + + '@scure/bip32@1.3.2': + resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} + + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + + '@scure/bip32@1.6.2': + resolution: {integrity: sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.1.1': + resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} + + '@scure/bip39@1.2.1': + resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} + + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + + '@scure/bip39@1.5.4': + resolution: {integrity: sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sentry/core@5.30.0': + resolution: {integrity: sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==} + engines: {node: '>=6'} + + '@sentry/hub@5.30.0': + resolution: {integrity: sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==} + engines: {node: '>=6'} + + '@sentry/minimal@5.30.0': + resolution: {integrity: sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==} + engines: {node: '>=6'} + + '@sentry/node@5.30.0': + resolution: {integrity: sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==} + engines: {node: '>=6'} + + '@sentry/tracing@5.30.0': + resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} + engines: {node: '>=6'} + + '@sentry/types@5.30.0': + resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} + engines: {node: '>=6'} + + '@sentry/utils@5.30.0': + resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} + engines: {node: '>=6'} + + '@shikijs/core@1.22.2': + resolution: {integrity: sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg==} + + '@shikijs/engine-javascript@1.22.2': + resolution: {integrity: sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw==} + + '@shikijs/engine-oniguruma@1.22.2': + resolution: {integrity: sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA==} + + '@shikijs/transformers@1.22.2': + resolution: {integrity: sha512-8f78OiBa6pZDoZ53lYTmuvpFPlWtevn23bzG+azpPVvZg7ITax57o/K3TC91eYL3OMJOO0onPbgnQyZjRos8XQ==} + + '@shikijs/twoslash@1.22.2': + resolution: {integrity: sha512-4R3A7aH/toZgtlveXHKk01nIsvn8hjAfPJ1aT550zcV4qK6vK/tfaEyYtaljOaY1wig2l5+8sKjNSEz3PcSiEw==} + + '@shikijs/types@1.22.2': + resolution: {integrity: sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg==} + + '@shikijs/vitepress-twoslash@1.22.2': + resolution: {integrity: sha512-F4cS9l6QTt/ILlz+S871bOido2CK8Xwdnl4q9gHdnTuZlN1PHAMRZbAOvYAtokvouPp9aZ2W7NtNa+CNCn3yPQ==} + + '@shikijs/vscode-textmate@9.3.0': + resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} + + '@sinclair/typebox@0.25.24': + resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/is@7.1.1': + resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} + engines: {node: '>=18'} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@smithy/abort-controller@4.2.7': + resolution: {integrity: sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.5': + resolution: {integrity: sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.20.0': + resolution: {integrity: sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.7': + resolution: {integrity: sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.8': + resolution: {integrity: sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.7': + resolution: {integrity: sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.7': + resolution: {integrity: sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.7': + resolution: {integrity: sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.1': + resolution: {integrity: sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.17': + resolution: {integrity: sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.8': + resolution: {integrity: sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.7': + resolution: {integrity: sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.7': + resolution: {integrity: sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.7': + resolution: {integrity: sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.7': + resolution: {integrity: sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.7': + resolution: {integrity: sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.7': + resolution: {integrity: sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.7': + resolution: {integrity: sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.7': + resolution: {integrity: sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.2': + resolution: {integrity: sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.7': + resolution: {integrity: sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.10.2': + resolution: {integrity: sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.11.0': + resolution: {integrity: sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.7': + resolution: {integrity: sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.16': + resolution: {integrity: sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.19': + resolution: {integrity: sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.7': + resolution: {integrity: sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.7': + resolution: {integrity: sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.7': + resolution: {integrity: sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.8': + resolution: {integrity: sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + + '@snyk/github-codeowners@1.1.0': + resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==} + engines: {node: '>=8.10'} + hasBin: true + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@solidity-parser/parser@0.20.2': + resolution: {integrity: sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==} + + '@speed-highlight/core@1.2.12': + resolution: {integrity: sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/cli@4.1.18': + resolution: {integrity: sha512-sMZ+lZbDyxwjD2E0L7oRUjJ01Ffjtme5OtjvvnC+cV4CEDcbqzbp25TCpxHj6kWLU9+DlqJOiNgSOgctC2aZmg==} + hasBin: true + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tanstack/match-sorter-utils@8.15.1': + resolution: {integrity: sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.0.5': + resolution: {integrity: sha512-MThCETMkHDHTnFZHp71L+SqTtD5d6XHftFCVR1xRJdWM3qGrlQ2VCXaj0SKVcyJej2e1Opa2c7iknu1llxCDNQ==} + + '@tanstack/query-core@5.49.1': + resolution: {integrity: sha512-JnC9ndmD1KKS01Rt/ovRUB1tmwO7zkyXAyIxN9mznuJrcNtOrkmOnQqdJF2ib9oHzc2VxHomnEG7xyfo54Npkw==} + + '@tanstack/query-core@5.90.15': + resolution: {integrity: sha512-mInIZNUZftbERE+/Hbtswfse49uUQwch46p+27gP9DWJL927UjnaWEF2t3RMOqBcXbfMdcNkPe06VyUIAZTV1g==} + + '@tanstack/query-devtools@5.0.5': + resolution: {integrity: sha512-xjuOhOrrO50sPoJ4WG9yPe3imQ0Ds/nutnmwdTqjM2ZTIkflh//p7q2iB6IxFBY9sB106h+PULlma8sgTuOKAQ==} + + '@tanstack/query-persist-client-core@5.0.5': + resolution: {integrity: sha512-xdxDiSN/gBG1QJBiyNZPv2y1DOBMrILvhrEd9PgtOzE1AswmgVUh96KENiD7QiABKCVVIihDtSDvJGj0ukbudg==} + + '@tanstack/query-sync-storage-persister@5.0.5': + resolution: {integrity: sha512-uk2/mcNf+YYVza3XaU61RSPCcIi/p+0DfsZWMyIim1yCxF7hzZ17zWheM/2v3zZbeTY/C6m1NIO9KIRiPAM9Mg==} + + '@tanstack/react-query-devtools@5.0.5': + resolution: {integrity: sha512-vJyS7HXx2zw43TQjm3m4uyaNUgGizOpK2SZL9Lc+DZSuhFbuZ55UEYJTz8yudCbHdLXlkuVZwo6TWWOhXWJFeA==} + peerDependencies: + '@tanstack/react-query': ^5.0.5 + react: ^18.0.0 + react-dom: ^18.0.0 + + '@tanstack/react-query-persist-client@5.0.5': + resolution: {integrity: sha512-V/jIKdiw0WyJYpnzwnKS+O19jgJPWSBDzvx9qFaXAm98Jnt+lGWFBZdUR0MgY2ufM1fbeejXTjcorgwqjc3kaA==} + peerDependencies: + '@tanstack/react-query': ^5.0.5 + react: ^18.0.0 + react-dom: ^18.0.0 + + '@tanstack/react-query@5.49.2': + resolution: {integrity: sha512-6rfwXDK9BvmHISbNFuGd+wY3P44lyW7lWiA9vIFGT/T0P9aHD1VkjTvcM4SDAIbAQ9ygEZZoLt7dlU1o3NjMVA==} + peerDependencies: + react: ^18.0.0 + + '@tanstack/react-query@5.90.15': + resolution: {integrity: sha512-uQvnDDcTOgJouNtAyrgRej+Azf0U5WDov3PXmHFUBc+t1INnAYhIlpZtCGNBLwCN41b43yO7dPNZu8xWkUFBwQ==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/vue-query@5.49.1': + resolution: {integrity: sha512-/nTqP8PNCRzMcqTGEuFQE3ntUD3A+K05r6Dw/0hNwNS3PLEaKUKlxytmAhIoaoloQwbbAghjLyKRQZ+CMWv90A==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.6.0 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/react@16.0.1': + resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@ts-morph/common@0.11.1': + resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@typechain/ethers-v6@0.5.1': + resolution: {integrity: sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==} + peerDependencies: + ethers: 6.x + typechain: ^8.3.2 + typescript: '>=4.7.0' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.5': + resolution: {integrity: sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==} + + '@types/babel__template@7.4.2': + resolution: {integrity: sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==} + + '@types/babel__traverse@7.20.2': + resolution: {integrity: sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==} + + '@types/bn.js@4.11.6': + resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} + + '@types/bn.js@5.1.5': + resolution: {integrity: sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==} + + '@types/bun@1.1.10': + resolution: {integrity: sha512-76KYVSwrHwr9zsnk6oLXOGs9KvyBg3U066GLO4rk6JZk1ypEPGCUDZ5yOiESyIHWs9cx9iC8r01utYN32XdmgA==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/cross-spawn@6.0.6': + resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/debug@4.1.7': + resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} + + '@types/dedent@0.7.2': + resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/lru-cache@5.1.1': + resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.3': + resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/ms@0.7.31': + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@16.18.11': + resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} + + '@types/node@20.12.10': + resolution: {integrity: sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==} + + '@types/node@20.12.14': + resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} + + '@types/node@20.19.27': + resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + + '@types/node@22.15.31': + resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + + '@types/node@24.0.1': + resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} + + '@types/parse-path@7.1.0': + resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} + deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. + + '@types/pbkdf2@3.1.2': + resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} + + '@types/prompts@2.4.9': + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/prop-types@15.7.5': + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.1': + resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/secp256k1@4.0.3': + resolution: {integrity: sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==} + + '@types/semver@7.5.3': + resolution: {integrity: sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==} + + '@types/statuses@2.0.4': + resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/trusted-types@2.0.3': + resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} + + '@types/unist@3.0.2': + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + + '@types/ws@8.5.10': + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + + '@typescript/vfs@1.6.0': + resolution: {integrity: sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==} + peerDependencies: + typescript: '*' + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@unhead/vue@2.1.1': + resolution: {integrity: sha512-WYa8ORhfv7lWDSoNpkMKhbW1Dbsux/3HqMcVkZS3xZ2/c/VrcChLj+IMadpCd1WNR0srITfRJhBYZ1i9hON5Qw==} + peerDependencies: + vue: '>=3.5.18' + + '@unocss/astro@0.59.4': + resolution: {integrity: sha512-DU3OR5MMR1Uvvec4/wB9EetDASHRg19Moy6z/MiIhn8JWJ0QzWYgSeJcfUX8exomMYv6WUEQJL+CyLI34Wmn8w==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 + peerDependenciesMeta: + vite: + optional: true + + '@unocss/cli@0.59.4': + resolution: {integrity: sha512-TT+WKedSifhsRqnpoYD2LfyYipVzEbzIU4DDGIaDNeDxGXYOGpb876zzkPDcvZSpI37IJ/efkkV7PGYpPBcQBQ==} + engines: {node: '>=14'} + hasBin: true + + '@unocss/config@0.59.4': + resolution: {integrity: sha512-h3yhj+D5Ygn5R7gbK4wMrtXZX6FF5DF6YD517sSSb0XB3lxHD9PhhT4HaV1hpHknvu0cMFU3460M45+TN1TI0Q==} + engines: {node: '>=14'} + + '@unocss/core@0.59.4': + resolution: {integrity: sha512-bBZ1sgcAtezQVZ1BST9IS3jqcsTLyqKNjiIf7FTnX3DHpfpYuMDFzSOtmkZDzBleOLO/CtcRWjT0HwTSQAmV0A==} + + '@unocss/extractor-arbitrary-variants@0.59.4': + resolution: {integrity: sha512-RDe4FgMGJQ+tp9GLvhPHni7Cc2O0lHBRMElVlN8LoXJAdODMICdbrEPGJlEfrc+7x/QgVFoR895KpYJh3hIgGA==} + + '@unocss/inspector@0.59.4': + resolution: {integrity: sha512-QczJFNDiggmekkJyNcbcZIUVwlhvxz7ZwjnSf0w7K4znxfjKkZ1hNUbqLviM1HumkTKOdT27VISW7saN/ysO4w==} + + '@unocss/postcss@0.59.4': + resolution: {integrity: sha512-KVz+AD7McHKp7VEWHbFahhyyVEo0oP/e1vnuNSuPlHthe+1V2zfH6lps+iJcvfL2072r5J+0PvD/1kOp5ryUSg==} + engines: {node: '>=14'} + + '@unocss/preset-attributify@0.59.4': + resolution: {integrity: sha512-BeogWuYaIakC1gmOZFFCjFVWmu/m3AqEX8UYQS6tY6lAaK2L4Qf4AstYBlT2zAMxy9LNxPDxFQrvfSfFk5Klsg==} + + '@unocss/preset-icons@0.59.4': + resolution: {integrity: sha512-Afjwh5oC4KRE8TNZDUkRK6hvvV1wKLrS1e5trniE0B0AM9HK3PBolQaIU7QmzPv6WQrog+MZgIwafg1eqsPUCA==} + + '@unocss/preset-mini@0.59.4': + resolution: {integrity: sha512-ZLywGrXi1OCr4My5vX2rLUb5Xgx6ufR9WTQOvpQJGBdIV/jnZn/pyE5avCs476SnOq2K172lnd8mFmTK7/zArA==} + + '@unocss/preset-tagify@0.59.4': + resolution: {integrity: sha512-vWMdTUoghOSmTbdmZtERssffmdUdOuhh4vUdl0R8Kv6KxB0PkvEFCu2FItn97nRJdSPlZSFxxDkaOIg9w+STNQ==} + + '@unocss/preset-typography@0.59.4': + resolution: {integrity: sha512-ZX9bxZUqlXK1qEDzO5lkK96ICt9itR/oNyn/7mMc1JPqwj263LumQMn5silocgzoLSUXEeq//L6GylqYjkL8GA==} + + '@unocss/preset-uno@0.59.4': + resolution: {integrity: sha512-G1f8ZluplvXZ3bERj+sM/8zzY//XD++nNOlAQNKOANSVht3qEoJebrfEiMClNpA5qW5VWOZhEhPkh0M7GsXtnA==} + + '@unocss/preset-web-fonts@0.59.4': + resolution: {integrity: sha512-ehutTjKHnf2KPmdatN42N9a8+y+glKSU3UlcBRNsVIIXVIlaBQuPVGZSPhnMtrKD17IgWylXq2K6RJK+ab0hZA==} + + '@unocss/preset-wind@0.59.4': + resolution: {integrity: sha512-CNX6w0ZpSQg/i1oF0/WKWzto8PtLqoknC5h8JmmcGb7VsyBQeV0oNnhbURxpbuMEhbv1MWVIGvk8a+P6y0rFkQ==} + + '@unocss/reset@0.59.4': + resolution: {integrity: sha512-Upy4xzdWl4RChbLAXBq1BoR4WqxXMoIfjvtcwSZcZK2sylXCFAseSWnyzJFdSiXPqNfmMuNgPXgiSxiQB+cmNA==} + + '@unocss/rule-utils@0.59.4': + resolution: {integrity: sha512-1qoLJlBWAkS4D4sg73990S1MT7E8E5md/YhopKjTQuEC9SyeVmEg+5pR/Xd8xhPKMqbcuBPl/DS8b6l/GQO56A==} + engines: {node: '>=14'} + + '@unocss/scope@0.59.4': + resolution: {integrity: sha512-wBQJ39kw4Tfj4km7AoGvSIobPKVnRZVsgc0bema5Y0PL3g1NeVQ/LopBI2zEJWdpxGXUWxSDsXm7BZo6qVlD/A==} + + '@unocss/transformer-attributify-jsx-babel@0.59.4': + resolution: {integrity: sha512-xtCRSgeTaDBiNJLVX7oOSFe63JiFB5nrdK23PHn3IlZM9O7Bxx4ZxI3MQJtFZFQNE+INFko+DVyY1WiFEm1p/Q==} + + '@unocss/transformer-attributify-jsx@0.59.4': + resolution: {integrity: sha512-m4b83utzKMfUQH/45V2QkjJoXd8Tu2pRP1nic91Xf7QRceyKDD+BxoTneo2JNC2K274cQu7HqqotnCm2aFfEGw==} + + '@unocss/transformer-compile-class@0.59.4': + resolution: {integrity: sha512-Vgk2OCLPW0pU+Uzr1IgDtHVspSBb+gPrQFkV+5gxHk9ZdKi3oYKxLuufVWYDSwv7o9yfQGbYrMH9YLsjRsnA7Q==} + + '@unocss/transformer-directives@0.59.4': + resolution: {integrity: sha512-nXUTEclUbs0vQ4KfLhKt4J/5SLSEq1az2FNlJmiXMmqmn75X89OrtCu2OJu9sGXhn+YyBApxgcSSdxmtpqMi1Q==} + + '@unocss/transformer-variant-group@0.59.4': + resolution: {integrity: sha512-9XLixxn1NRgP62Kj4R/NC/rpqhql5F2s6ulJ8CAMTEbd/NylVhEANluPGDVUGcLJ4cj6E02hFa8C1PLGSm7/xw==} + + '@unocss/vite@0.59.4': + resolution: {integrity: sha512-q7GN7vkQYn79n7vYIUlaa7gXGwc7pk0Qo3z3ZFwWGE43/DtZnn2Hwl5UjgBAgi9McA+xqHJEHRsJnI7HJPHUYA==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 + + '@vercel/analytics@1.6.1': + resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + peerDependencies: + '@remix-run/react': ^2 + '@sveltejs/kit': ^1 || ^2 + next: '>= 13' + react: ^18 || ^19 || ^19.0.0-rc + svelte: '>= 4' + vue: ^3 + vue-router: ^4 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + svelte: + optional: true + vue: + optional: true + vue-router: + optional: true + + '@vercel/backends@0.0.14': + resolution: {integrity: sha512-4a4LQueJCvwqJhz+B9DBlEOZOdyl+BrIMkC1LZC3++YGbEA9KLhcBwS10WF7hndQR1jizpf7klMQbcU2FwaN/g==} + + '@vercel/blob@1.0.2': + resolution: {integrity: sha512-Im/KeFH4oPx7UsM+QiteimnE07bIUD7JK6CBafI9Z0jRFogaialTBMiZj8EKk/30ctUYsrpIIyP9iIY1YxWnUQ==} + engines: {node: '>=16.14'} + + '@vercel/build-utils@13.2.2': + resolution: {integrity: sha512-VNGFd/bpjsrpMSHCkRhhcbzdaMJ1tRW9E3BW6uf5SpFB1zn3GUab+aNC6Zq23kHxtNhutjTmttybqWB2hYYTKQ==} + + '@vercel/cervel@0.0.6': + resolution: {integrity: sha512-IW/gXg17sozYYb7O/1ycjUj08c2EOCWaTFULNzPs5K2nZEgd1E6CQNmCcyRjHeVzIl3i40YXzytdhUBp2zH9sQ==} + hasBin: true + peerDependencies: + typescript: ^4.0.0 || ^5.0.0 + + '@vercel/detect-agent@1.0.0': + resolution: {integrity: sha512-AIPgNkmtFcDgPCl+xvTT1ga90OL7OTX2RKM4zu0PMpwBthPfN2DpdHy10n3bh8K+CA22GDU0/ncjzprZsrk0sw==} + engines: {node: '>=14'} + + '@vercel/elysia@0.1.12': + resolution: {integrity: sha512-0+pUbwTP2n6ii2QMafoYhO7F6FcvxYyeKoPmKIeXKCFFRkwI25EJFDOICLviWnZg7ijNJQym/tqQg72eeHb9AQ==} + + '@vercel/error-utils@2.0.3': + resolution: {integrity: sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==} + + '@vercel/express@0.1.17': + resolution: {integrity: sha512-FS/uBC6aeJJ3IvbUPiAupcgXkrX//3MVdZ7TsqxM7jPYNoJE1JFoonfZ4zIu9WKP5JdvvtnXzxeRsoSgpEMiCQ==} + + '@vercel/fastify@0.1.15': + resolution: {integrity: sha512-FVL5pK9MvlT4cxnrJ7ELv1AdERloIGYZbwfbwPMqGasW3nPhoFQfGA8z1XdEvY+NfDJtjAzBfakC91qtDVAUIQ==} + + '@vercel/fun@1.2.0': + resolution: {integrity: sha512-WSmS9qe2R+5roucDEwYB3atKhs9sUbkHV3laJGEUoqz25O83jLE/jqg/B/3yTunB0av1xqiCIBFFVKnXsqSc8w==} + engines: {node: '>= 18'} + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + resolution: {integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==} + + '@vercel/gatsby-plugin-vercel-builder@2.0.112': + resolution: {integrity: sha512-35lLBlbXECEAX3BfPNMsT1W1rpz5gARjePjGwiTS/G1T54ptGCiAfxGBqCj0rilAkjhH3L4Oa07FMbkcGExEUQ==} + + '@vercel/go@3.2.3': + resolution: {integrity: sha512-PErgHlV7cf8hyPq31aRsL4xm5t4rCSO6vN5AQLlAGSy3ctdgqG7sI6hq/CAKo3CfgIhVHUwNYapFJgGJB/s4OA==} + + '@vercel/h3@0.1.21': + resolution: {integrity: sha512-a87vIJwqfy3M2eUC7dhFS7WbsE6lo9eaQZmyPGVzFgVrMjSMeko/fI/A4AldgEAUp2Jndo5IB+0HA6Gv8TPu9A==} + + '@vercel/hono@0.2.15': + resolution: {integrity: sha512-syASDqf2ssUH92xr2Z0jvoIfmuMibcsYutvL02f6WFrsGyWnmX3JKvsLwieuqRjBBz47/kfArouH7IgLrMcwRw==} + + '@vercel/hydrogen@1.3.2': + resolution: {integrity: sha512-jI83xL0BnTo7XH5F0mDz7wwaio2oVnpzrj/NmT+5XXVozqdYFegY68HF7BQbzMsgZhZ6H1RwKCS1qb28C2RYuw==} + + '@vercel/introspection@0.0.5': + resolution: {integrity: sha512-4v35gsQ7KczLQKXB8XovpPhr3wVT5rpXqK78J5sXpJHSa7LYPO2n4wSCpN+XBQ98Xi56ONZY4VXcBj+kpRxZfg==} + + '@vercel/nestjs@0.2.16': + resolution: {integrity: sha512-VIpqtGsKy/TJi8VzUfJq+GB/jZ63MWYhUAOzM5kRU82E0+6PApxf3fj3C9xNg8OpeK5tHjx8QckS3wd4GHV8eg==} + + '@vercel/next@4.15.7': + resolution: {integrity: sha512-h2Knaxq4DK0z1yoHdpDPnTIXZx9TvmJOMHU1A//vjoMO6cgq+Xv/pjTEdSn/OUtftFIwqA+hrd5PpXLyEdW/7Q==} + + '@vercel/nft@0.30.4': + resolution: {integrity: sha512-wE6eAGSXScra60N2l6jWvNtVK0m+sh873CpfZW4KI2v8EHuUQp+mSEi4T+IcdPCSEDgCdAS/7bizbhQlkjzrSA==} + engines: {node: '>=18'} + hasBin: true + + '@vercel/nft@1.1.1': + resolution: {integrity: sha512-mKMGa7CEUcXU75474kOeqHbtvK1kAcu4wiahhmlUenB5JbTQB8wVlDI8CyHR3rpGo0qlzoRWqcDzI41FUoBJCA==} + engines: {node: '>=20'} + hasBin: true + + '@vercel/node@5.5.14': + resolution: {integrity: sha512-pEhGm8L1qbefx6P7rjm1F7vrWLN4vB1+UOD1xmWiLvWJSyzUDnk2nelaPPmgLSh6dObUnxucoSnWkoLpmZnL/Q==} + + '@vercel/python@6.1.0': + resolution: {integrity: sha512-I8ddjrpjY0WoJdFpALYqwydj40DM2YRD97798hN0DNnT9fMZtltyJar5mm7lxLWo4evKNqRkqtUeex2t8WxV6w==} + + '@vercel/redwood@2.4.5': + resolution: {integrity: sha512-mFFZSFJ2ND3Sym6PDOISyaOChLY1AIXBW5ZQaSiNg/ZqFtcjwY6cYCw8xTvG/ThlAZE0/XqetLWJJqffoW2lxA==} + + '@vercel/remix-builder@5.5.5': + resolution: {integrity: sha512-r1Hrt1UM3+GsQ1bpqEGrM/bU2NzbHk8MjyxwxcvdltWHnHvuQCWxUf79hUGa+vWjYJITit6Bqg/978R9vLr5nQ==} + + '@vercel/ruby@2.2.2': + resolution: {integrity: sha512-PgO3kVPPmYw914BQpgi2/NQSgaXe4xoLLdNhYuI7tJxwfZzVfYizNvhjREyoOvn96LeuB4gNS5P7nbeJjHqb/w==} + + '@vercel/rust@1.0.3': + resolution: {integrity: sha512-u0kh2ZafuxTelXPRbv2/tR6cC9MBjgRhrzVuKxOZk7GyLnFwNTDuNwoP4iJ03yF1k5GqGYB0v+0Zvty67isAAQ==} + + '@vercel/static-build@2.8.13': + resolution: {integrity: sha512-kppgO752q0ri+UKCFps91gSSwrEyXybqsJWfF/Amtavp0agx378wKHFXi/GhUS8FgidZe4928XFa2/tCjePWlw==} + + '@vercel/static-config@3.1.2': + resolution: {integrity: sha512-2d+TXr6K30w86a+WbMbGm2W91O0UzO5VeemZYBBUJbCjk/5FLLGIi8aV6RS2+WmaRvtcqNTn2pUA7nCOK3bGcQ==} + + '@vitejs/plugin-react@4.2.1': + resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + '@vitejs/plugin-vue-jsx@5.1.3': + resolution: {integrity: sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@5.0.4': + resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + + '@vitejs/plugin-vue@5.1.4': + resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + + '@vitejs/plugin-vue@6.0.3': + resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vue: ^3.2.25 + + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@volar/language-core@2.2.1': + resolution: {integrity: sha512-iHJAZKcYldZgyS8gx6DfIZApViVBeqbf6iPhqoZpG5A6F4zsZiFldKfwaKaBA3/wnOTWE2i8VUbXywI1WywCPg==} + + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + + '@volar/language-core@2.4.8': + resolution: {integrity: sha512-K/GxMOXGq997bO00cdFhTNuR85xPxj0BEEAy+BaqqayTmy9Tmhfgmq2wpJcVspRhcwfgPoE2/mEJa26emUhG/g==} + + '@volar/source-map@2.2.1': + resolution: {integrity: sha512-w1Bgpguhbp7YTr7VUFu6gb4iAZjeEPsOX4zpgiuvlldbzvIWDWy4t0jVifsIsxZ99HAu+c3swiME7wt+GeNqhA==} + + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + + '@volar/source-map@2.4.8': + resolution: {integrity: sha512-jeWJBkC/WivdelMwxKkpFL811uH/jJ1kVxa+c7OvG48DXc3VrP7pplSWPP2W1dLMqBxD+awRlg55FQQfiup4cA==} + + '@volar/typescript@2.2.1': + resolution: {integrity: sha512-Z/tqluR7Hz5/5dCqQp7wo9C/6tSv/IYl+tTzgzUt2NjTq95bKSsuO4E+V06D0c+3aP9x5S9jggLqw451hpnc6Q==} + + '@vue-macros/common@3.0.0-beta.16': + resolution: {integrity: sha512-8O2gWxWFiaoNkk7PGi0+p7NPGe/f8xJ3/INUufvje/RZOs7sJvlI1jnR4lydtRFa/mU0ylMXUXXjSK0fHDEYTA==} + engines: {node: '>=20.18.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + + '@vue/babel-helper-vue-transform-on@2.0.1': + resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==} + + '@vue/babel-plugin-jsx@2.0.1': + resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@2.0.1': + resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.4.27': + resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==} + + '@vue/compiler-core@3.5.12': + resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} + + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + + '@vue/compiler-dom@3.4.27': + resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==} + + '@vue/compiler-dom@3.5.12': + resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} + + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + + '@vue/compiler-sfc@3.4.27': + resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==} + + '@vue/compiler-sfc@3.5.12': + resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} + + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + + '@vue/compiler-ssr@3.4.27': + resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==} + + '@vue/compiler-ssr@3.5.12': + resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} + + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.1': + resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.6.2': + resolution: {integrity: sha512-NCT0ujqlwAhoFvCsAG7G5qS8w/A/dhvFSt2BhmNxyqgpYDrf9CG1zYyWLQkE3dsZ+5lCT6ULUic2VKNaE07Vzg==} + + '@vue/devtools-core@7.7.9': + resolution: {integrity: sha512-48jrBSwG4GVQRvVeeXn9p9+dlx+ISgasM7SxZZKczseohB0cBz+ITKr4YbLWjmJdy45UHL7UMPlR4Y0CWTRcSQ==} + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@7.6.2': + resolution: {integrity: sha512-k61BxHRmcTtIQZFouF9QWt9nCCNtSdw12lhg8VNtHq5/XOBGD+ewiK27a40UJ8UPYoCJvi80hbvbYr5E/Zeu1g==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-shared@7.6.2': + resolution: {integrity: sha512-lcjyJ7hCC0W0kNwnCGMLVTMvDLoZgjcq9BvboPgS+6jQyDul7fpzRSKTGtGhCHoxrDox7qBAKGbAl2Rcf7GE1A==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/language-core@2.0.16': + resolution: {integrity: sha512-Bc2sexRH99pznOph8mLw2BlRZ9edm7tW51kcBXgx8adAoOcZUWJj3UNSsdQ6H9Y8meGz7BoazVrVo/jUukIsPw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@2.1.10': + resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@3.2.1': + resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} + + '@vue/reactivity@3.4.27': + resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} + + '@vue/reactivity@3.5.12': + resolution: {integrity: sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==} + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.4.27': + resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} + + '@vue/runtime-core@3.5.12': + resolution: {integrity: sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.4.27': + resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} + + '@vue/runtime-dom@3.5.12': + resolution: {integrity: sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.4.27': + resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} + peerDependencies: + vue: 3.4.27 + + '@vue/server-renderer@3.5.12': + resolution: {integrity: sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==} + peerDependencies: + vue: 3.5.12 + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + + '@vue/shared@3.4.27': + resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + + '@vue/shared@3.5.12': + resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} + + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + + '@vue/test-utils@2.4.6': + resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + + '@vueuse/core@11.2.0': + resolution: {integrity: sha512-JIUwRcOqOWzcdu1dGlfW04kaJhW3EXnnjJJfLTtddJanymTL7lF1C0+dVVZ/siLfc73mWn+cGP1PE1PKPruRSA==} + + '@vueuse/integrations@11.2.0': + resolution: {integrity: sha512-zGXz3dsxNHKwiD9jPMvR3DAxQEOV6VWIEYTGVSB9PNpk4pTWR+pXrHz9gvXWcP2sTk3W2oqqS6KwWDdntUvNVA==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@11.2.0': + resolution: {integrity: sha512-L0ZmtRmNx+ZW95DmrgD6vn484gSpVeRbgpWevFKXwqqQxW9hnSi2Ppuh2BzMjnbv4aJRiIw8tQatXT9uOB23dQ==} + + '@vueuse/shared@11.2.0': + resolution: {integrity: sha512-VxFjie0EanOudYSgMErxXfq6fo8vhr5ICI+BuE3I9FnX7ePllEsVrRQ7O6Q1TLgApeLuPKcHQxAXpP+KnlrJsg==} + + '@walletconnect/core@2.19.2': + resolution: {integrity: sha512-iu0mgLj51AXcKpdNj8+4EdNNBd/mkNjLEhZn6UMc/r7BM9WbmpPMEydA39WeRLbdLO4kbpmq4wTbiskI1rg+HA==} + engines: {node: '>=18'} + + '@walletconnect/core@2.20.2': + resolution: {integrity: sha512-48XnarxQQrpJ0KZJOjit56DxuzfVRYUdL8XVMvUh/ZNUiX2FB5w6YuljUUeTLfYOf04Et6qhVGEUkmX3W+9/8w==} + engines: {node: '>=18'} + + '@walletconnect/environment@1.0.1': + resolution: {integrity: sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==} + + '@walletconnect/ethereum-provider@2.20.2': + resolution: {integrity: sha512-fGNJtytHuBWZcmMXRIG1djlfEiPMvPJ0R3JlfJjAx2VfVN+O+1xdF6QSWcZxFizviIUFJV+f1zWt0V2VVD61Rg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + + '@walletconnect/events@1.0.1': + resolution: {integrity: sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==} + + '@walletconnect/heartbeat@1.2.2': + resolution: {integrity: sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==} + + '@walletconnect/jsonrpc-http-connection@1.0.8': + resolution: {integrity: sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==} + + '@walletconnect/jsonrpc-provider@1.0.14': + resolution: {integrity: sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==} + + '@walletconnect/jsonrpc-types@1.0.4': + resolution: {integrity: sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==} + + '@walletconnect/jsonrpc-utils@1.0.8': + resolution: {integrity: sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==} + + '@walletconnect/jsonrpc-ws-connection@1.0.16': + resolution: {integrity: sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==} + + '@walletconnect/keyvaluestorage@1.1.1': + resolution: {integrity: sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==} + peerDependencies: + '@react-native-async-storage/async-storage': 1.x + peerDependenciesMeta: + '@react-native-async-storage/async-storage': + optional: true + + '@walletconnect/logger@2.1.2': + resolution: {integrity: sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==} + + '@walletconnect/relay-api@1.0.11': + resolution: {integrity: sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==} + + '@walletconnect/relay-auth@1.1.0': + resolution: {integrity: sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==} + + '@walletconnect/safe-json@1.0.2': + resolution: {integrity: sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==} + + '@walletconnect/sign-client@2.19.2': + resolution: {integrity: sha512-a/K5PRIFPCjfHq5xx3WYKHAAF8Ft2I1LtxloyibqiQOoUtNLfKgFB1r8sdMvXM7/PADNPe4iAw4uSE6PrARrfg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + + '@walletconnect/sign-client@2.20.2': + resolution: {integrity: sha512-KyeDToypZ1OjCbij4Jx0cAg46bMwZ6zCKt0HzCkqENcex3Zchs7xBp9r8GtfEMGw+PUnXwqrhzmLBH0x/43oIQ==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + + '@walletconnect/time@1.0.2': + resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==} + + '@walletconnect/types@2.19.2': + resolution: {integrity: sha512-/LZWhkVCUN+fcTgQUxArxhn2R8DF+LSd/6Wh9FnpjeK/Sdupx1EPS8okWG6WPAqq2f404PRoNAfQytQ82Xdl3g==} + + '@walletconnect/types@2.20.2': + resolution: {integrity: sha512-XPPbJM/mGU05i6jUxhC3yI/YvhSF6TYJQ5SXTWM53lVe6hs6ukvlEhPctu9ZBTGwGFhwPXIjtK/eWx+v4WY5iw==} + + '@walletconnect/universal-provider@2.19.2': + resolution: {integrity: sha512-LkKg+EjcSUpPUhhvRANgkjPL38wJPIWumAYD8OK/g4OFuJ4W3lS/XTCKthABQfFqmiNbNbVllmywiyE44KdpQg==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + + '@walletconnect/universal-provider@2.20.2': + resolution: {integrity: sha512-6uVu1E88tioaXEEJCbJKwCIQlOHif1nmfY092BznZEnBn2lli5ICzQh2bxtUDNmNNLKsMDI3FV1fODFeWMVJTQ==} + deprecated: 'Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases' + + '@walletconnect/utils@2.19.2': + resolution: {integrity: sha512-VU5CcUF4sZDg8a2/ov29OJzT3KfLuZqJUM0GemW30dlipI5fkpb0VPenZK7TcdLPXc1LN+Q+7eyTqHRoAu/BIA==} + + '@walletconnect/utils@2.20.2': + resolution: {integrity: sha512-2uRUDvpYSIJFYcr1WIuiFy6CEszLF030o6W8aDMkGk9/MfAZYEJQHMJcjWyaNMPHLJT0POR5lPaqkYOpuyPIQQ==} + + '@walletconnect/window-getters@1.0.1': + resolution: {integrity: sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==} + + '@walletconnect/window-metadata@1.0.1': + resolution: {integrity: sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + abitype@1.0.0: + resolution: {integrity: sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abitype@1.0.2: + resolution: {integrity: sha512-aFt4k2H+eiAKy/zxtnORa9iIb10BMBeWL18l8v4+QuwYEBXPxxjSB1bFZCzQmKPoj8m7j68K705l3uY+E2gAjg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abitype@1.0.4: + resolution: {integrity: sha512-UivtYZOGJGE8rsrM/N5vdRkUpqEZVmuTumfTuolm7m/6O09wprd958rx8kUBwVAAAhQDveGAgD0GJdBuR8s6tw==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abitype@1.0.5: + resolution: {integrity: sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==} + 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: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.4.16: + resolution: {integrity: sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==} + engines: {node: '>=0.3.0'} + + 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'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@8.6.3: + resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==} + + algoliasearch@5.12.0: + resolution: {integrity: sha512-psGBRYdGgik8I6m28iAB8xpubvjEt7UQU+w5MAJUA2324WHiGoHap5BPkkjB14rMaXeRts6pmOsrVIglGyOVwg==} + engines: {node: '>= 14.0.0'} + + alien-signals@0.2.0: + resolution: {integrity: sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==} + + alien-signals@3.1.2: + resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + arg@4.1.0: + resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-union@1.0.2: + resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} + engines: {node: '>=0.10.0'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array-uniq@1.0.3: + resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} + engines: {node: '>=0.10.0'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-v8-to-istanbul@0.3.9: + resolution: {integrity: sha512-dSC6tJeOJxbZrPzPbv5mMd6CMiQ1ugaVXXPRad2fXUSsy1kstFn9XQWemV9VW7Y7kpxgQ/4WMoZfwdH8XSU48w==} + + ast-walker-scope@0.8.3: + resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==} + engines: {node: '>=20.19.0'} + + async-listen@1.2.0: + resolution: {integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==} + + async-listen@3.0.0: + resolution: {integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==} + engines: {node: '>= 14'} + + async-listen@3.0.1: + resolution: {integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==} + engines: {node: '>= 14'} + + async-mutex@0.2.6: + resolution: {integrity: sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==} + + async-ref@0.1.6: + resolution: {integrity: sha512-gIvfC7ahv452pM+nwnxZJd/vhUh8UFMrd1DCvIvWjoy9WrRmYroXTTDxwg91oiD+4CqQKrg+11uCxZrzat4UvQ==} + + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + base-x@3.0.9: + resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} + + base-x@5.0.1: + resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.11: + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} + hasBin: true + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + big.js@6.2.2: + resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} + + binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + birpc@0.2.19: + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + blakejs@1.2.1: + resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} + + bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + + bn.js@4.12.2: + resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} + + bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + + bn.js@5.2.2: + resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} + + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + + bs58@6.0.0: + resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + + bs58check@2.1.2: + resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bufferutil@4.0.8: + resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} + engines: {node: '>=6.14.2'} + + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} + engines: {node: '>=6.14.2'} + + bun-types@1.1.29: + resolution: {integrity: sha512-En3/TzSPMPyl5UlUB1MHzHpcrZDakTm7mS203eLoX1fBoEa3PW+aSS8GAqVJ7Is/m34Z5ogL+ECniLY0uDaCPw==} + + bun@1.1.30: + resolution: {integrity: sha512-ysRL1pq10Xba0jqVLPrKU3YIv0ohfp3cTajCPtpjCyppbn3lfiAVNpGoHfyaxS17OlPmWmR67UZRPw/EueQuug==} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + bytes@3.1.0: + resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} + engines: {node: '>= 0.8'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + c12@3.3.3: + resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.0: + resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} + engines: {node: '>= 14.16.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cipher-base@1.0.4: + resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clipboardy@4.0.0: + resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} + engines: {node: '>=18'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + code-block-writer@10.1.1: + resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compatx@0.2.0: + resolution: {integrity: sha512-6gLRNt4ygsi5NyMVhceOCFv14CIdDFN7fQjX1U4+47qVE/+kjPoXMK65KWK+dWxmFzMTuKazoQ9sch6pM0p5oA==} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-type@1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + + convert-hrtime@3.0.0: + resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==} + engines: {node: '>=8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + copy-paste@2.2.0: + resolution: {integrity: sha512-jqSL4r9DSeiIvJZStLzY/sMLt9ToTM7RsK237lYOTG+KcbQJHGala3R1TUpa8h1p9adswVgIdV4qGbseVhL4lg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + + create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + croner@9.1.0: + resolution: {integrity: sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g==} + engines: {node: '>=18.0'} + + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crossws@0.2.4: + resolution: {integrity: sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==} + peerDependencies: + uWebSockets.js: '*' + peerDependenciesMeta: + uWebSockets.js: + optional: true + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + crypto-random-string@1.0.0: + resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==} + engines: {node: '>=4'} + + css-declaration-sorter@7.3.0: + resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.0.9 + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-default@7.0.10: + resolution: {integrity: sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + cssnano-utils@5.0.1: + resolution: {integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + cssnano@7.1.2: + resolution: {integrity: sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + db0@0.3.4: + resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==} + peerDependencies: + '@electric-sql/pglite': '*' + '@libsql/client': '*' + better-sqlite3: '*' + drizzle-orm: '*' + mysql2: '*' + sqlite3: '*' + peerDependenciesMeta: + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + better-sqlite3: + optional: true + drizzle-orm: + optional: true + mysql2: + optional: true + sqlite3: + optional: true + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + derive-valtio@0.1.0: + resolution: {integrity: sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==} + peerDependencies: + valtio: '*' + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-browser@5.3.0: + resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devalue@5.6.1: + resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dijkstrajs@1.0.2: + resolution: {integrity: sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==} + + dir-glob@2.2.2: + resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==} + engines: {node: '>=4'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dot-prop@10.1.0: + resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} + engines: {node: '>=20'} + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + + duplexify@4.1.2: + resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + easy-table@1.2.0: + resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} + + eciesjs@0.4.16: + resolution: {integrity: sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + + edge-runtime@2.5.9: + resolution: {integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==} + engines: {node: '>=16'} + hasBin: true + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.4.757: + resolution: {integrity: sha512-jftDaCknYSSt/+KKeXzH3LX5E2CvRLm75P3Hj+J/dv3CL0qUYcOt13d5FN1NiL5IJbbhzHrb3BomeG2tkSlZmw==} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + + elliptic@6.6.0: + resolution: {integrity: sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + encode-utf8@1.0.3: + resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.1.0: + resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + engine.io-client@6.6.4: + resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + enquirer@2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + errx@0.1.0: + resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-toolkit@1.33.0: + resolution: {integrity: sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg==} + + esbuild-android-64@0.14.47: + resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.47: + resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.47: + resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.47: + resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.47: + resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.47: + resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.47: + resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.47: + resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.47: + resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.47: + resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.47: + resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.47: + resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.47: + resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.47: + resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.47: + resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.47: + resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.47: + resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.47: + resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.47: + resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.47: + resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.47: + resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eth-block-tracker@7.1.0: + resolution: {integrity: sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg==} + engines: {node: '>=14.0.0'} + + eth-json-rpc-filters@6.0.1: + resolution: {integrity: sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig==} + engines: {node: '>=14.0.0'} + + eth-query@2.1.2: + resolution: {integrity: sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==} + + eth-rpc-errors@4.0.3: + resolution: {integrity: sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==} + + ethereum-cryptography@0.1.3: + resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} + + ethereum-cryptography@1.2.0: + resolution: {integrity: sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==} + + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + + ethereumjs-abi@0.6.8: + resolution: {integrity: sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==} + deprecated: This library has been deprecated and usage is discouraged. + + ethereumjs-util@6.2.1: + resolution: {integrity: sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==} + + ethers@6.16.0: + resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} + engines: {node: '>=14.0.0'} + + ethjs-util@0.1.6: + resolution: {integrity: sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==} + engines: {node: '>=6.5.0', npm: '>=3'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + events-intercept@2.0.0: + resolution: {integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} + + execa@3.2.0: + resolution: {integrity: sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==} + engines: {node: ^8.12.0 || >=9.7.0} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + execa@9.1.0: + resolution: {integrity: sha512-lSgHc4Elo2m6bUDhc3Hl/VxvUDJdQWI40RZ4KMY9bKRc+hgMOT7II/JjbNDhI8VnMtrCb7U/fhpJIkLORZozWw==} + engines: {node: '>=18'} + + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + extension-port-stream@3.0.0: + resolution: {integrity: sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==} + engines: {node: '>=12.0.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + externality@1.0.2: + resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==} + + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-npm-meta@0.4.7: + resolution: {integrity: sha512-aZU3i3eRcSb2NCq8i6N6IlyiTyF6vqAqzBGl2NBF6ngNx/GIqfYbkLDIKZ4z4P0o/RmtsFnVqHwdrSm13o4tnQ==} + + fast-redact@3.1.2: + resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + + fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.1.1: + resolution: {integrity: sha512-QfKBVg453Dyn3mr0Q0O+Tkr1r79lOTAKSi9f/Ot4+qVEwxWhav2Z+SudrG9vQjM2aYRMQQZ2/Q1zdA8ACM1pDg==} + peerDependencies: + picomatch: 3.x + peerDependenciesMeta: + picomatch: + optional: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fixturez@1.1.0: + resolution: {integrity: sha512-c4q9eZsAmCzj9gkrEO/YwIRlrHWt/TXQiX9jR9WeLFOqeeV6EyzdiiV28CpSzF6Ip+gyYrSv5UeOHqyzfcNTVA==} + + flag@5.0.1: + resolution: {integrity: sha512-4P/rvPGKcFBneboyYHMOKbKaJL5ZhNjScbY7bGToas7FgBvrTWbh76yxRaoSoFe3HSjWc6IJ0EajUwSadcP6Lg==} + peerDependencies: + react: ^18 + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + floating-vue@5.2.2: + resolution: {integrity: sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg==} + peerDependencies: + '@nuxt/kit': ^3.2.0 + vue: ^3.2.0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + focus-trap@7.6.0: + resolution: {integrity: sha512-1td0l3pMkWJLFipobUcGaf+5DTY4PLDDrcqoSaKP8ediO/CoWCCYk/fT/Y2A4e6TNB+Sh6clRJCjOPPnKoNHnQ==} + + follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fp-ts@1.19.3: + resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + framer-motion@12.23.26: + resolution: {integrity: sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@0.30.0: + resolution: {integrity: sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==} + + fs-extra@11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + engines: {node: '>=14.14'} + + fs-extra@5.0.0: + resolution: {integrity: sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + fuse.js@7.1.0: + resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} + engines: {node: '>=10'} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + generic-pool@3.4.2: + resolution: {integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==} + engines: {node: '>= 4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-port-please@3.2.0: + resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + git-up@8.1.1: + resolution: {integrity: sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==} + + git-url-parse@16.1.0: + resolution: {integrity: sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + + glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@15.0.0: + resolution: {integrity: sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==} + engines: {node: '>=20'} + + globby@7.1.1: + resolution: {integrity: sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==} + engines: {node: '>=4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@16.8.1: + resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + + gzip-size@7.0.0: + resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + h3@1.15.4: + resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + + happy-dom@20.0.2: + resolution: {integrity: sha512-pYOyu624+6HDbY+qkjILpQGnpvZOusItCk+rvF5/V+6NkcgTKnbOldpIy22tBnxoaLtlM9nXgoqAcW29/B7CIw==} + engines: {node: '>=20.0.0'} + + hardhat@2.22.3: + resolution: {integrity: sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==} + hasBin: true + peerDependencies: + ts-node: '*' + typescript: '*' + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hash-base@3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} + engines: {node: '>=4'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-html@9.0.3: + resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + headers-polyfill@4.0.2: + resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-errors@1.4.0: + resolution: {integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==} + engines: {node: '>= 0.6'} + + http-errors@1.7.3: + resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + http-shutdown@1.2.2: + resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + 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'} + + httpxy@0.1.7: + resolution: {integrity: sha512-pXNx8gnANKAndgga5ahefxc++tJvNL87CXoRwxn1cJE2ZkWEojF3tNfQIEhZX/vfpt+wzeAzpUI4qkediX1MLQ==} + + human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + hasBin: true + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + human-signals@7.0.0: + resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.1: + resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + engines: {node: '>=0.10.0'} + + idb-keyval@6.2.1: + resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + + idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@3.3.10: + resolution: {integrity: sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + image-meta@0.2.2: + resolution: {integrity: sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==} + + immutable@4.3.5: + resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==} + + impound@1.0.0: + resolution: {integrity: sha512-8lAJ+1Arw2sMaZ9HE2ZmL5zOcMnt18s6+7Xqgq2aUVy4P1nlzAyPtzCDxsk51KVFwHEEdc6OWvUyqwHwhRYaug==} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.1: + resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + io-ts@1.10.4: + resolution: {integrity: sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==} + + ioredis@5.8.2: + resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} + engines: {node: '>=12.22.0'} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hex-prefixed@1.0.0: + resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} + engines: {node: '>=6.5.0', npm: '>=3'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-installed-globally@1.0.0: + resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} + engines: {node: '>=18'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-ssh@1.4.1: + resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-unicode-supported@2.0.0: + resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} + engines: {node: '>=18'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + is64bit@2.0.0: + resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} + engines: {node: '>=18'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + isows@1.0.4: + resolution: {integrity: sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==} + peerDependencies: + ws: '*' + + isows@1.0.6: + resolution: {integrity: sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==} + peerDependencies: + ws: '*' + + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jose@5.9.6: + resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + + js-beautify@1.15.1: + resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-canonicalize@1.2.0: + resolution: {integrity: sha512-TTdjBvqrqJKSADlEsY5rWbx8/1tOrVlTR/aSLU8N2VSInCTffP0p+byYB8Es+OmL4ZOeEftjUdvV+eJeSzJC/Q==} + + json-rpc-engine@6.1.0: + resolution: {integrity: sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==} + engines: {node: '>=10.0.0'} + + json-rpc-random-id@1.0.1: + resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} + + json-schema-to-ts@1.6.4: + resolution: {integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@2.4.0: + resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + keccak256@1.0.6: + resolution: {integrity: sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==} + + keccak@3.0.3: + resolution: {integrity: sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==} + engines: {node: '>=10.0.0'} + + keccak@3.0.4: + resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} + engines: {node: '>=10.0.0'} + + keyvaluestorage-interface@1.0.0: + resolution: {integrity: sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==} + + klaw@1.3.1: + resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + knip@5.30.6: + resolution: {integrity: sha512-YkcnRVl0N99xZ7eaXE7KlH/4cPTCn6BGuk9KxINEdCMFN3yita2vGBizApy97ZOHgghy8tb589gQ3xvLMFIO4w==} + engines: {node: '>=18.6.0'} + hasBin: true + peerDependencies: + '@types/node': '>=18' + typescript: '>=5.0.4' + + knitwork@1.3.0: + resolution: {integrity: sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lefthook-darwin-arm64@1.13.6: + resolution: {integrity: sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A==} + cpu: [arm64] + os: [darwin] + + lefthook-darwin-x64@1.13.6: + resolution: {integrity: sha512-CoRpdzanu9RK3oXR1vbEJA5LN7iB+c7hP+sONeQJzoOXuq4PNKVtEaN84Gl1BrVtCNLHWFAvCQaZPPiiXSy8qg==} + cpu: [x64] + os: [darwin] + + lefthook-freebsd-arm64@1.13.6: + resolution: {integrity: sha512-X4A7yfvAJ68CoHTqP+XvQzdKbyd935sYy0bQT6Ajz7FL1g7hFiro8dqHSdPdkwei9hs8hXeV7feyTXbYmfjKQQ==} + cpu: [arm64] + os: [freebsd] + + lefthook-freebsd-x64@1.13.6: + resolution: {integrity: sha512-ai2m+Sj2kGdY46USfBrCqLKe9GYhzeq01nuyDYCrdGISePeZ6udOlD1k3lQKJGQCHb0bRz4St0r5nKDSh1x/2A==} + cpu: [x64] + os: [freebsd] + + lefthook-linux-arm64@1.13.6: + resolution: {integrity: sha512-cbo4Wtdq81GTABvikLORJsAWPKAJXE8Q5RXsICFUVznh5PHigS9dFW/4NXywo0+jfFPCT6SYds2zz4tCx6DA0Q==} + cpu: [arm64] + os: [linux] + + lefthook-linux-x64@1.13.6: + resolution: {integrity: sha512-uJl9vjCIIBTBvMZkemxCE+3zrZHlRO7Oc+nZJ+o9Oea3fu+W82jwX7a7clw8jqNfaeBS+8+ZEQgiMHWCloTsGw==} + cpu: [x64] + os: [linux] + + lefthook-openbsd-arm64@1.13.6: + resolution: {integrity: sha512-7r153dxrNRQ9ytRs2PmGKKkYdvZYFPre7My7XToSTiRu5jNCq++++eAKVkoyWPduk97dGIA+YWiEr5Noe0TK2A==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@1.13.6: + resolution: {integrity: sha512-Z+UhLlcg1xrXOidK3aLLpgH7KrwNyWYE3yb7ITYnzJSEV8qXnePtVu8lvMBHs/myzemjBzeIr/U/+ipjclR06g==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@1.13.6: + resolution: {integrity: sha512-Uxef6qoDxCmUNQwk8eBvddYJKSBFglfwAY9Y9+NnnmiHpWTjjYiObE9gT2mvGVpEgZRJVAatBXc+Ha5oDD/OgQ==} + cpu: [arm64] + os: [win32] + + lefthook-windows-x64@1.13.6: + resolution: {integrity: sha512-mOZoM3FQh3o08M8PQ/b3IYuL5oo36D9ehczIw1dAgp1Ly+Tr4fJ96A+4SEJrQuYeRD4mex9bR7Ps56I73sBSZA==} + cpu: [x64] + os: [win32] + + lefthook@1.13.6: + resolution: {integrity: sha512-ojj4/4IJ29Xn4drd5emqVgilegAPN3Kf0FQM2p/9+lwSTpU+SZ1v4Ig++NF+9MOa99UKY8bElmVrLhnUUNFh5g==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + listhen@1.7.2: + resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==} + hasBin: true + + listhen@1.9.0: + resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} + hasBin: true + + lit-element@4.2.0: + resolution: {integrity: sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==} + + lit-html@3.3.0: + resolution: {integrity: sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==} + + lit@3.1.0: + resolution: {integrity: sha512-rzo/hmUqX8zmOdamDAeydfjsGXbbdtAFqMhmocnh2j9aDYqbu0fjXygjCa0T99Od9VQ/2itwaGrjZz/ZELVl7w==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lokijs@1.5.12: + resolution: {integrity: sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru_map@0.3.3: + resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-regexp@0.10.0: + resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==} + + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + markdown-table@3.0.3: + resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + + marked-terminal@7.1.0: + resolution: {integrity: sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <14' + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + + mdast-util-find-and-replace@3.0.1: + resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.0: + resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.1.0: + resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micro-ftch@0.3.1: + resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} + + micro@9.3.5-canary.3: + resolution: {integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==} + engines: {node: '>= 8.0.0'} + hasBin: true + + micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + + micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + mime@4.1.0: + resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} + engines: {node: '>=16'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minisearch@7.1.0: + resolution: {integrity: sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mipd@0.0.7: + resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.0: + resolution: {integrity: sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mnemonist@0.38.5: + resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} + + mocha@10.2.0: + resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} + engines: {node: '>= 14.0.0'} + hasBin: true + + mocked-exports@0.1.1: + resolution: {integrity: sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==} + + motion-dom@12.23.23: + resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + motion@12.23.26: + resolution: {integrity: sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.1: + resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.4.9: + resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + + nanotar@0.2.0: + resolution: {integrity: sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==} + + next@15.4.10: + resolution: {integrity: sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + nitropack@2.12.9: + resolution: {integrity: sha512-t6qqNBn2UDGMWogQuORjbL2UPevB8PvIPsPHmqvWpeGOlPr4P8Oc5oA8t3wFwGmaolM2M/s2SwT23nx9yARmOg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + xml2js: ^0.6.2 + peerDependenciesMeta: + xml2js: + optional: true + + node-addon-api@2.0.2: + resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-emoji@2.1.3: + resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} + engines: {node: '>=18'} + + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.6.9: + resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + + node-forge@1.3.3: + resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} + engines: {node: '>= 6.13.0'} + + node-gyp-build@4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nuxt@3.19.0: + resolution: {integrity: sha512-gDXI7sL6dtdgHVh+D90UpI0El5LzQhKvwhIB6ppm8Zh3khG5WsFj3GZkPF0JVkN2BG9oAFgU14qsVajkOEXG3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@parcel/watcher': ^2.1.0 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + peerDependenciesMeta: + '@parcel/watcher': + optional: true + '@types/node': + optional: true + + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + obj-multiplex@1.0.0: + resolution: {integrity: sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obliterator@2.0.4: + resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + on-change@5.0.1: + resolution: {integrity: sha512-n7THCP7RkyReRSLkJb8kUWoNsxUIBxTkIp3JKno+sEz6o/9AJ3w3P9fzQkITEkMwyTKJjZciF3v/pVoouxZZMg==} + engines: {node: '>=18'} + + on-exit-leak-free@0.2.0: + resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.3.3: + resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + oniguruma-to-js@0.4.3: + resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + deprecated: use oniguruma-to-es instead + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + openapi-fetch@0.13.8: + resolution: {integrity: sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==} + + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + + os-paths@4.4.0: + resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==} + engines: {node: '>= 6.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + outvariant@1.4.2: + resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + ox@0.11.1: + resolution: {integrity: sha512-1l1gOLAqg0S0xiN1dH5nkPna8PucrZgrIJOfS49MLNiMevxu07Iz4ZjuJS9N+xifvT+PsZyIptS7WHM8nC+0+A==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + ox@0.6.7: + resolution: {integrity: sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + ox@0.7.2: + resolution: {integrity: sha512-JJHxdef3KqwQ8xW9H+UVLWaQiCUh/DeFN/Ii65XwbHTvSuXZveihTV2gs9hhLw5XEWPBrcH7sQBM0rqG7/QurQ==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + ox@0.8.1: + resolution: {integrity: sha512-e+z5epnzV+Zuz91YYujecW8cF01mzmrUtWotJ0oEPym/G82uccs7q0WDHTYL3eiONbTUEvcZrptAKLgTBD3u2A==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + oxc-minify@0.86.0: + resolution: {integrity: sha512-pjtM94KElw/RxF3R1ls1ADcBUyZcrCgn0qeL4nD8cOotfzeVFa0xXwQQeCkk+5GPiOqdRApNFuJvK//lQgpqJw==} + engines: {node: '>=14.0.0'} + + oxc-parser@0.86.0: + resolution: {integrity: sha512-v9+uomgqyLSxlq3qlaMqJJtXg2+rUsa368p/zkmgi5OMGmcZAtZt5GIeSVFF84iNET+08Hdx/rUtd/FyIdfNFQ==} + engines: {node: '>=20.0.0'} + + oxc-transform@0.86.0: + resolution: {integrity: sha512-Ghgm/zzjPXROMpljLy4HYBcko/25sixWi2yJQJ6rDu/ltgFB1nEQ4JYCYV5F+ENt0McsJkcgmX5I4dRfDViyDA==} + engines: {node: '>=14.0.0'} + + oxc-walker@0.4.0: + resolution: {integrity: sha512-x5TJAZQD3kRnRBGZ+8uryMZUwkTYddwzBftkqyJIcmpBOXmoK/fwriRKATjZroR2d+aS7+2w1B0oz189bBTwfw==} + peerDependencies: + oxc-parser: '>=0.72.0' + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-finally@2.0.1: + resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==} + engines: {node: '>=8'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@0.2.0: + resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse-path@7.1.0: + resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} + + parse-url@9.2.0: + resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} + engines: {node: '>=14.13.0'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-match@1.2.4: + resolution: {integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==} + deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + + path-to-regexp@1.9.0: + resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} + + path-to-regexp@6.1.0: + resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + pbkdf2@3.1.2: + resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} + engines: {node: '>=0.12'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + perfect-debounce@2.0.0: + resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} + + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + pino-abstract-transport@0.5.0: + resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-pretty@13.0.0: + resolution: {integrity: sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==} + hasBin: true + + pino-std-serializers@4.0.0: + resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + + pino@7.11.0: + resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} + hasBin: true + + pkg-types@1.1.0: + resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} + + pkg-types@1.1.1: + resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + pony-cause@2.1.11: + resolution: {integrity: sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==} + engines: {node: '>=12.0.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-calc@10.1.1: + resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==} + engines: {node: ^18.12 || ^20.9 || >=22.0} + peerDependencies: + postcss: ^8.4.38 + + postcss-colormin@7.0.5: + resolution: {integrity: sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-convert-values@7.0.8: + resolution: {integrity: sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-comments@7.0.5: + resolution: {integrity: sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-duplicates@7.0.2: + resolution: {integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-empty@7.0.1: + resolution: {integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-discard-overridden@7.0.1: + resolution: {integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-merge-longhand@7.0.5: + resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-merge-rules@7.0.7: + resolution: {integrity: sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-font-values@7.0.1: + resolution: {integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-gradients@7.0.1: + resolution: {integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-params@7.0.5: + resolution: {integrity: sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-minify-selectors@7.0.5: + resolution: {integrity: sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-charset@7.0.1: + resolution: {integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-display-values@7.0.1: + resolution: {integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-positions@7.0.1: + resolution: {integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-repeat-style@7.0.1: + resolution: {integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-string@7.0.1: + resolution: {integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-timing-functions@7.0.1: + resolution: {integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-unicode@7.0.5: + resolution: {integrity: sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-url@7.0.1: + resolution: {integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-normalize-whitespace@7.0.1: + resolution: {integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-ordered-values@7.0.2: + resolution: {integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-reduce-initial@7.0.5: + resolution: {integrity: sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-reduce-transforms@7.0.1: + resolution: {integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-svgo@7.1.0: + resolution: {integrity: sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==} + engines: {node: ^18.12.0 || ^20.9.0 || >= 18} + peerDependencies: + postcss: ^8.4.32 + + postcss-unique-selectors@7.0.4: + resolution: {integrity: sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.17.1: + resolution: {integrity: sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==} + + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} + + prettier-plugin-solidity@2.2.1: + resolution: {integrity: sha512-LOHfxECJ/gHsY7qi4D7vanz8cVsCf6yFotBapJ5O0qaX0ZR1sGUzbWfMd4JeQYOItFl+wXW9IcjZOdfr6bmSvQ==} + engines: {node: '>=20'} + peerDependencies: + prettier: '>=3.0.0' + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.0.3: + resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + engines: {node: '>=14'} + hasBin: true + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + + pretty-bytes@7.1.0: + resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==} + engines: {node: '>=20'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + + pretty-ms@9.0.0: + resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} + engines: {node: '>=18'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process-warning@1.0.0: + resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + promisepipe@3.0.0: + resolution: {integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prool@0.0.23: + resolution: {integrity: sha512-r1d0DIiVsp7aXqGiNGKmgrqJZa8GjMGEjsgjQO22DEClYYvK+HMPZTQ9diBqleGuwfiRk3lnsWRMbFTRmFbk9g==} + engines: {node: '>=22'} + peerDependencies: + '@pimlico/alto': '*' + peerDependenciesMeta: + '@pimlico/alto': + optional: true + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + protocols@2.0.2: + resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + + proxy-compare@2.6.0: + resolution: {integrity: sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + + publint@0.3.12: + resolution: {integrity: sha512-1w3MMtL9iotBjm1mmXtG3Nk06wnq9UhGNRpQ2j6n1Zq7YAD6gnxMMZMIxlRPAydVjVbjSm+n0lhwqsD1m4LD5w==} + engines: {node: '>=18'} + hasBin: true + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode@1.5.3: + resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.4.1: + resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} + engines: {node: '>= 0.8'} + + raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-apple-signin-auth@1.1.2: + resolution: {integrity: sha512-E5bPu4LtNR3IDsd08A/f1Y0HyuHfjqQpRNRCtQQ3JSVby2JK50FoixyK8EwUh6cbu8N4qrJStL77dEb51Ny5uA==} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-hook-form@7.69.0: + resolution: {integrity: sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-refresh@0.14.0: + resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + real-require@0.1.0: + resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} + engines: {node: '>= 12.13.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + + regex@4.4.0: + resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.17.0: + resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + rimraf@6.1.2: + resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==} + engines: {node: 20 || >=22} + hasBin: true + + ripemd160@2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + + rlp@2.2.7: + resolution: {integrity: sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==} + hasBin: true + + rolldown@1.0.0-beta.35: + resolution: {integrity: sha512-gJATyqcsJe0Cs8RMFO8XgFjfTc0lK1jcSvirDQDSIfsJE+vt53QH/Ob+OBSJsXb98YtZXHfP/bHpELpPwCprow==} + hasBin: true + + rollup-plugin-visualizer@6.0.5: + resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + secp256k1@4.0.3: + resolution: {integrity: sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==} + engines: {node: '>=10.0.0'} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-placeholder@2.0.2: + resolution: {integrity: sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.1.1: + resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + sherif-darwin-arm64@1.0.0: + resolution: {integrity: sha512-BRzDsWGjdZ6JqaDQ0HdcpapfHcnZyN24wUWpnFkljZOH78N+vB4qr+wwhmM7oyePJiO4pZWEoIBvzVT4cn1+3g==} + cpu: [arm64] + os: [darwin] + + sherif-darwin-x64@1.0.0: + resolution: {integrity: sha512-forkTw6v2N2sjvDdHGL+MqSPdLc0e7Z0v9BsmSdIKv5kdCPncVn6tRv/4xfAE7q+Xqa2a2bH9EEXppGb4gR3Tw==} + cpu: [x64] + os: [darwin] + + sherif-linux-arm64@1.0.0: + resolution: {integrity: sha512-psjD3YupFQtphWbwptM8EnU2jRkS6cnhxdxqJhMG9/yJpGsk99JD4tEmrDq0j/+T9UXZ5g7kXvQZXzocl3J62A==} + cpu: [arm64] + os: [linux] + + sherif-linux-x64@1.0.0: + resolution: {integrity: sha512-4VM2Z0xfKOEEkZ2bZppq4PAxP4RYC2eWyUq23Jl/nQFeoPMQpA9IkF51UGzxZT4WZ2kZDFftgyJeB09yPvd1CA==} + cpu: [x64] + os: [linux] + + sherif-windows-arm64@1.0.0: + resolution: {integrity: sha512-tSEzytTz3guhKLtdMCKWWru6UtmuCXD+0RsUWcqOMpzPBZZwvSr7OrTc83z8Oabmo8k6SJ5fvQeg33JSthgTqw==} + cpu: [arm64] + os: [win32] + + sherif-windows-x64@1.0.0: + resolution: {integrity: sha512-R/KXUHBWVPU9hSlWS+Gea/ogP1h/3q/Dm/quqGrVq+MN/F+fiRsJlU52EAjAJ6G5r/4RsvQddD1ova8MKsffdw==} + cpu: [x64] + os: [win32] + + sherif@1.0.0: + resolution: {integrity: sha512-x5gZqXmBT0G6Xnr2N63FwbMjaOikk/mPszl2bl3pnDMMyRi89w1ynAfcdIJpOyqZXW445418zkMIXAkQEfEtHw==} + hasBin: true + + shiki@1.22.2: + resolution: {integrity: sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.0.2: + resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==} + engines: {node: '>=14'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-git-hooks@2.11.1: + resolution: {integrity: sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg==} + hasBin: true + + simple-git@3.30.0: + resolution: {integrity: sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==} + + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + slash@1.0.0: + resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==} + engines: {node: '>=0.10.0'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + + smol-toml@1.3.0: + resolution: {integrity: sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==} + engines: {node: '>= 18'} + + socket.io-client@4.8.3: + resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.5: + resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} + engines: {node: '>=10.0.0'} + + solc@0.7.3: + resolution: {integrity: sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==} + engines: {node: '>=8.0.0'} + hasBin: true + + sonic-boom@2.8.0: + resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + srvx@0.8.9: + resolution: {integrity: sha512-wYc3VLZHRzwYrWJhkEqkhLb31TI0SOkfYZDkUhXdp3NoCnNS0FqajiQszZZjfow/VYEuc6Q5sZh9nM6kPy2NBQ==} + engines: {node: '>=20.16.0'} + hasBin: true + + srvx@0.9.8: + resolution: {integrity: sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ==} + engines: {node: '>=20.16.0'} + hasBin: true + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + stacktrace-parser@0.1.10: + resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} + engines: {node: '>=6'} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + stat-mode@0.3.0: + resolution: {integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + stream-shift@1.0.1: + resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} + + stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + + stream-to-promise@2.2.0: + resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==} + + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-hex-prefix@1.0.0: + resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} + engines: {node: '>=6.5.0', npm: '>=3'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-json-comments@5.0.1: + resolution: {integrity: sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==} + engines: {node: '>=14.16'} + + strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + strnum@2.1.2: + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} + + structured-clone-es@1.0.0: + resolution: {integrity: sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylehacks@7.0.7: + resolution: {integrity: sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.32 + + summary@2.1.0: + resolution: {integrity: sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==} + + superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + superstruct@1.0.3: + resolution: {integrity: sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==} + engines: {node: '>=14.0.0'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + + supports-hyperlinks@3.0.0: + resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + + system-architecture@0.1.0: + resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} + engines: {node: '>=18'} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tar@7.2.0: + resolution: {integrity: sha512-hctwP0Nb4AB60bj8WQgRYaMOuJYRAPMGiQUAotms5igN8ppfQM+IvjQ5HcKu1MaZh2Wy2KWVTe563Yj8dfc14w==} + engines: {node: '>=18'} + + tar@7.5.2: + resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} + engines: {node: '>=18'} + + temp-dir@1.0.0: + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} + engines: {node: '>=4'} + + tempy@0.2.1: + resolution: {integrity: sha512-LB83o9bfZGrntdqPuRdanIVCPReam9SOZKW0fOy5I9X3A854GGWi0tjCqoXEk84XIEYBc/x9Hq3EFop/H5wJaw==} + engines: {node: '>=4'} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + thread-stream@0.15.2: + resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + + time-span@4.0.0: + resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} + engines: {node: '>=10'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.0: + resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} + engines: {node: '>=0.6'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + ts-essentials@7.0.3: + resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==} + peerDependencies: + typescript: '>=3.7.0' + + ts-morph@12.0.0: + resolution: {integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==} + + ts-node@10.9.1: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + ts-toolbelt@6.15.5: + resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsort@0.0.1: + resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} + + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo-darwin-64@2.7.2: + resolution: {integrity: sha512-dxY3X6ezcT5vm3coK6VGixbrhplbQMwgNsCsvZamS/+/6JiebqW9DKt4NwpgYXhDY2HdH00I7FWs3wkVuan4rA==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.7.2: + resolution: {integrity: sha512-1bXmuwPLqNFt3mzrtYcVx1sdJ8UYb124Bf48nIgcpMCGZy3kDhgxNv1503kmuK/37OGOZbsWSQFU4I08feIuSg==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.7.2: + resolution: {integrity: sha512-kP+TiiMaiPugbRlv57VGLfcjFNsFbo8H64wMBCPV2270Or2TpDCBULMzZrvEsvWFjT3pBFvToYbdp8/Kw0jAQg==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.7.2: + resolution: {integrity: sha512-VDJwQ0+8zjAfbyY6boNaWfP6RIez4ypKHxwkuB6SrWbOSk+vxTyW5/hEjytTwK8w/TsbKVcMDyvpora8tEsRFw==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.7.2: + resolution: {integrity: sha512-rPjqQXVnI6A6oxgzNEE8DNb6Vdj2Wwyhfv3oDc+YM3U9P7CAcBIlKv/868mKl4vsBtz4ouWpTQNXG8vljgJO+w==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.7.2: + resolution: {integrity: sha512-tcnHvBhO515OheIFWdxA+qUvZzNqqcHbLVFc1+n+TJ1rrp8prYicQtbtmsiKgMvr/54jb9jOabU62URAobnB7g==} + cpu: [arm64] + os: [win32] + + turbo@2.7.2: + resolution: {integrity: sha512-5JIA5aYBAJSAhrhbyag1ZuMSgUZnHtI+Sq3H8D3an4fL8PeF+L1yYvbEJg47akP1PFfATMf5ehkqFnxfkmuwZQ==} + hasBin: true + + tweetnacl-util@0.15.1: + resolution: {integrity: sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==} + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + twoslash-protocol@0.2.12: + resolution: {integrity: sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==} + + twoslash-vue@0.2.12: + resolution: {integrity: sha512-kxH60DLn2QBcN2wjqxgMDkyRgmPXsytv7fJIlsyFMDPSkm1/lMrI/UMrNAshNaRHcI+hv8x3h/WBgcvlb2RNAQ==} + peerDependencies: + typescript: '*' + + twoslash@0.2.12: + resolution: {integrity: sha512-tEHPASMqi7kqwfJbkk7hc/4EhlrKCSLcur+TcvYki3vhIfaRMXnXjaYFgXpoZRbT6GdprD4tGuVBEmTpUgLBsw==} + peerDependencies: + typescript: '*' + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + type-fest@4.18.2: + resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} + engines: {node: '>=16'} + + type-fest@5.3.1: + resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} + engines: {node: '>=20'} + + type-level-regexp@0.1.17: + resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + uid-promise@1.0.0: + resolution: {integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==} + + uint8arrays@3.1.0: + resolution: {integrity: sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==} + + uint8arrays@3.1.1: + resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + unconfig@0.3.13: + resolution: {integrity: sha512-N9Ph5NC4+sqtcOjPfHrRcHekBCadCXWTBzp2VYYbySOHW0PfD9XLCeXshTXjkPYwLrBr9AtSeU0CZmkYECJhng==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + unctx@2.5.0: + resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + undici@5.28.3: + resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==} + engines: {node: '>=14.0'} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + unhead@2.1.1: + resolution: {integrity: sha512-NOt8n2KybAOxSLfNXegAVai4SGU8bPKqWnqCzNAvnRH2i8mW+0bbFjN/L75LBgCSTiOjJSpANe5w2V34Grr7Cw==} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unimport@3.7.1: + resolution: {integrity: sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==} + + unimport@5.6.0: + resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==} + engines: {node: '>=18.12.0'} + + unique-string@1.0.0: + resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==} + engines: {node: '>=4'} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unocss@0.59.4: + resolution: {integrity: sha512-QmCVjRObvVu/gsGrJGVt0NnrdhFFn314BUZn2WQyXV9rIvHLRmG5bIu0j5vibJkj7ZhFchTrnTM1pTFXP1xt5g==} + engines: {node: '>=14'} + peerDependencies: + '@unocss/webpack': 0.59.4 + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 + peerDependenciesMeta: + '@unocss/webpack': + optional: true + vite: + optional: true + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unplugin-utils@0.2.5: + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} + engines: {node: '>=18.12.0'} + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin-vue-router@0.15.0: + resolution: {integrity: sha512-PyGehCjd9Ny9h+Uer4McbBjjib3lHihcyUEILa7pHKl6+rh8N7sFyw4ZkV+N30Oq2zmIUG7iKs3qpL0r+gXAaQ==} + peerDependencies: + '@vue/compiler-sfc': ^3.5.17 + vue-router: ^4.5.1 + peerDependenciesMeta: + vue-router: + optional: true + + unplugin@1.10.1: + resolution: {integrity: sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==} + engines: {node: '>=14.0.0'} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + unstorage@1.10.2: + resolution: {integrity: sha512-cULBcwDqrS8UhlIysUJs2Dk0Mmt8h7B0E6mtR+relW9nZvsf/u4SkAYyNliPiPW7XtFNb5u3IUMkxGxFTTRTgQ==} + peerDependencies: + '@azure/app-configuration': ^1.5.0 + '@azure/cosmos': ^4.0.0 + '@azure/data-tables': ^13.2.2 + '@azure/identity': ^4.0.1 + '@azure/keyvault-secrets': ^4.8.0 + '@azure/storage-blob': ^12.17.0 + '@capacitor/preferences': ^5.0.7 + '@netlify/blobs': ^6.5.0 || ^7.0.0 + '@planetscale/database': ^1.16.0 + '@upstash/redis': ^1.28.4 + '@vercel/kv': ^1.0.1 + idb-keyval: ^6.2.1 + ioredis: ^5.3.2 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/kv': + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + + unstorage@1.17.3: + resolution: {integrity: sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + untun@0.1.3: + resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} + hasBin: true + + untyped@1.4.2: + resolution: {integrity: sha512-nC5q0DnPEPVURPhfPQLahhSTnemVtPzdx7ofiRxXpOB2SYnb3MfdU3DVGyJdS8Lx+tBWeAePO8BfU/3EgksM7Q==} + hasBin: true + + untyped@2.0.0: + resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} + hasBin: true + + unwasm@0.3.11: + resolution: {integrity: sha512-Vhp5gb1tusSQw5of/g3Q697srYgMXvwMgXMjcG4ZNga02fDX9coxJ9fAb0Ci38hM2Hv/U1FXRPGgjP2BYqhNoQ==} + + update-browserslist-db@1.0.15: + resolution: {integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uqr@0.1.2: + resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + + utf-8-validate@6.0.5: + resolution: {integrity: sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==} + engines: {node: '>=6.14.2'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + valtio@1.13.2: + resolution: {integrity: sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=16.8' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + vercel@48.12.1: + resolution: {integrity: sha512-+lMj+qIXI/Iy7UXKu1wpFCwCaeV1lmrUdBbYQWXBM1/9XsX8vUfohHLkPrPSam8tDyVghKmaYu1ZD5uuHgo5uw==} + engines: {node: '>= 18'} + hasBin: true + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + + viem@2.10.8: + resolution: {integrity: sha512-ttCXlDmjjcZ8M/eJezXFzDtHj+RFOjEQ3elmXnCC7suXo/y8CuIM1LrIoyUFk7LKIE5E+bzmWUErS4u/MQBtpQ==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.17.0: + resolution: {integrity: sha512-+gaVlsfDsHL1oYdjpatdRxW1WK/slLYVvpOws3fEdLfQFUToezKI6YLC9l1g2uKm4Hg3OdGX1KQy/G7/58tTKQ==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.23.2: + resolution: {integrity: sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.31.4: + resolution: {integrity: sha512-0UZ/asvzl6p44CIBRDbwEcn3HXIQQurBZcMo5qmLhQ8s27Ockk+RYohgTLlpLvkYs8/t4UUEREAbHLuek1kXcw==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.43.3: + resolution: {integrity: sha512-zM251fspfSjENCtfmT7cauuD+AA/YAlkFU7cksdEQJxj7wDuO0XFRWRH+RMvfmTFza88B9kug5cKU+Wk2nAjJg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.43.5: + resolution: {integrity: sha512-QuJpuEMEPM3EreN+vX4mVY68Sci0+zDxozYfbh/WfV+SSy/Gthm74PH8XmitXdty1xY54uTCJ+/Gbbd1IiMPSA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + vite-dev-rpc@1.1.0: + resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite-plugin-checker@0.10.3: + resolution: {integrity: sha512-f4sekUcDPF+T+GdbbE8idb1i2YplBAoH+SfRS0e/WRBWb2rYb1Jf5Pimll0Rj+3JgIYWwG2K5LtBPCXxoibkLg==} + engines: {node: '>=14.16'} + peerDependencies: + '@biomejs/biome': '>=1.7' + eslint: '>=7' + meow: ^13.2.0 + optionator: ^0.9.4 + stylelint: '>=16' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: ~2.2.10 || ^3.0.0 + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + + vite-plugin-inspect@11.3.3: + resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-tracer@1.2.0: + resolution: {integrity: sha512-a9Z/TLpxwmoE9kIcv28wqQmiszM7ec4zgndXWEsVD/2lEZLRGzcg7ONXmplzGF/UP5W59QNtS809OdywwpUWQQ==} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 + vue: ^3.5.0 + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress@1.5.0: + resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-bundle-renderer@2.2.0: + resolution: {integrity: sha512-sz/0WEdYH1KfaOm0XaBmRZOWgYTEvUDt6yPYaUzl4E52qzgWLlknaPPTTZmp6benaPTlQAI/hN1x3tAzZygycg==} + + vue-component-type-helpers@2.0.16: + resolution: {integrity: sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-devtools-stub@0.1.0: + resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} + + vue-resize@2.0.0-alpha.1: + resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==} + peerDependencies: + vue: ^3.0.0 + + vue-router@4.3.2: + resolution: {integrity: sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==} + peerDependencies: + vue: ^3.2.0 + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-template-compiler@2.7.16: + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} + + vue-tsc@2.0.16: + resolution: {integrity: sha512-/gHAWJa216PeEhfxtAToIbxdWgw01wuQzo48ZUqMYVEyNqDp+OYV9xMO5HaPS2P3Ls0+EsjguMZLY4cGobX4Ew==} + hasBin: true + peerDependencies: + typescript: '*' + + vue@3.4.27: + resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vue@3.5.12: + resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + web-vitals@0.2.4: + resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} + + webextension-polyfill@0.10.0: + resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-bundle-analyzer@4.10.1: + resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} + engines: {node: '>= 10.13.0'} + hasBin: true + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack-virtual-modules@0.6.1: + resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-module@2.0.0: + resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + 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.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + 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@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + 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.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + 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.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + 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.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + 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.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + 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 + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + xdg-app-paths@5.1.0: + resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==} + engines: {node: '>=6'} + + xdg-portable@7.3.0: + resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==} + engines: {node: '>= 6.0'} + + xmlhttprequest-ssl@2.1.2: + resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} + engines: {node: '>=0.4.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl-clone@1.0.4: + resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==} + engines: {node: '>=6'} + + yauzl-promise@2.1.3: + resolution: {integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==} + engines: {node: '>=6'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors@2.0.2: + resolution: {integrity: sha512-Ct97huExsu7cWeEjmrXlofevF8CvzUglJ4iGUet5B8xn1oumtAZBpHU4GzYuoE6PVqcZ5hghtBrSlhwHuR1Jmw==} + engines: {node: '>=18'} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.13: + resolution: {integrity: sha512-3+AG1Xvt+R7M7PSDudhbfbwiyveW6B8PLBIwTyEC598biEYIjHhC89i6DBEvR0EZUjGY3uGSnC429HpIa2Z09g==} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + + zod-validation-error@3.2.0: + resolution: {integrity: sha512-cYlPR6zuyrgmu2wRTdumEAJGuwI7eHVHGT+VyneAQxmRAKtGRL1/7pjz4wfLhz4J05f5qoSZc3rGacswgyTjjw==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.18.0 + + zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + + zod@3.25.20: + resolution: {integrity: sha512-z03fqpTMDF1G02VLKUMt6vyACE7rNWkh3gpXVHgPTw28NPtDFRGvcpTtPwn2kMKtQ0idtYJUTxchytmnqYswcw==} + + zustand@5.0.0: + resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + 0xsequence@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)): + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/account': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/api': 2.3.39 + '@0xsequence/auth': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/guard': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/indexer': 2.3.39 + '@0xsequence/metadata': 2.3.39 + '@0xsequence/migration': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/provider': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/relayer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/sessions': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/signhub': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/abi@2.3.39': {} + + '@0xsequence/account@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/migration': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/relayer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/sessions': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/api@2.3.39': {} + + '@0xsequence/auth@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/account': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/api': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/ethauth': 1.0.0(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/indexer': 2.3.39 + '@0xsequence/metadata': 2.3.39 + '@0xsequence/migration': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/sessions': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/signhub': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/connect@0.0.0-20250924112110(@types/react-dom@18.3.0)(@types/react@18.3.27)(bufferutil@4.1.0)(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(utf-8-validate@6.0.5)(viem@2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20))(wagmi@packages+react)(zod@3.25.20)': + dependencies: + 0xsequence: 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/api': 2.3.39 + '@0xsequence/auth': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/dapp-client': git+https://git@github.com:0xsequence/sequence.js.git#6a3501096be6b465b65c034b648549c0b69f8990(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + '@0xsequence/design-system': 2.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(motion@12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@0xsequence/ethauth': 1.0.0(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/hooks': 0.0.0-20250924112110(@0xsequence/api@2.3.39)(@0xsequence/indexer@2.3.39)(@0xsequence/metadata@2.3.39)(@0xsequence/network@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)))(@tanstack/react-query@5.90.15(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20)) + '@0xsequence/indexer': 2.3.39 + '@0xsequence/metadata': 2.3.39 + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/provider': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/waas': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@databeat/tracker': 0.9.3 + '@react-oauth/google': 0.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tailwindcss/cli': 4.1.18 + '@tanstack/react-query': 5.90.15(react@18.3.1) + clsx: 2.1.1 + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + fuse.js: 7.1.0 + motion: 12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-apple-signin-auth: 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + tailwindcss: 4.1.18 + uuid: 10.0.0 + viem: 2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + wagmi: link:packages/react + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@types/react' + - '@types/react-dom' + - aws-crt + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/core@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/dapp-client@git+https://git@github.com:0xsequence/sequence.js.git#6a3501096be6b465b65c034b648549c0b69f8990(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20)': + dependencies: + '@0xsequence/guard': git+https://git@github.com:0xsequence/sequence.js.git#501693a7bd7332ad10fd0f4a72e0dd153b44af0a(typescript@5.8.3)(zod@3.25.20) + '@0xsequence/relayer': git+https://git@github.com:0xsequence/sequence.js.git#07221565e01c85b8cd0bcbb45ace5f9baf57f498(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + '@0xsequence/wallet-core': git+https://git@github.com:0xsequence/sequence.js.git#a623e7dad948e7b281119b34b5b6343bdf502eb8(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + '@0xsequence/wallet-primitives': git+https://git@github.com:0xsequence/sequence.js.git#d2967108579e84603f3b5e05685c6f331c1f3db7(typescript@5.8.3)(zod@3.25.20) + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/design-system@2.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(motion@12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-aspect-ratio': 1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': 1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 2.2.6(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + motion: 12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-hook-form: 7.69.0(react@18.3.1) + tailwind-merge: 3.4.0 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + '@0xsequence/ethauth@1.0.0(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + js-base64: 3.7.8 + + '@0xsequence/guard@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/account': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/signhub': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/guard@git+https://git@github.com:0xsequence/sequence.js.git#501693a7bd7332ad10fd0f4a72e0dd153b44af0a(typescript@5.8.3)(zod@3.25.20)': + dependencies: + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/hooks@0.0.0-20250924112110(@0xsequence/api@2.3.39)(@0xsequence/indexer@2.3.39)(@0xsequence/metadata@2.3.39)(@0xsequence/network@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)))(@tanstack/react-query@5.90.15(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20))': + dependencies: + '@0xsequence/api': 2.3.39 + '@0xsequence/indexer': 2.3.39 + '@0xsequence/metadata': 2.3.39 + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@tanstack/react-query': 5.90.15(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + viem: 2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + + '@0xsequence/indexer@2.3.39': {} + + '@0xsequence/metadata@2.3.39': {} + + '@0xsequence/migration@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/network@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/indexer': 2.3.39 + '@0xsequence/relayer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/provider@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/account': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/auth': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/migration': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/relayer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/wallet': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@databeat/tracker': 0.9.3 + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + eventemitter2: 6.4.9 + webextension-polyfill: 0.10.0 + + '@0xsequence/relayer@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/relayer@git+https://git@github.com:0xsequence/sequence.js.git#07221565e01c85b8cd0bcbb45ace5f9baf57f498(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20)': + dependencies: + '@0xsequence/wallet-primitives': git+https://git@github.com:0xsequence/sequence.js.git#d2967108579e84603f3b5e05685c6f331c1f3db7(typescript@5.8.3)(zod@3.25.20) + mipd: 0.0.7(typescript@5.8.3) + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + viem: 2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/replacer@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/sessions@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/migration': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/replacer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + idb: 7.1.1 + + '@0xsequence/signhub@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@0xsequence/utils@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + js-base64: 3.7.8 + + '@0xsequence/waas@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@aws-sdk/client-cognito-identity-provider': 3.958.0 + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + idb: 7.1.1 + json-canonicalize: 1.2.0 + jwt-decode: 4.0.0 + transitivePeerDependencies: + - aws-crt + + '@0xsequence/wallet-contracts@3.0.1(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)': + dependencies: + '@typechain/ethers-v6': 0.5.1(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))(typescript@5.8.3) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + keccak256: 1.0.6 + transitivePeerDependencies: + - bufferutil + - typechain + - typescript + - utf-8-validate + + '@0xsequence/wallet-core@git+https://git@github.com:0xsequence/sequence.js.git#a623e7dad948e7b281119b34b5b6343bdf502eb8(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20)': + dependencies: + '@0xsequence/guard': git+https://git@github.com:0xsequence/sequence.js.git#501693a7bd7332ad10fd0f4a72e0dd153b44af0a(typescript@5.8.3)(zod@3.25.20) + '@0xsequence/relayer': git+https://git@github.com:0xsequence/sequence.js.git#07221565e01c85b8cd0bcbb45ace5f9baf57f498(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + '@0xsequence/wallet-primitives': git+https://git@github.com:0xsequence/sequence.js.git#d2967108579e84603f3b5e05685c6f331c1f3db7(typescript@5.8.3)(zod@3.25.20) + mipd: 0.0.7(typescript@5.8.3) + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + viem: 2.31.4(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@0xsequence/wallet-primitives@0.0.0-20250915145821(typescript@5.8.3)(zod@3.25.20)': + dependencies: + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/wallet-primitives@git+https://git@github.com:0xsequence/sequence.js.git#d2967108579e84603f3b5e05685c6f331c1f3db7(typescript@5.8.3)(zod@3.25.20)': + dependencies: + ox: 0.7.2(typescript@5.8.3)(zod@3.25.20) + transitivePeerDependencies: + - typescript + - zod + + '@0xsequence/wallet@2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))': + dependencies: + '@0xsequence/abi': 2.3.39 + '@0xsequence/core': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/network': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/relayer': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/signhub': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + '@0xsequence/utils': 2.3.39(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + + '@adraffy/ens-normalize@1.10.0': {} + + '@adraffy/ens-normalize@1.10.1': {} + + '@adraffy/ens-normalize@1.11.1': {} + + '@algolia/autocomplete-core@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + dependencies: + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@algolia/client-search': 5.12.0 + algoliasearch: 5.12.0 + + '@algolia/autocomplete-shared@1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + dependencies: + '@algolia/client-search': 5.12.0 + algoliasearch: 5.12.0 + + '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0)': + dependencies: + '@algolia/client-search': 5.12.0 + algoliasearch: 5.12.0 + + '@algolia/client-abtesting@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/client-analytics@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/client-common@5.12.0': {} + + '@algolia/client-insights@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/client-personalization@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/client-query-suggestions@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/client-search@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/ingestion@1.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/monitoring@1.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/recommend@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + '@algolia/requester-browser-xhr@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + + '@algolia/requester-fetch@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + + '@algolia/requester-node-http@5.12.0': + dependencies: + '@algolia/client-common': 5.12.0 + + '@ampproject/remapping@2.2.1': + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.17 + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@andrewbranch/untar.js@1.0.3': {} + + '@antfu/install-pkg@0.1.1': + dependencies: + execa: 5.1.1 + find-up: 5.0.0 + + '@antfu/utils@0.7.7': {} + + '@arethetypeswrong/cli@0.16.4': + dependencies: + '@arethetypeswrong/core': 0.16.4 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.1.0(marked@9.1.6) + semver: 7.6.2 + + '@arethetypeswrong/core@0.16.4': + dependencies: + '@andrewbranch/untar.js': 1.0.3 + cjs-module-lexer: 1.4.1 + fflate: 0.8.2 + lru-cache: 10.4.3 + semver: 7.7.3 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-locate-window': 3.957.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.957.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-cognito-identity-provider@3.958.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/credential-provider-node': 3.958.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.958.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@aws-sdk/xml-builder': 3.957.0 + '@smithy/core': 3.20.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/signature-v4': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/credential-provider-env': 3.957.0 + '@aws-sdk/credential-provider-http': 3.957.0 + '@aws-sdk/credential-provider-login': 3.958.0 + '@aws-sdk/credential-provider-process': 3.957.0 + '@aws-sdk/credential-provider-sso': 3.958.0 + '@aws-sdk/credential-provider-web-identity': 3.958.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.958.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.957.0 + '@aws-sdk/credential-provider-http': 3.957.0 + '@aws-sdk/credential-provider-ini': 3.958.0 + '@aws-sdk/credential-provider-process': 3.957.0 + '@aws-sdk/credential-provider-sso': 3.958.0 + '@aws-sdk/credential-provider-web-identity': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.958.0': + dependencies: + '@aws-sdk/client-sso': 3.958.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/token-providers': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-host-header@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@aws/lambda-invoke-store': 0.2.2 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.957.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@smithy/core': 3.20.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.958.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.957.0 + '@aws-sdk/middleware-host-header': 3.957.0 + '@aws-sdk/middleware-logger': 3.957.0 + '@aws-sdk/middleware-recursion-detection': 3.957.0 + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/region-config-resolver': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@aws-sdk/util-endpoints': 3.957.0 + '@aws-sdk/util-user-agent-browser': 3.957.0 + '@aws-sdk/util-user-agent-node': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/core': 3.20.0 + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/hash-node': 4.2.7 + '@smithy/invalid-dependency': 4.2.7 + '@smithy/middleware-content-length': 4.2.7 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-retry': 4.4.17 + '@smithy/middleware-serde': 4.2.8 + '@smithy/middleware-stack': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/node-http-handler': 4.4.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.16 + '@smithy/util-defaults-mode-node': 4.2.19 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/config-resolver': 4.4.5 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.958.0': + dependencies: + '@aws-sdk/core': 3.957.0 + '@aws-sdk/nested-clients': 3.958.0 + '@aws-sdk/types': 3.957.0 + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.957.0': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-endpoints': 3.2.7 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.957.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.957.0': + dependencies: + '@aws-sdk/types': 3.957.0 + '@smithy/types': 4.11.0 + bowser: 2.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.957.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.957.0 + '@aws-sdk/types': 3.957.0 + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.957.0': + dependencies: + '@smithy/types': 4.11.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.2': {} + + '@babel/code-frame@7.24.2': + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.1.1 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.24.4': {} + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.24.5': + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helpers': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.24.5': + dependencies: + '@babel/types': 7.27.1 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.22.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-compilation-targets@7.23.6': + dependencies: + '@babel/compat-data': 7.24.4 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.24.5(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.24.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-split-export-declaration': 7.24.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-environment-visitor@7.22.20': {} + + '@babel/helper-function-name@7.23.0': + dependencies: + '@babel/template': 7.24.0 + '@babel/types': 7.28.5 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-hoist-variables@7.22.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-member-expression-to-functions@7.24.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.24.3': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.22.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-plugin-utils@7.24.5': {} + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.24.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.24.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-string-parser@7.24.1': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.24.5': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.23.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.24.5': + dependencies: + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/highlight@7.24.5': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/parser@7.24.5': + dependencies: + '@babel/types': 7.27.1 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-simple-access': 7.24.5 + + '@babel/plugin-transform-react-jsx-self@7.24.5(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + + '@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + + '@babel/plugin-transform-typescript@7.24.5(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.24.5) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.24.5) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.24.1(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-validator-option': 7.23.5 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.5) + '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.24.5) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.0 + + '@babel/runtime@7.28.4': {} + + '@babel/standalone@7.24.5': {} + + '@babel/template@7.24.0': + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.24.5': + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/parser': 7.26.2 + '@babel/types': 7.27.1 + debug: 4.4.3 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.5': + dependencies: + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.24.5 + to-fast-properties: 2.0.0 + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@babel/types@7.27.1': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@bomb.sh/tab@0.0.10(cac@6.7.14)(citty@0.1.6)': + optionalDependencies: + cac: 6.7.14 + citty: 0.1.6 + + '@bundled-es-modules/cookie@2.0.0': + dependencies: + cookie: 0.5.0 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@bytecodealliance/preview2-shim@0.17.6': {} + + '@changesets/apply-release-plan@7.0.14': + dependencies: + '@changesets/config': 3.1.2 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.3 + + '@changesets/apply-release-plan@7.0.5': + dependencies: + '@changesets/config': 3.0.3 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.3 + + '@changesets/assemble-release-plan@6.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.3 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.3 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/changelog-github@0.4.6(encoding@0.1.13)': + dependencies: + '@changesets/get-github-info': 0.5.2(encoding@0.1.13) + '@changesets/types': 5.2.1 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + + '@changesets/cli@2.27.8': + dependencies: + '@changesets/apply-release-plan': 7.0.5 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.3 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.4 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.2 + '@manypkg/get-packages': 1.1.3 + '@types/semver': 7.5.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.3.6 + external-editor: 3.1.0 + fs-extra: 7.0.1 + mri: 1.2.0 + outdent: 0.5.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.0 + picocolors: 1.1.0 + resolve-from: 5.0.0 + semver: 7.6.2 + spawndamnit: 2.0.0 + term-size: 2.2.1 + + '@changesets/cli@2.29.8(@types/node@24.0.1)': + dependencies: + '@changesets/apply-release-plan': 7.0.14 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.2 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.14 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@24.0.1) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.3 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.0.3': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 + + '@changesets/config@3.1.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.2': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.0 + semver: 7.7.3 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.3 + + '@changesets/get-github-info@0.5.2(encoding@0.1.13)': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.6.9(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + '@changesets/get-release-plan@4.0.14': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.2 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-release-plan@4.0.4': + dependencies: + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/config': 3.0.3 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.1': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.5 + spawndamnit: 2.0.0 + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.0 + + '@changesets/parse@0.4.0': + dependencies: + '@changesets/types': 6.0.0 + js-yaml: 3.14.1 + + '@changesets/parse@0.4.2': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/pre@2.0.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.1': + dependencies: + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.0 + + '@changesets/read@0.6.6': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.2 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.1': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@5.2.1': {} + + '@changesets/types@6.0.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.3.2': + dependencies: + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.8.8 + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.3 + prettier: 2.8.8 + + '@clack/core@1.0.0-alpha.7': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@1.0.0-alpha.8': + dependencies: + '@clack/core': 1.0.0-alpha.7 + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@cloudflare/kv-asset-handler@0.4.1': + dependencies: + mime: 3.0.0 + + '@coinbase/wallet-sdk@3.9.3': + dependencies: + bn.js: 5.2.1 + buffer: 6.0.3 + clsx: 1.2.1 + eth-block-tracker: 7.1.0 + eth-json-rpc-filters: 6.0.1 + eventemitter3: 5.0.1 + keccak: 3.0.3 + preact: 10.17.1 + sha.js: 2.4.11 + transitivePeerDependencies: + - supports-color + + '@coinbase/wallet-sdk@4.3.0': + dependencies: + '@noble/hashes': 1.5.0 + clsx: 1.2.1 + eventemitter3: 5.0.1 + preact: 10.24.3 + + '@colors/colors@1.5.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@databeat/tracker@0.9.3': + dependencies: + '@noble/hashes': 1.8.0 + + '@discoveryjs/json-ext@0.5.7': {} + + '@docsearch/css@3.6.3': {} + + '@docsearch/js@3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@docsearch/react': 3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + preact: 10.24.3 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@algolia/autocomplete-preset-algolia': 1.17.6(@algolia/client-search@5.12.0)(algoliasearch@5.12.0) + '@docsearch/css': 3.6.3 + algoliasearch: 5.12.0 + optionalDependencies: + '@types/react': 18.3.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@algolia/client-search' + + '@ecies/ciphers@0.2.5(@noble/ciphers@1.3.0)': + dependencies: + '@noble/ciphers': 1.3.0 + + '@edge-runtime/format@2.2.1': {} + + '@edge-runtime/node-utils@2.3.0': {} + + '@edge-runtime/ponyfill@2.4.2': {} + + '@edge-runtime/primitives@4.1.0': {} + + '@edge-runtime/vm@3.2.0': + dependencies: + '@edge-runtime/primitives': 4.1.0 + + '@emnapi/core@1.7.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@ethereumjs/common@3.2.0': + dependencies: + '@ethereumjs/util': 8.1.0 + crc-32: 1.2.2 + + '@ethereumjs/rlp@4.0.1': {} + + '@ethereumjs/tx@4.2.0': + dependencies: + '@ethereumjs/common': 3.2.0 + '@ethereumjs/rlp': 4.0.1 + '@ethereumjs/util': 8.1.0 + ethereum-cryptography: 2.2.1 + + '@ethereumjs/util@8.1.0': + dependencies: + '@ethereumjs/rlp': 4.0.1 + ethereum-cryptography: 2.2.1 + micro-ftch: 0.3.1 + + '@ethersproject/abi@5.7.0': + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + + '@ethersproject/abstract-provider@5.7.0': + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 + + '@ethersproject/abstract-signer@5.7.0': + dependencies: + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + + '@ethersproject/address@5.7.0': + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/rlp': 5.7.0 + + '@ethersproject/base64@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + + '@ethersproject/bignumber@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + bn.js: 5.2.1 + + '@ethersproject/bytes@5.7.0': + dependencies: + '@ethersproject/logger': 5.7.0 + + '@ethersproject/constants@5.7.0': + dependencies: + '@ethersproject/bignumber': 5.7.0 + + '@ethersproject/hash@5.7.0': + dependencies: + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + + '@ethersproject/keccak256@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + js-sha3: 0.8.0 + + '@ethersproject/logger@5.7.0': {} + + '@ethersproject/networks@5.7.1': + dependencies: + '@ethersproject/logger': 5.7.0 + + '@ethersproject/properties@5.7.0': + dependencies: + '@ethersproject/logger': 5.7.0 + + '@ethersproject/rlp@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + + '@ethersproject/signing-key@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + bn.js: 5.2.2 + elliptic: 6.5.4 + hash.js: 1.1.7 + + '@ethersproject/strings@5.7.0': + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + + '@ethersproject/transactions@5.7.0': + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + + '@ethersproject/web@5.7.1': + dependencies: + '@ethersproject/base64': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + + '@fastify/busboy@2.1.0': {} + + '@fastify/busboy@2.1.1': {} + + '@floating-ui/core@1.6.1': + dependencies: + '@floating-ui/utils': 0.2.2 + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.1.1': + dependencies: + '@floating-ui/core': 1.6.1 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.10': {} + + '@floating-ui/utils@0.2.2': {} + + '@iarna/toml@2.2.5': {} + + '@iconify-json/simple-icons@1.2.10': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.1.23': + dependencies: + '@antfu/install-pkg': 0.1.1 + '@antfu/utils': 0.7.7 + '@iconify/types': 2.0.0 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 0.5.1 + mlly: 1.8.0 + transitivePeerDependencies: + - supports-color + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@inquirer/confirm@3.1.6': + dependencies: + '@inquirer/core': 8.1.0 + '@inquirer/type': 1.3.1 + + '@inquirer/core@8.1.0': + dependencies: + '@inquirer/figures': 1.0.1 + '@inquirer/type': 1.3.1 + '@types/mute-stream': 0.0.4 + '@types/node': 20.19.27 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + '@inquirer/external-editor@1.0.3(@types/node@24.0.1)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.1 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/figures@1.0.1': {} + + '@inquirer/type@1.3.1': {} + + '@ioredis/commands@1.4.0': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/gen-mapping@0.3.3': + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.0': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.4.14': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.17': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + + '@lit-labs/ssr-dom-shim@1.3.0': {} + + '@lit/reactive-element@2.1.0': + dependencies: + '@lit-labs/ssr-dom-shim': 1.3.0 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.4 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.4 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@mapbox/node-pre-gyp@2.0.3(encoding@0.1.13)': + dependencies: + consola: 3.4.2 + detect-libc: 2.1.2 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0(encoding@0.1.13) + nopt: 8.1.0 + semver: 7.7.3 + tar: 7.5.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@metamask/eth-json-rpc-provider@1.0.1': + dependencies: + '@metamask/json-rpc-engine': 7.3.3 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 5.0.2 + transitivePeerDependencies: + - supports-color + + '@metamask/eth-sig-util@4.0.1': + dependencies: + ethereumjs-abi: 0.6.8 + ethereumjs-util: 6.2.1 + ethjs-util: 0.1.6 + tweetnacl: 1.0.3 + tweetnacl-util: 0.15.1 + + '@metamask/json-rpc-engine@7.3.3': + dependencies: + '@metamask/rpc-errors': 6.4.0 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + transitivePeerDependencies: + - supports-color + + '@metamask/json-rpc-engine@8.0.2': + dependencies: + '@metamask/rpc-errors': 6.4.0 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + transitivePeerDependencies: + - supports-color + + '@metamask/json-rpc-middleware-stream@7.0.2': + dependencies: + '@metamask/json-rpc-engine': 8.0.2 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + readable-stream: 3.6.2 + transitivePeerDependencies: + - supports-color + + '@metamask/object-multiplex@2.1.0': + dependencies: + once: 1.4.0 + readable-stream: 3.6.2 + + '@metamask/onboarding@1.0.1': + dependencies: + bowser: 2.13.1 + + '@metamask/providers@16.1.0': + dependencies: + '@metamask/json-rpc-engine': 8.0.2 + '@metamask/json-rpc-middleware-stream': 7.0.2 + '@metamask/object-multiplex': 2.1.0 + '@metamask/rpc-errors': 6.4.0 + '@metamask/safe-event-emitter': 3.1.2 + '@metamask/utils': 8.5.0 + detect-browser: 5.3.0 + extension-port-stream: 3.0.0 + fast-deep-equal: 3.1.3 + is-stream: 2.0.1 + readable-stream: 3.6.2 + webextension-polyfill: 0.10.0 + transitivePeerDependencies: + - supports-color + + '@metamask/rpc-errors@6.4.0': + dependencies: + '@metamask/utils': 9.3.0 + fast-safe-stringify: 2.1.1 + transitivePeerDependencies: + - supports-color + + '@metamask/safe-event-emitter@2.0.0': {} + + '@metamask/safe-event-emitter@3.0.0': {} + + '@metamask/safe-event-emitter@3.1.2': {} + + '@metamask/sdk-analytics@0.0.5': + dependencies: + openapi-fetch: 0.13.8 + + '@metamask/sdk-communication-layer@0.33.1(cross-fetch@4.1.0(encoding@0.1.13))(eciesjs@0.4.16)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + dependencies: + '@metamask/sdk-analytics': 0.0.5 + bufferutil: 4.1.0 + cross-fetch: 4.1.0(encoding@0.1.13) + date-fns: 2.30.0 + debug: 4.3.4(supports-color@8.1.1) + eciesjs: 0.4.16 + eventemitter2: 6.4.9 + readable-stream: 3.6.2 + socket.io-client: 4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + utf-8-validate: 5.0.10 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + + '@metamask/sdk-install-modal-web@0.32.1': + dependencies: + '@paulmillr/qr': 0.2.1 + + '@metamask/sdk@0.33.1(bufferutil@4.1.0)(encoding@0.1.13)(utf-8-validate@5.0.10)': + dependencies: + '@babel/runtime': 7.28.4 + '@metamask/onboarding': 1.0.1 + '@metamask/providers': 16.1.0 + '@metamask/sdk-analytics': 0.0.5 + '@metamask/sdk-communication-layer': 0.33.1(cross-fetch@4.1.0(encoding@0.1.13))(eciesjs@0.4.16)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + '@metamask/sdk-install-modal-web': 0.32.1 + '@paulmillr/qr': 0.2.1 + bowser: 2.13.1 + cross-fetch: 4.1.0(encoding@0.1.13) + debug: 4.3.4(supports-color@8.1.1) + eciesjs: 0.4.16 + eth-rpc-errors: 4.0.3 + eventemitter2: 6.4.9 + obj-multiplex: 1.0.0 + pump: 3.0.3 + readable-stream: 3.6.2 + socket.io-client: 4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + tslib: 2.8.1 + util: 0.12.5 + uuid: 8.3.2 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@metamask/superstruct@3.2.1': {} + + '@metamask/utils@5.0.2': + dependencies: + '@ethereumjs/tx': 4.2.0 + '@types/debug': 4.1.7 + debug: 4.4.3 + semver: 7.7.3 + superstruct: 1.0.3 + transitivePeerDependencies: + - supports-color + + '@metamask/utils@8.5.0': + dependencies: + '@ethereumjs/tx': 4.2.0 + '@metamask/superstruct': 3.2.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@types/debug': 4.1.12 + debug: 4.3.4(supports-color@8.1.1) + pony-cause: 2.1.11 + semver: 7.7.3 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + + '@metamask/utils@9.3.0': + dependencies: + '@ethereumjs/tx': 4.2.0 + '@metamask/superstruct': 3.2.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@types/debug': 4.1.12 + debug: 4.3.4(supports-color@8.1.1) + pony-cause: 2.1.11 + semver: 7.7.3 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + + '@mswjs/interceptors@0.35.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.0': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@next/bundle-analyzer@15.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.5)': + dependencies: + webpack-bundle-analyzer: 4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@next/env@15.4.10': {} + + '@next/swc-darwin-arm64@15.4.8': + optional: true + + '@next/swc-darwin-x64@15.4.8': + optional: true + + '@next/swc-linux-arm64-gnu@15.4.8': + optional: true + + '@next/swc-linux-arm64-musl@15.4.8': + optional: true + + '@next/swc-linux-x64-gnu@15.4.8': + optional: true + + '@next/swc-linux-x64-musl@15.4.8': + optional: true + + '@next/swc-win32-arm64-msvc@15.4.8': + optional: true + + '@next/swc-win32-x64-msvc@15.4.8': + optional: true + + '@noble/ciphers@1.2.1': {} + + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.2.0': + dependencies: + '@noble/hashes': 1.3.2 + + '@noble/curves@1.4.0': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.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/hashes@1.2.0': {} + + '@noble/hashes@1.3.2': {} + + '@noble/hashes@1.4.0': {} + + '@noble/hashes@1.5.0': {} + + '@noble/hashes@1.7.0': {} + + '@noble/hashes@1.7.1': {} + + '@noble/hashes@1.8.0': {} + + '@noble/secp256k1@1.7.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + + '@nomicfoundation/edr-darwin-arm64@0.3.7': + optional: true + + '@nomicfoundation/edr-darwin-x64@0.3.7': + optional: true + + '@nomicfoundation/edr-linux-arm64-gnu@0.3.7': + optional: true + + '@nomicfoundation/edr-linux-arm64-musl@0.3.7': + optional: true + + '@nomicfoundation/edr-linux-x64-gnu@0.3.7': + optional: true + + '@nomicfoundation/edr-linux-x64-musl@0.3.7': + optional: true + + '@nomicfoundation/edr-win32-x64-msvc@0.3.7': + optional: true + + '@nomicfoundation/edr@0.3.7': + optionalDependencies: + '@nomicfoundation/edr-darwin-arm64': 0.3.7 + '@nomicfoundation/edr-darwin-x64': 0.3.7 + '@nomicfoundation/edr-linux-arm64-gnu': 0.3.7 + '@nomicfoundation/edr-linux-arm64-musl': 0.3.7 + '@nomicfoundation/edr-linux-x64-gnu': 0.3.7 + '@nomicfoundation/edr-linux-x64-musl': 0.3.7 + '@nomicfoundation/edr-win32-x64-msvc': 0.3.7 + + '@nomicfoundation/ethereumjs-common@4.0.4': + dependencies: + '@nomicfoundation/ethereumjs-util': 9.0.4 + transitivePeerDependencies: + - c-kzg + + '@nomicfoundation/ethereumjs-rlp@5.0.4': {} + + '@nomicfoundation/ethereumjs-tx@5.0.4': + dependencies: + '@nomicfoundation/ethereumjs-common': 4.0.4 + '@nomicfoundation/ethereumjs-rlp': 5.0.4 + '@nomicfoundation/ethereumjs-util': 9.0.4 + ethereum-cryptography: 0.1.3 + + '@nomicfoundation/ethereumjs-util@9.0.4': + dependencies: + '@nomicfoundation/ethereumjs-rlp': 5.0.4 + ethereum-cryptography: 0.1.3 + + '@nomicfoundation/slang@1.3.1': + dependencies: + '@bytecodealliance/preview2-shim': 0.17.6 + + '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.1': + optional: true + + '@nomicfoundation/solidity-analyzer@0.1.1': + optionalDependencies: + '@nomicfoundation/solidity-analyzer-darwin-arm64': 0.1.1 + '@nomicfoundation/solidity-analyzer-darwin-x64': 0.1.1 + '@nomicfoundation/solidity-analyzer-freebsd-x64': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-arm64-musl': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-x64-gnu': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-arm64-msvc': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 + + '@nuxt/cli@3.31.3(cac@6.7.14)(magicast@0.5.1)': + dependencies: + '@bomb.sh/tab': 0.0.10(cac@6.7.14)(citty@0.1.6) + '@clack/prompts': 1.0.0-alpha.8 + c12: 3.3.3(magicast@0.5.1) + citty: 0.1.6 + confbox: 0.2.2 + consola: 3.4.2 + copy-paste: 2.2.0 + debug: 4.4.3 + defu: 6.1.4 + exsolve: 1.0.8 + fuse.js: 7.1.0 + giget: 2.0.0 + jiti: 2.6.1 + listhen: 1.9.0 + nypm: 0.6.2 + ofetch: 1.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + scule: 1.3.0 + semver: 7.7.3 + srvx: 0.9.8 + std-env: 3.10.0 + tinyexec: 1.0.2 + ufo: 1.6.1 + youch: 4.1.0-beta.13 + transitivePeerDependencies: + - cac + - commander + - magicast + - supports-color + + '@nuxt/devalue@2.0.2': {} + + '@nuxt/devtools-kit@2.7.0(magicast@0.3.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))': + dependencies: + '@nuxt/kit': 3.20.2(magicast@0.3.5) + execa: 8.0.1 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + transitivePeerDependencies: + - magicast + + '@nuxt/devtools-wizard@2.7.0': + dependencies: + consola: 3.4.2 + diff: 8.0.2 + execa: 8.0.1 + magicast: 0.3.5 + pathe: 2.0.3 + pkg-types: 2.3.0 + prompts: 2.4.2 + semver: 7.7.3 + + '@nuxt/devtools@2.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@nuxt/devtools-kit': 2.7.0(magicast@0.3.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + '@nuxt/devtools-wizard': 2.7.0 + '@nuxt/kit': 3.20.2(magicast@0.3.5) + '@vue/devtools-core': 7.7.9(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@vue/devtools-kit': 7.7.9 + birpc: 2.9.0 + consola: 3.4.2 + destr: 2.0.5 + error-stack-parser-es: 1.0.5 + execa: 8.0.1 + fast-npm-meta: 0.4.7 + get-port-please: 3.2.0 + hookable: 5.5.3 + image-meta: 0.2.2 + is-installed-globally: 1.0.0 + launch-editor: 2.12.0 + local-pkg: 1.1.2 + magicast: 0.3.5 + nypm: 0.6.2 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + semver: 7.7.3 + simple-git: 3.30.0 + sirv: 3.0.2 + structured-clone-es: 1.0.0 + tinyglobby: 0.2.15 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vite-plugin-inspect: 11.3.3(@nuxt/kit@3.20.2(magicast@0.3.5))(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + vite-plugin-vue-tracer: 1.2.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + which: 5.0.0 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + - vue + + '@nuxt/kit@3.19.0(magicast@0.5.1)': + dependencies: + c12: 3.3.3(magicast@0.5.1) + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + errx: 0.1.0 + exsolve: 1.0.8 + ignore: 7.0.5 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + mlly: 1.8.0 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + rc9: 2.1.2 + scule: 1.3.0 + semver: 7.7.3 + std-env: 3.10.0 + tinyglobby: 0.2.15 + ufo: 1.6.1 + unctx: 2.5.0 + unimport: 5.6.0 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + + '@nuxt/kit@3.20.2(magicast@0.3.5)': + dependencies: + c12: 3.3.3(magicast@0.3.5) + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + errx: 0.1.0 + exsolve: 1.0.8 + ignore: 7.0.5 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + mlly: 1.8.0 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + rc9: 2.1.2 + scule: 1.3.0 + semver: 7.7.3 + tinyglobby: 0.2.15 + ufo: 1.6.1 + unctx: 2.5.0 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + + '@nuxt/kit@3.20.2(magicast@0.5.1)': + dependencies: + c12: 3.3.3(magicast@0.5.1) + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + errx: 0.1.0 + exsolve: 1.0.8 + ignore: 7.0.5 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + mlly: 1.8.0 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + rc9: 2.1.2 + scule: 1.3.0 + semver: 7.7.3 + tinyglobby: 0.2.15 + ufo: 1.6.1 + unctx: 2.5.0 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + + '@nuxt/schema@3.11.2(rollup@4.54.0)': + dependencies: + '@nuxt/ui-templates': 1.3.3 + consola: 3.2.3 + defu: 6.1.4 + hookable: 5.5.3 + pathe: 1.1.2 + pkg-types: 1.1.1 + scule: 1.3.0 + std-env: 3.7.0 + ufo: 1.5.3 + unimport: 3.7.1(rollup@4.54.0) + untyped: 1.4.2 + transitivePeerDependencies: + - rollup + - supports-color + + '@nuxt/schema@3.19.0': + dependencies: + '@vue/shared': 3.5.26 + consola: 3.4.2 + defu: 6.1.4 + pathe: 2.0.3 + std-env: 3.10.0 + ufo: 1.6.1 + + '@nuxt/telemetry@2.6.6(magicast@0.5.1)': + dependencies: + '@nuxt/kit': 3.20.2(magicast@0.5.1) + citty: 0.1.6 + consola: 3.4.2 + destr: 2.0.5 + dotenv: 16.6.1 + git-url-parse: 16.1.0 + is-docker: 3.0.0 + ofetch: 1.5.1 + package-manager-detector: 1.6.0 + pathe: 2.0.3 + rc9: 2.1.2 + std-env: 3.10.0 + transitivePeerDependencies: + - magicast + + '@nuxt/ui-templates@1.3.3': {} + + '@nuxt/vite-builder@3.19.0(@biomejs/biome@1.9.4)(@types/node@24.0.1)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)': + dependencies: + '@nuxt/kit': 3.19.0(magicast@0.5.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.54.0) + '@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': 5.1.3(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + autoprefixer: 10.4.23(postcss@8.5.6) + consola: 3.4.2 + cssnano: 7.1.2(postcss@8.5.6) + defu: 6.1.4 + esbuild: 0.25.12 + escape-string-regexp: 5.0.0 + exsolve: 1.0.8 + externality: 1.0.2 + get-port-please: 3.2.0 + h3: 1.15.4 + jiti: 2.6.1 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + mocked-exports: 0.1.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + postcss: 8.5.6 + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-beta.35)(rollup@4.54.0) + std-env: 3.10.0 + ufo: 1.6.1 + unenv: 2.0.0-rc.24 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vite-plugin-checker: 0.10.3(@biomejs/biome@1.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + vue: 3.5.26(typescript@5.9.3) + vue-bundle-renderer: 2.2.0 + transitivePeerDependencies: + - '@biomejs/biome' + - '@types/node' + - eslint + - less + - lightningcss + - magicast + - meow + - optionator + - rolldown + - rollup + - sass + - sass-embedded + - stylelint + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - vls + - vti + - vue-tsc + - yaml + + '@one-ini/wasm@0.1.1': {} + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@oven/bun-darwin-aarch64@1.1.30': + optional: true + + '@oven/bun-darwin-x64-baseline@1.1.30': + optional: true + + '@oven/bun-darwin-x64@1.1.30': + optional: true + + '@oven/bun-linux-aarch64@1.1.30': + optional: true + + '@oven/bun-linux-x64-baseline@1.1.30': + optional: true + + '@oven/bun-linux-x64@1.1.30': + optional: true + + '@oven/bun-windows-x64-baseline@1.1.30': + optional: true + + '@oven/bun-windows-x64@1.1.30': + optional: true + + '@oxc-minify/binding-android-arm64@0.86.0': + optional: true + + '@oxc-minify/binding-darwin-arm64@0.86.0': + optional: true + + '@oxc-minify/binding-darwin-x64@0.86.0': + optional: true + + '@oxc-minify/binding-freebsd-x64@0.86.0': + optional: true + + '@oxc-minify/binding-linux-arm-gnueabihf@0.86.0': + optional: true + + '@oxc-minify/binding-linux-arm-musleabihf@0.86.0': + optional: true + + '@oxc-minify/binding-linux-arm64-gnu@0.86.0': + optional: true + + '@oxc-minify/binding-linux-arm64-musl@0.86.0': + optional: true + + '@oxc-minify/binding-linux-riscv64-gnu@0.86.0': + optional: true + + '@oxc-minify/binding-linux-s390x-gnu@0.86.0': + optional: true + + '@oxc-minify/binding-linux-x64-gnu@0.86.0': + optional: true + + '@oxc-minify/binding-linux-x64-musl@0.86.0': + optional: true + + '@oxc-minify/binding-wasm32-wasi@0.86.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 + optional: true + + '@oxc-minify/binding-win32-arm64-msvc@0.86.0': + optional: true + + '@oxc-minify/binding-win32-x64-msvc@0.86.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.86.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.86.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.86.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.86.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.86.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.86.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.86.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.86.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.86.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.86.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.86.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.86.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.86.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.86.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.86.0': + optional: true + + '@oxc-project/runtime@0.82.3': {} + + '@oxc-project/types@0.82.3': {} + + '@oxc-project/types@0.86.0': {} + + '@oxc-transform/binding-android-arm64@0.86.0': + optional: true + + '@oxc-transform/binding-darwin-arm64@0.86.0': + optional: true + + '@oxc-transform/binding-darwin-x64@0.86.0': + optional: true + + '@oxc-transform/binding-freebsd-x64@0.86.0': + optional: true + + '@oxc-transform/binding-linux-arm-gnueabihf@0.86.0': + optional: true + + '@oxc-transform/binding-linux-arm-musleabihf@0.86.0': + optional: true + + '@oxc-transform/binding-linux-arm64-gnu@0.86.0': + optional: true + + '@oxc-transform/binding-linux-arm64-musl@0.86.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-gnu@0.86.0': + optional: true + + '@oxc-transform/binding-linux-s390x-gnu@0.86.0': + optional: true + + '@oxc-transform/binding-linux-x64-gnu@0.86.0': + optional: true + + '@oxc-transform/binding-linux-x64-musl@0.86.0': + optional: true + + '@oxc-transform/binding-wasm32-wasi@0.86.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 + optional: true + + '@oxc-transform/binding-win32-arm64-msvc@0.86.0': + optional: true + + '@oxc-transform/binding-win32-x64-msvc@0.86.0': + optional: true + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-wasm@2.4.1': + dependencies: + is-glob: 4.0.3 + micromatch: 4.0.8 + + '@parcel/watcher-wasm@2.5.1': + dependencies: + is-glob: 4.0.3 + micromatch: 4.0.8 + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + + '@paulmillr/qr@0.2.1': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.1.1 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@publint/pack@0.1.2': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-aspect-ratio@1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-context@1.1.3(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-id@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-progress@1.1.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-toast@1.2.15(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-visually-hidden@1.2.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.0 + + '@radix-ui/rect@1.1.1': {} + + '@react-oauth/google@0.11.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@reown/appkit-common@1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.22.4)': + dependencies: + big.js: 6.2.2 + dayjs: 1.11.13 + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.22.4) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@reown/appkit-common@1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + big.js: 6.2.2 + dayjs: 1.11.13 + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + + '@reown/appkit-controllers@1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-wallet': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.19.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + valtio: 1.13.2(@types/react@18.3.27)(react@18.3.1) + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@reown/appkit-polyfills@1.7.3': + dependencies: + buffer: 6.0.3 + + '@reown/appkit-scaffold-ui@1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-controllers': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-ui': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-utils': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-wallet': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + lit: 3.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@reown/appkit-ui@1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-controllers': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-wallet': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + lit: 3.1.0 + qrcode: 1.5.3 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@reown/appkit-utils@1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-controllers': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-polyfills': 1.7.3 + '@reown/appkit-wallet': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@walletconnect/logger': 2.1.2 + '@walletconnect/universal-provider': 2.19.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + valtio: 1.13.2(@types/react@18.3.27)(react@18.3.1) + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@reown/appkit-wallet@1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.22.4) + '@reown/appkit-polyfills': 1.7.3 + '@walletconnect/logger': 2.1.2 + zod: 3.22.4 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + + '@reown/appkit@1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit-common': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-controllers': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-polyfills': 1.7.3 + '@reown/appkit-scaffold-ui': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-ui': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-utils': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@reown/appkit-wallet': 1.7.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@walletconnect/types': 2.19.2(ioredis@5.8.2) + '@walletconnect/universal-provider': 2.19.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + bs58: 6.0.0 + valtio: 1.13.2(@types/react@18.3.27)(react@18.3.1) + viem: 2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@rolldown/binding-android-arm64@1.0.0-beta.35': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.35': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.35': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.35': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.35': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.35': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.35': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.35': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.35': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.35': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.35': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.35': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.35': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.35': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.35': {} + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@rolldown/pluginutils@1.0.0-beta.57': {} + + '@rollup/plugin-alias@5.1.1(rollup@4.54.0)': + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-commonjs@28.0.9(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.3) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-inject@5.0.5(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + estree-walker: 2.0.2 + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-json@6.1.0(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-node-resolve@16.0.3(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-replace@6.0.3(rollup@4.54.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/plugin-terser@0.4.4(rollup@4.54.0)': + dependencies: + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.44.1 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/pluginutils@5.1.0(rollup@4.54.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/pluginutils@5.3.0(rollup@4.54.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + + '@safe-global/safe-apps-provider@0.18.6(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + events: 3.3.0 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + + '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@safe-global/safe-gateway-typescript-sdk': 3.8.0(encoding@0.1.13) + viem: 2.17.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + + '@safe-global/safe-gateway-typescript-sdk@3.8.0(encoding@0.1.13)': + dependencies: + cross-fetch: 3.2.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + '@scure/base@1.1.3': {} + + '@scure/base@1.1.9': {} + + '@scure/base@1.2.6': {} + + '@scure/bip32@1.1.5': + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.1.9 + + '@scure/bip32@1.3.2': + dependencies: + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.0 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip32@1.6.2': + dependencies: + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@scure/base': 1.2.6 + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.1.1': + dependencies: + '@noble/hashes': 1.2.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.2.1': + dependencies: + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.5.4': + dependencies: + '@noble/hashes': 1.7.1 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@sec-ant/readable-stream@0.4.1': {} + + '@sentry/core@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/hub@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/minimal@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/types': 5.30.0 + tslib: 1.14.1 + + '@sentry/node@5.30.0': + dependencies: + '@sentry/core': 5.30.0 + '@sentry/hub': 5.30.0 + '@sentry/tracing': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + cookie: 0.4.2 + https-proxy-agent: 5.0.1 + lru_map: 0.3.3 + tslib: 1.14.1 + transitivePeerDependencies: + - supports-color + + '@sentry/tracing@5.30.0': + dependencies: + '@sentry/hub': 5.30.0 + '@sentry/minimal': 5.30.0 + '@sentry/types': 5.30.0 + '@sentry/utils': 5.30.0 + tslib: 1.14.1 + + '@sentry/types@5.30.0': {} + + '@sentry/utils@5.30.0': + dependencies: + '@sentry/types': 5.30.0 + tslib: 1.14.1 + + '@shikijs/core@1.22.2': + dependencies: + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.3 + + '@shikijs/engine-javascript@1.22.2': + dependencies: + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + oniguruma-to-js: 0.4.3 + + '@shikijs/engine-oniguruma@1.22.2': + dependencies: + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + + '@shikijs/transformers@1.22.2': + dependencies: + shiki: 1.22.2 + + '@shikijs/twoslash@1.22.2(typescript@5.9.3)': + dependencies: + '@shikijs/core': 1.22.2 + '@shikijs/types': 1.22.2 + twoslash: 0.2.12(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + - typescript + + '@shikijs/types@1.22.2': + dependencies: + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + + '@shikijs/vitepress-twoslash@1.22.2(@nuxt/kit@3.20.2(magicast@0.5.1))(typescript@5.9.3)': + dependencies: + '@shikijs/twoslash': 1.22.2(typescript@5.9.3) + floating-vue: 5.2.2(@nuxt/kit@3.20.2(magicast@0.5.1))(vue@3.5.12(typescript@5.9.3)) + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm: 3.0.0 + mdast-util-to-hast: 13.2.0 + shiki: 1.22.2 + twoslash: 0.2.12(typescript@5.9.3) + twoslash-vue: 0.2.12(typescript@5.9.3) + vue: 3.5.12(typescript@5.9.3) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - typescript + + '@shikijs/vscode-textmate@9.3.0': {} + + '@sinclair/typebox@0.25.24': {} + + '@sindresorhus/is@4.6.0': {} + + '@sindresorhus/is@7.1.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@smithy/abort-controller@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.5': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.7 + '@smithy/util-middleware': 4.2.7 + tslib: 2.8.1 + + '@smithy/core@3.20.0': + dependencies: + '@smithy/middleware-serde': 4.2.8 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-stream': 4.5.8 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.7': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.8': + dependencies: + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.7': + dependencies: + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.1': + dependencies: + '@smithy/core': 3.20.0 + '@smithy/middleware-serde': 4.2.8 + '@smithy/node-config-provider': 4.3.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + '@smithy/url-parser': 4.2.7 + '@smithy/util-middleware': 4.2.7 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.17': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/service-error-classification': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-retry': 4.2.7 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.8': + dependencies: + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.7': + dependencies: + '@smithy/property-provider': 4.2.7 + '@smithy/shared-ini-file-loader': 4.4.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.7': + dependencies: + '@smithy/abort-controller': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/querystring-builder': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + + '@smithy/shared-ini-file-loader@4.4.2': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.7': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.7 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/smithy-client@4.10.2': + dependencies: + '@smithy/core': 3.20.0 + '@smithy/middleware-endpoint': 4.4.1 + '@smithy/middleware-stack': 4.2.7 + '@smithy/protocol-http': 5.3.7 + '@smithy/types': 4.11.0 + '@smithy/util-stream': 4.5.8 + tslib: 2.8.1 + + '@smithy/types@4.11.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.7': + dependencies: + '@smithy/querystring-parser': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.16': + dependencies: + '@smithy/property-provider': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.19': + dependencies: + '@smithy/config-resolver': 4.4.5 + '@smithy/credential-provider-imds': 4.2.7 + '@smithy/node-config-provider': 4.3.7 + '@smithy/property-provider': 4.2.7 + '@smithy/smithy-client': 4.10.2 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.2.7': + dependencies: + '@smithy/node-config-provider': 4.3.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.7': + dependencies: + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.7': + dependencies: + '@smithy/service-error-classification': 4.2.7 + '@smithy/types': 4.11.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.8': + dependencies: + '@smithy/fetch-http-handler': 5.3.8 + '@smithy/node-http-handler': 4.4.7 + '@smithy/types': 4.11.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.8.1 + + '@snyk/github-codeowners@1.1.0': + dependencies: + commander: 4.1.1 + ignore: 5.3.2 + p-map: 4.0.0 + + '@socket.io/component-emitter@3.1.2': {} + + '@solidity-parser/parser@0.20.2': {} + + '@speed-highlight/core@1.2.12': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/cli@4.1.18': + dependencies: + '@parcel/watcher': 2.5.1 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + enhanced-resolve: 5.18.4 + mri: 1.2.0 + picocolors: 1.1.1 + tailwindcss: 4.1.18 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tanstack/match-sorter-utils@8.15.1': + dependencies: + remove-accents: 0.5.0 + + '@tanstack/query-core@5.0.5': {} + + '@tanstack/query-core@5.49.1': {} + + '@tanstack/query-core@5.90.15': {} + + '@tanstack/query-devtools@5.0.5': {} + + '@tanstack/query-persist-client-core@5.0.5': + dependencies: + '@tanstack/query-core': 5.0.5 + + '@tanstack/query-sync-storage-persister@5.0.5': + dependencies: + '@tanstack/query-core': 5.0.5 + '@tanstack/query-persist-client-core': 5.0.5 + + '@tanstack/react-query-devtools@5.0.5(@tanstack/react-query@5.49.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-devtools': 5.0.5 + '@tanstack/react-query': 5.49.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-query-persist-client@5.0.5(@tanstack/react-query@5.49.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/query-persist-client-core': 5.0.5 + '@tanstack/react-query': 5.49.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/react-query@5.49.2(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.49.1 + react: 18.3.1 + + '@tanstack/react-query@5.90.15(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.15 + react: 18.3.1 + + '@tanstack/vue-query@5.49.1(vue@3.4.27(typescript@5.9.3))': + dependencies: + '@tanstack/match-sorter-utils': 8.15.1 + '@tanstack/query-core': 5.49.1 + '@vue/devtools-api': 6.6.1 + vue: 3.4.27(typescript@5.9.3) + vue-demi: 0.14.10(vue@3.4.27(typescript@5.9.3)) + + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/runtime': 7.26.0 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@testing-library/dom': 10.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.1 + '@types/react-dom': 18.3.0 + + '@tootallnate/once@2.0.0': {} + + '@ts-morph/common@0.11.1': + dependencies: + fast-glob: 3.3.3 + minimatch: 3.1.2 + mkdirp: 1.0.4 + path-browserify: 1.0.1 + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@typechain/ethers-v6@0.5.1(ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5))(typescript@5.8.3)': + dependencies: + ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + lodash: 4.17.21 + ts-essentials: 7.0.3(typescript@5.8.3) + typescript: 5.8.3 + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 + '@types/babel__generator': 7.6.5 + '@types/babel__template': 7.4.2 + '@types/babel__traverse': 7.20.2 + + '@types/babel__generator@7.6.5': + dependencies: + '@babel/types': 7.27.1 + + '@types/babel__template@7.4.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.27.1 + + '@types/babel__traverse@7.20.2': + dependencies: + '@babel/types': 7.27.1 + + '@types/bn.js@4.11.6': + dependencies: + '@types/node': 22.15.31 + + '@types/bn.js@5.1.5': + dependencies: + '@types/node': 24.0.1 + + '@types/bun@1.1.10': + dependencies: + bun-types: 1.1.29 + + '@types/cookie@0.6.0': {} + + '@types/cross-spawn@6.0.6': + dependencies: + '@types/node': 20.12.10 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/debug@4.1.7': + dependencies: + '@types/ms': 0.7.31 + + '@types/dedent@0.7.2': {} + + '@types/estree@1.0.5': {} + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.2 + + '@types/json-schema@7.0.15': {} + + '@types/linkify-it@5.0.0': {} + + '@types/lru-cache@5.1.1': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.3': + dependencies: + '@types/unist': 3.0.2 + + '@types/mdurl@2.0.0': {} + + '@types/ms@0.7.31': {} + + '@types/ms@2.1.0': {} + + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 22.15.31 + + '@types/node@12.20.55': {} + + '@types/node@16.18.11': {} + + '@types/node@20.12.10': + dependencies: + undici-types: 5.26.5 + + '@types/node@20.12.14': + dependencies: + undici-types: 5.26.5 + + '@types/node@20.19.27': + dependencies: + undici-types: 6.21.0 + + '@types/node@22.15.31': + dependencies: + undici-types: 6.21.0 + + '@types/node@22.7.5': + dependencies: + undici-types: 6.19.8 + + '@types/node@24.0.1': + dependencies: + undici-types: 7.8.0 + + '@types/parse-path@7.1.0': + dependencies: + parse-path: 7.1.0 + + '@types/pbkdf2@3.1.2': + dependencies: + '@types/node': 22.15.31 + + '@types/prompts@2.4.9': + dependencies: + '@types/node': 20.12.10 + kleur: 3.0.3 + + '@types/prop-types@15.7.15': {} + + '@types/prop-types@15.7.5': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.1 + + '@types/react@18.3.1': + dependencies: + '@types/prop-types': 15.7.5 + csstype: 3.1.3 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/resolve@1.20.2': {} + + '@types/secp256k1@4.0.3': + dependencies: + '@types/node': 22.15.31 + + '@types/semver@7.5.3': {} + + '@types/statuses@2.0.4': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/trusted-types@2.0.3': {} + + '@types/unist@3.0.2': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/web-bluetooth@0.0.20': {} + + '@types/whatwg-mimetype@3.0.2': {} + + '@types/wrap-ansi@3.0.0': {} + + '@types/ws@8.5.10': + dependencies: + '@types/node': 22.15.31 + + '@typescript/vfs@1.6.0(typescript@5.9.3)': + dependencies: + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@ungap/structured-clone@1.2.0': {} + + '@unhead/vue@2.1.1(vue@3.5.26(typescript@5.9.3))': + dependencies: + hookable: 5.5.3 + unhead: 2.1.1 + vue: 3.5.26(typescript@5.9.3) + + '@unocss/astro@0.59.4(rollup@4.54.0)': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/reset': 0.59.4 + '@unocss/vite': 0.59.4(rollup@4.54.0) + transitivePeerDependencies: + - rollup + + '@unocss/cli@0.59.4(rollup@4.54.0)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@rollup/pluginutils': 5.1.0(rollup@4.54.0) + '@unocss/config': 0.59.4 + '@unocss/core': 0.59.4 + '@unocss/preset-uno': 0.59.4 + cac: 6.7.14 + chokidar: 3.6.0 + colorette: 2.0.20 + consola: 3.4.2 + fast-glob: 3.3.2 + magic-string: 0.30.21 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + transitivePeerDependencies: + - rollup + + '@unocss/config@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + unconfig: 0.3.13 + + '@unocss/core@0.59.4': {} + + '@unocss/extractor-arbitrary-variants@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/inspector@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/rule-utils': 0.59.4 + gzip-size: 6.0.0 + sirv: 2.0.4 + + '@unocss/postcss@0.59.4': + dependencies: + '@unocss/config': 0.59.4 + '@unocss/core': 0.59.4 + '@unocss/rule-utils': 0.59.4 + css-tree: 2.3.1 + fast-glob: 3.3.2 + magic-string: 0.30.21 + postcss: 8.5.6 + + '@unocss/preset-attributify@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/preset-icons@0.59.4': + dependencies: + '@iconify/utils': 2.1.23 + '@unocss/core': 0.59.4 + ofetch: 1.5.1 + transitivePeerDependencies: + - supports-color + + '@unocss/preset-mini@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/extractor-arbitrary-variants': 0.59.4 + '@unocss/rule-utils': 0.59.4 + + '@unocss/preset-tagify@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/preset-typography@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/preset-mini': 0.59.4 + + '@unocss/preset-uno@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/preset-mini': 0.59.4 + '@unocss/preset-wind': 0.59.4 + '@unocss/rule-utils': 0.59.4 + + '@unocss/preset-web-fonts@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + ofetch: 1.5.1 + + '@unocss/preset-wind@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/preset-mini': 0.59.4 + '@unocss/rule-utils': 0.59.4 + + '@unocss/reset@0.59.4': {} + + '@unocss/rule-utils@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + magic-string: 0.30.21 + + '@unocss/scope@0.59.4': {} + + '@unocss/transformer-attributify-jsx-babel@0.59.4': + dependencies: + '@babel/core': 7.24.5 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.5) + '@babel/preset-typescript': 7.24.1(@babel/core@7.24.5) + '@unocss/core': 0.59.4 + transitivePeerDependencies: + - supports-color + + '@unocss/transformer-attributify-jsx@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/transformer-compile-class@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/transformer-directives@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + '@unocss/rule-utils': 0.59.4 + css-tree: 2.3.1 + + '@unocss/transformer-variant-group@0.59.4': + dependencies: + '@unocss/core': 0.59.4 + + '@unocss/vite@0.59.4(rollup@4.54.0)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@rollup/pluginutils': 5.1.0(rollup@4.54.0) + '@unocss/config': 0.59.4 + '@unocss/core': 0.59.4 + '@unocss/inspector': 0.59.4 + '@unocss/scope': 0.59.4 + '@unocss/transformer-directives': 0.59.4 + chokidar: 3.6.0 + fast-glob: 3.3.2 + magic-string: 0.30.21 + transitivePeerDependencies: + - rollup + + '@vercel/analytics@1.6.1(next@15.4.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(vue-router@4.6.4(vue@3.5.26(typescript@5.8.3)))(vue@3.5.26(typescript@5.8.3))': + optionalDependencies: + next: 15.4.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + vue: 3.5.26(typescript@5.8.3) + vue-router: 4.6.4(vue@3.5.26(typescript@5.8.3)) + + '@vercel/backends@0.0.14(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3)': + dependencies: + '@vercel/cervel': 0.0.6(typescript@5.8.3) + '@vercel/introspection': 0.0.5 + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + fs-extra: 11.1.0 + rolldown: 1.0.0-beta.35 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + - typescript + + '@vercel/blob@1.0.2': + dependencies: + async-retry: 1.3.3 + is-buffer: 2.0.5 + is-node-process: 1.2.0 + throttleit: 2.1.0 + undici: 5.29.0 + + '@vercel/build-utils@13.2.2': {} + + '@vercel/cervel@0.0.6(typescript@5.8.3)': + dependencies: + execa: 3.2.0 + rolldown: 1.0.0-beta.35 + srvx: 0.8.9 + tsx: 4.19.2 + typescript: 5.8.3 + + '@vercel/detect-agent@1.0.0': {} + + '@vercel/elysia@0.1.12(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/error-utils@2.0.3': {} + + '@vercel/express@0.1.17(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3)': + dependencies: + '@vercel/cervel': 0.0.6(typescript@5.8.3) + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + fs-extra: 11.1.0 + path-to-regexp: 8.3.0 + rolldown: 1.0.0-beta.35 + ts-morph: 12.0.0 + zod: 3.22.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + - typescript + + '@vercel/fastify@0.1.15(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/fun@1.2.0(encoding@0.1.13)': + dependencies: + '@tootallnate/once': 2.0.0 + async-listen: 1.2.0 + debug: 4.3.4(supports-color@8.1.1) + generic-pool: 3.4.2 + micro: 9.3.5-canary.3 + ms: 2.1.1 + node-fetch: 2.6.7(encoding@0.1.13) + path-match: 1.2.4 + promisepipe: 3.0.0 + semver: 7.5.4 + stat-mode: 0.3.0 + stream-to-promise: 2.2.0 + tar: 6.2.1 + tinyexec: 0.3.2 + tree-kill: 1.2.2 + uid-promise: 1.0.0 + xdg-app-paths: 5.1.0 + yauzl-promise: 2.1.3 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + dependencies: + web-vitals: 0.2.4 + + '@vercel/gatsby-plugin-vercel-builder@2.0.112': + dependencies: + '@sinclair/typebox': 0.25.24 + '@vercel/build-utils': 13.2.2 + esbuild: 0.14.47 + etag: 1.8.1 + fs-extra: 11.1.0 + + '@vercel/go@3.2.3': {} + + '@vercel/h3@0.1.21(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/hono@0.2.15(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + fs-extra: 11.1.0 + path-to-regexp: 8.3.0 + rolldown: 1.0.0-beta.35 + ts-morph: 12.0.0 + zod: 3.22.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/hydrogen@1.3.2': + dependencies: + '@vercel/static-config': 3.1.2 + ts-morph: 12.0.0 + + '@vercel/introspection@0.0.5': + dependencies: + path-to-regexp: 8.3.0 + zod: 3.22.4 + + '@vercel/nestjs@0.2.16(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/next@4.15.7(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/nft@0.30.4(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.3 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/nft@1.1.1(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 13.0.0 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.3 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/node@5.5.14(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@edge-runtime/node-utils': 2.3.0 + '@edge-runtime/primitives': 4.1.0 + '@edge-runtime/vm': 3.2.0 + '@types/node': 16.18.11 + '@vercel/build-utils': 13.2.2 + '@vercel/error-utils': 2.0.3 + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + async-listen: 3.0.0 + cjs-module-lexer: 1.2.3 + edge-runtime: 2.5.9 + es-module-lexer: 1.4.1 + esbuild: 0.14.47 + etag: 1.8.1 + mime-types: 2.1.35 + node-fetch: 2.6.9(encoding@0.1.13) + path-to-regexp: 6.1.0 + path-to-regexp-updated: path-to-regexp@6.3.0 + ts-morph: 12.0.0 + ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5) + typescript: 4.9.5 + typescript5: typescript@5.9.3 + undici: 5.28.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + + '@vercel/python@6.1.0': {} + + '@vercel/redwood@2.4.5(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + semver: 6.3.1 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/remix-builder@5.5.5(encoding@0.1.13)(rollup@4.54.0)': + dependencies: + '@vercel/error-utils': 2.0.3 + '@vercel/nft': 1.1.1(encoding@0.1.13)(rollup@4.54.0) + '@vercel/static-config': 3.1.2 + path-to-regexp: 6.1.0 + path-to-regexp-updated: path-to-regexp@6.3.0 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/ruby@2.2.2': {} + + '@vercel/rust@1.0.3': + dependencies: + '@iarna/toml': 2.2.5 + execa: 5.1.1 + + '@vercel/static-build@2.8.13': + dependencies: + '@vercel/gatsby-plugin-vercel-analytics': 1.0.11 + '@vercel/gatsby-plugin-vercel-builder': 2.0.112 + '@vercel/static-config': 3.1.2 + ts-morph: 12.0.0 + + '@vercel/static-config@3.1.2': + dependencies: + ajv: 8.6.3 + json-schema-to-ts: 1.6.4 + ts-morph: 12.0.0 + + '@vitejs/plugin-react@4.2.1(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.24.5 + '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.0 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue-jsx@5.1.3(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.57 + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5) + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vue: 3.5.26(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.0.4(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.4.27(typescript@5.9.3))': + dependencies: + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vue: 3.4.27(typescript@5.9.3) + + '@vitejs/plugin-vue@5.1.4(vite@5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.12(typescript@5.9.3))': + dependencies: + vite: 5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + vue: 3.5.12(typescript@5.9.3) + + '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.53 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vue: 3.5.26(typescript@5.9.3) + + '@vitest/coverage-v8@3.2.4(vitest@2.1.9(@edge-runtime/vm@3.2.0)(@types/node@24.0.1)(happy-dom@20.0.2)(lightningcss@1.30.2)(msw@2.4.9(typescript@5.8.3))(terser@5.44.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.9 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 2.1.9(@edge-runtime/vm@3.2.0)(@types/node@24.0.1)(happy-dom@20.0.2)(lightningcss@1.30.2)(msw@2.4.9(typescript@5.8.3))(terser@5.44.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(msw@2.4.9(typescript@5.8.3))(vite@5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.4.9(typescript@5.8.3) + vite: 5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.1.3 + tinyrainbow: 1.2.0 + + '@volar/language-core@2.2.1': + dependencies: + '@volar/source-map': 2.2.1 + + '@volar/language-core@2.4.27': + dependencies: + '@volar/source-map': 2.4.27 + + '@volar/language-core@2.4.8': + dependencies: + '@volar/source-map': 2.4.8 + + '@volar/source-map@2.2.1': + dependencies: + muggle-string: 0.4.1 + + '@volar/source-map@2.4.27': {} + + '@volar/source-map@2.4.8': {} + + '@volar/typescript@2.2.1': + dependencies: + '@volar/language-core': 2.2.1 + path-browserify: 1.0.1 + + '@vue-macros/common@3.0.0-beta.16(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/compiler-sfc': 3.5.26 + ast-kit: 2.2.0 + local-pkg: 1.1.2 + magic-string-ast: 1.0.3 + unplugin-utils: 0.2.5 + optionalDependencies: + vue: 3.5.26(typescript@5.9.3) + + '@vue/babel-helper-vue-transform-on@2.0.1': {} + + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.5)': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@vue/babel-helper-vue-transform-on': 2.0.1 + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.28.5) + '@vue/shared': 3.5.26 + optionalDependencies: + '@babel/core': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.28.5)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/parser': 7.28.5 + '@vue/compiler-sfc': 3.5.26 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.4.27': + dependencies: + '@babel/parser': 7.26.2 + '@vue/shared': 3.4.27 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-core@3.5.12': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.12 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.4.27': + dependencies: + '@vue/compiler-core': 3.4.27 + '@vue/shared': 3.4.27 + + '@vue/compiler-dom@3.5.12': + dependencies: + '@vue/compiler-core': 3.5.12 + '@vue/shared': 3.5.12 + + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-sfc@3.4.27': + dependencies: + '@babel/parser': 7.26.2 + '@vue/compiler-core': 3.4.27 + '@vue/compiler-dom': 3.4.27 + '@vue/compiler-ssr': 3.4.27 + '@vue/shared': 3.4.27 + estree-walker: 2.0.2 + magic-string: 0.30.12 + postcss: 8.4.47 + source-map-js: 1.2.1 + + '@vue/compiler-sfc@3.5.12': + dependencies: + '@babel/parser': 7.26.2 + '@vue/compiler-core': 3.5.12 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.4.27': + dependencies: + '@vue/compiler-dom': 3.4.27 + '@vue/shared': 3.4.27 + + '@vue/compiler-ssr@3.5.12': + dependencies: + '@vue/compiler-dom': 3.5.12 + '@vue/shared': 3.5.12 + + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.1': {} + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.6.2': + dependencies: + '@vue/devtools-kit': 7.6.2 + + '@vue/devtools-core@7.7.9(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/devtools-kit': 7.7.9 + '@vue/devtools-shared': 7.7.9 + mitt: 3.0.1 + nanoid: 5.1.6 + pathe: 2.0.3 + vite-hot-client: 2.1.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + vue: 3.5.26(typescript@5.9.3) + transitivePeerDependencies: + - vite + + '@vue/devtools-kit@7.6.2': + dependencies: + '@vue/devtools-shared': 7.6.2 + birpc: 0.2.19 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.1 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.6.2': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@2.0.16(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.2.1 + '@vue/compiler-dom': 3.5.12 + '@vue/shared': 3.5.26 + computeds: 0.0.1 + minimatch: 9.0.4 + path-browserify: 1.0.1 + vue-template-compiler: 2.7.16 + optionalDependencies: + typescript: 5.9.3 + + '@vue/language-core@2.1.10(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.8 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.26 + alien-signals: 0.2.0 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/language-core@3.2.1': + dependencies: + '@volar/language-core': 2.4.27 + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + alien-signals: 3.1.2 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + + '@vue/reactivity@3.4.27': + dependencies: + '@vue/shared': 3.4.27 + + '@vue/reactivity@3.5.12': + dependencies: + '@vue/shared': 3.5.12 + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.4.27': + dependencies: + '@vue/reactivity': 3.4.27 + '@vue/shared': 3.4.27 + + '@vue/runtime-core@3.5.12': + dependencies: + '@vue/reactivity': 3.5.12 + '@vue/shared': 3.5.12 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.4.27': + dependencies: + '@vue/runtime-core': 3.4.27 + '@vue/shared': 3.4.27 + csstype: 3.1.3 + + '@vue/runtime-dom@3.5.12': + dependencies: + '@vue/reactivity': 3.5.12 + '@vue/runtime-core': 3.5.12 + '@vue/shared': 3.5.12 + csstype: 3.2.3 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.4.27(vue@3.4.27(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.4.27 + '@vue/shared': 3.4.27 + vue: 3.4.27(typescript@5.9.3) + + '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 + vue: 3.5.12(typescript@5.9.3) + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.8.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.8.3) + optional: true + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.9.3) + + '@vue/shared@3.4.27': {} + + '@vue/shared@3.5.12': {} + + '@vue/shared@3.5.26': {} + + '@vue/test-utils@2.4.6': + dependencies: + js-beautify: 1.15.1 + vue-component-type-helpers: 2.0.16 + + '@vueuse/core@11.2.0(vue@3.5.12(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.2.0 + '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.9.3)) + vue-demi: 0.14.10(vue@3.5.12(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/integrations@11.2.0(change-case@5.4.4)(focus-trap@7.6.0)(fuse.js@7.1.0)(idb-keyval@6.2.1)(jwt-decode@4.0.0)(qrcode@1.5.3)(vue@3.5.12(typescript@5.9.3))': + dependencies: + '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.9.3)) + '@vueuse/shared': 11.2.0(vue@3.5.12(typescript@5.9.3)) + vue-demi: 0.14.10(vue@3.5.12(typescript@5.9.3)) + optionalDependencies: + change-case: 5.4.4 + focus-trap: 7.6.0 + fuse.js: 7.1.0 + idb-keyval: 6.2.1 + jwt-decode: 4.0.0 + qrcode: 1.5.3 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@11.2.0': {} + + '@vueuse/shared@11.2.0(vue@3.5.12(typescript@5.9.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.12(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@walletconnect/core@2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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.1.0)(utf-8-validate@5.0.10) + '@walletconnect/keyvaluestorage': 1.1.1(ioredis@5.8.2) + '@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.2(ioredis@5.8.2) + '@walletconnect/utils': 2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/window-getters': 1.0.1 + es-toolkit: 1.33.0 + events: 3.3.0 + uint8arrays: 3.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/core@2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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.1.0)(utf-8-validate@5.0.10) + '@walletconnect/keyvaluestorage': 1.1.1(ioredis@5.8.2) + '@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.20.2(ioredis@5.8.2) + '@walletconnect/utils': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/window-getters': 1.0.1 + es-toolkit: 1.33.0 + events: 3.3.0 + uint8arrays: 3.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/environment@1.0.1': + dependencies: + tslib: 1.14.1 + + '@walletconnect/ethereum-provider@2.20.2(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@reown/appkit': 1.7.3(@types/react@18.3.27)(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(react@18.3.1)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@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(ioredis@5.8.2) + '@walletconnect/sign-client': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/types': 2.20.2(ioredis@5.8.2) + '@walletconnect/universal-provider': 2.20.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/utils': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - react + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/events@1.0.1': + dependencies: + keyvaluestorage-interface: 1.0.0 + tslib: 1.14.1 + + '@walletconnect/heartbeat@1.2.2': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/time': 1.0.2 + events: 3.3.0 + + '@walletconnect/jsonrpc-http-connection@1.0.8(encoding@0.1.13)': + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + cross-fetch: 3.2.0(encoding@0.1.13) + events: 3.3.0 + transitivePeerDependencies: + - encoding + + '@walletconnect/jsonrpc-provider@1.0.14': + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + events: 3.3.0 + + '@walletconnect/jsonrpc-types@1.0.4': + dependencies: + events: 3.3.0 + keyvaluestorage-interface: 1.0.0 + + '@walletconnect/jsonrpc-utils@1.0.8': + dependencies: + '@walletconnect/environment': 1.0.1 + '@walletconnect/jsonrpc-types': 1.0.4 + tslib: 1.14.1 + + '@walletconnect/jsonrpc-ws-connection@1.0.16(bufferutil@4.1.0)(utf-8-validate@5.0.10)': + dependencies: + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/safe-json': 1.0.2 + events: 3.3.0 + ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@walletconnect/keyvaluestorage@1.1.1(ioredis@5.8.2)': + dependencies: + '@walletconnect/safe-json': 1.0.2 + idb-keyval: 6.2.1 + unstorage: 1.10.2(idb-keyval@6.2.1)(ioredis@5.8.2) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/kv' + - ioredis + - uWebSockets.js + + '@walletconnect/logger@2.1.2': + dependencies: + '@walletconnect/safe-json': 1.0.2 + pino: 7.11.0 + + '@walletconnect/relay-api@1.0.11': + dependencies: + '@walletconnect/jsonrpc-types': 1.0.4 + + '@walletconnect/relay-auth@1.1.0': + dependencies: + '@noble/curves': 1.8.0 + '@noble/hashes': 1.7.0 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + uint8arrays: 3.1.1 + + '@walletconnect/safe-json@1.0.2': + dependencies: + tslib: 1.14.1 + + '@walletconnect/sign-client@2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@walletconnect/core': 2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@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.2(ioredis@5.8.2) + '@walletconnect/utils': 2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/sign-client@2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + dependencies: + '@walletconnect/core': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@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.20.2(ioredis@5.8.2) + '@walletconnect/utils': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/time@1.0.2': + dependencies: + tslib: 1.14.1 + + '@walletconnect/types@2.19.2(ioredis@5.8.2)': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/keyvaluestorage': 1.1.1(ioredis@5.8.2) + '@walletconnect/logger': 2.1.2 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - ioredis + - uWebSockets.js + + '@walletconnect/types@2.20.2(ioredis@5.8.2)': + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.2 + '@walletconnect/jsonrpc-types': 1.0.4 + '@walletconnect/keyvaluestorage': 1.1.1(ioredis@5.8.2) + '@walletconnect/logger': 2.1.2 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - ioredis + - uWebSockets.js + + '@walletconnect/universal-provider@2.19.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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(ioredis@5.8.2) + '@walletconnect/logger': 2.1.2 + '@walletconnect/sign-client': 2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/types': 2.19.2(ioredis@5.8.2) + '@walletconnect/utils': 2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + es-toolkit: 1.33.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/universal-provider@2.20.2(bufferutil@4.1.0)(encoding@0.1.13)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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(ioredis@5.8.2) + '@walletconnect/logger': 2.1.2 + '@walletconnect/sign-client': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + '@walletconnect/types': 2.20.2(ioredis@5.8.2) + '@walletconnect/utils': 2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + es-toolkit: 1.33.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/utils@2.19.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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(ioredis@5.8.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.2(ioredis@5.8.2) + '@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.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/utils@2.20.2(bufferutil@4.1.0)(ioredis@5.8.2)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20)': + 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(ioredis@5.8.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.20.2(ioredis@5.8.2) + '@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.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@netlify/blobs' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - ioredis + - typescript + - uWebSockets.js + - utf-8-validate + - zod + + '@walletconnect/window-getters@1.0.1': + dependencies: + tslib: 1.14.1 + + '@walletconnect/window-metadata@1.0.1': + dependencies: + '@walletconnect/window-getters': 1.0.1 + tslib: 1.14.1 + + abbrev@2.0.0: {} + + abbrev@3.0.1: {} + + abitype@1.0.0(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abitype@1.0.2(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abitype@1.0.4(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abitype@1.0.5(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abitype@1.0.8(typescript@5.8.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.8.3 + zod: 3.25.20 + + abitype@1.0.8(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abitype@1.2.3(typescript@5.8.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.8.3 + zod: 3.25.20 + + abitype@1.2.3(typescript@5.9.3)(zod@3.22.4): + optionalDependencies: + typescript: 5.9.3 + zod: 3.22.4 + + abitype@1.2.3(typescript@5.9.3)(zod@3.25.20): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.20 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.11.3: {} + + acorn@8.15.0: {} + + adm-zip@0.4.16: {} + + 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: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@8.6.3: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + algoliasearch@5.12.0: + dependencies: + '@algolia/client-abtesting': 5.12.0 + '@algolia/client-analytics': 5.12.0 + '@algolia/client-common': 5.12.0 + '@algolia/client-insights': 5.12.0 + '@algolia/client-personalization': 5.12.0 + '@algolia/client-query-suggestions': 5.12.0 + '@algolia/client-search': 5.12.0 + '@algolia/ingestion': 1.12.0 + '@algolia/monitoring': 1.12.0 + '@algolia/recommend': 5.12.0 + '@algolia/requester-browser-xhr': 5.12.0 + '@algolia/requester-fetch': 5.12.0 + '@algolia/requester-node-http': 5.12.0 + + alien-signals@0.2.0: {} + + alien-signals@3.1.2: {} + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-colors@4.1.1: {} + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + ansis@4.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + arg@4.1.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-union@1.0.2: + dependencies: + array-uniq: 1.0.3 + + array-union@2.1.0: {} + + array-uniq@1.0.3: {} + + assertion-error@2.0.1: {} + + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.28.5 + pathe: 2.0.3 + + ast-v8-to-istanbul@0.3.9: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + + ast-walker-scope@0.8.3: + dependencies: + '@babel/parser': 7.28.5 + ast-kit: 2.2.0 + + async-listen@1.2.0: {} + + async-listen@3.0.0: {} + + async-listen@3.0.1: {} + + async-mutex@0.2.6: + dependencies: + tslib: 2.8.1 + + async-ref@0.1.6: {} + + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + + async-sema@3.1.1: {} + + async@3.2.6: {} + + atomic-sleep@1.0.0: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001761 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + b4a@1.7.3: {} + + balanced-match@1.0.2: {} + + bare-events@2.8.2: {} + + base-x@3.0.9: + dependencies: + safe-buffer: 5.2.1 + + base-x@5.0.1: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.11: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + big.js@6.2.2: {} + + binary-extensions@2.2.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + birpc@0.2.19: {} + + birpc@2.9.0: {} + + blakejs@1.2.1: {} + + bn.js@4.12.0: {} + + bn.js@4.12.2: {} + + bn.js@5.2.1: {} + + bn.js@5.2.2: {} + + boolbase@1.0.0: {} + + bowser@2.13.1: {} + + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.2: + dependencies: + fill-range: 7.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brorand@1.1.0: {} + + browser-stdout@1.3.1: {} + + browserify-aes@1.2.0: + dependencies: + buffer-xor: 1.0.3 + cipher-base: 1.0.4 + create-hash: 1.2.0 + evp_bytestokey: 1.0.3 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + browserslist@4.23.0: + dependencies: + caniuse-lite: 1.0.30001761 + electron-to-chromium: 1.4.757 + node-releases: 2.0.14 + update-browserslist-db: 1.0.15(browserslist@4.23.0) + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001761 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bs58@4.0.1: + dependencies: + base-x: 3.0.9 + + bs58@6.0.0: + dependencies: + base-x: 5.0.1 + + bs58check@2.1.2: + dependencies: + bs58: 4.0.1 + create-hash: 1.2.0 + safe-buffer: 5.2.1 + + buffer-crc32@0.2.13: {} + + buffer-crc32@1.0.0: {} + + buffer-from@1.1.2: {} + + buffer-xor@1.0.3: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bufferutil@4.0.8: + dependencies: + node-gyp-build: 4.6.0 + + bufferutil@4.1.0: + dependencies: + node-gyp-build: 4.8.4 + + bun-types@1.1.29: + dependencies: + '@types/node': 20.12.14 + '@types/ws': 8.5.10 + + bun@1.1.30: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.1.30 + '@oven/bun-darwin-x64': 1.1.30 + '@oven/bun-darwin-x64-baseline': 1.1.30 + '@oven/bun-linux-aarch64': 1.1.30 + '@oven/bun-linux-x64': 1.1.30 + '@oven/bun-linux-x64-baseline': 1.1.30 + '@oven/bun-windows-x64': 1.1.30 + '@oven/bun-windows-x64-baseline': 1.1.30 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bundle-require@5.1.0(esbuild@0.27.2): + dependencies: + esbuild: 0.27.2 + load-tsconfig: 0.2.5 + + bytes@3.1.0: {} + + bytes@3.1.2: {} + + c12@3.3.3(magicast@0.3.5): + dependencies: + chokidar: 5.0.0 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 17.2.3 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 + + c12@3.3.3(magicast@0.5.1): + dependencies: + chokidar: 5.0.0 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 17.2.3 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.5.1 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001761 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001761: {} + + ccount@2.0.1: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + change-case@5.4.4: {} + + char-regex@1.0.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + chardet@0.7.0: {} + + chardet@2.1.1: {} + + check-error@2.1.1: {} + + chokidar@3.5.3: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.0: + dependencies: + readdirp: 4.1.2 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chownr@2.0.0: {} + + chownr@3.0.0: {} + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + cipher-base@1.0.4: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + cjs-module-lexer@1.2.3: {} + + cjs-module-lexer@1.4.1: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clean-stack@2.2.0: {} + + cli-boxes@2.2.1: {} + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@4.1.0: {} + + client-only@0.0.1: {} + + clipboardy@4.0.0: + dependencies: + execa: 8.0.1 + is-wsl: 3.1.0 + is64bit: 2.0.0 + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: + optional: true + + clsx@1.2.1: {} + + clsx@2.1.1: {} + + cluster-key-slot@1.1.2: {} + + code-block-writer@10.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colord@2.9.3: {} + + colorette@2.0.20: {} + + comma-separated-tokens@2.0.3: {} + + command-exists@1.2.9: {} + + commander@10.0.1: {} + + commander@11.1.0: {} + + commander@2.20.3: {} + + commander@3.0.2: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commondir@1.0.1: {} + + compatx@0.2.0: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.7: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + consola@3.2.3: {} + + consola@3.4.2: {} + + content-type@1.0.4: {} + + convert-hrtime@3.0.0: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cookie-es@2.0.0: {} + + cookie@0.4.2: {} + + cookie@0.5.0: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + copy-paste@2.2.0: + dependencies: + iconv-lite: 0.4.24 + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + + create-hash@1.2.0: + dependencies: + cipher-base: 1.0.4 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.2 + sha.js: 2.4.11 + + create-hmac@1.1.7: + dependencies: + cipher-base: 1.0.4 + create-hash: 1.2.0 + inherits: 2.0.4 + ripemd160: 2.0.2 + safe-buffer: 5.2.1 + sha.js: 2.4.11 + + create-require@1.1.1: {} + + croner@9.1.0: {} + + cross-fetch@3.2.0(encoding@0.1.13): + dependencies: + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + cross-fetch@4.1.0(encoding@0.1.13): + dependencies: + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.2.4: {} + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + crypto-random-string@1.0.0: {} + + css-declaration-sorter@7.3.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@7.0.10(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + css-declaration-sorter: 7.3.0(postcss@8.5.6) + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 10.1.1(postcss@8.5.6) + postcss-colormin: 7.0.5(postcss@8.5.6) + postcss-convert-values: 7.0.8(postcss@8.5.6) + postcss-discard-comments: 7.0.5(postcss@8.5.6) + postcss-discard-duplicates: 7.0.2(postcss@8.5.6) + postcss-discard-empty: 7.0.1(postcss@8.5.6) + postcss-discard-overridden: 7.0.1(postcss@8.5.6) + postcss-merge-longhand: 7.0.5(postcss@8.5.6) + postcss-merge-rules: 7.0.7(postcss@8.5.6) + postcss-minify-font-values: 7.0.1(postcss@8.5.6) + postcss-minify-gradients: 7.0.1(postcss@8.5.6) + postcss-minify-params: 7.0.5(postcss@8.5.6) + postcss-minify-selectors: 7.0.5(postcss@8.5.6) + postcss-normalize-charset: 7.0.1(postcss@8.5.6) + postcss-normalize-display-values: 7.0.1(postcss@8.5.6) + postcss-normalize-positions: 7.0.1(postcss@8.5.6) + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.6) + postcss-normalize-string: 7.0.1(postcss@8.5.6) + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.6) + postcss-normalize-unicode: 7.0.5(postcss@8.5.6) + postcss-normalize-url: 7.0.1(postcss@8.5.6) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.6) + postcss-ordered-values: 7.0.2(postcss@8.5.6) + postcss-reduce-initial: 7.0.5(postcss@8.5.6) + postcss-reduce-transforms: 7.0.1(postcss@8.5.6) + postcss-svgo: 7.1.0(postcss@8.5.6) + postcss-unique-selectors: 7.0.4(postcss@8.5.6) + + cssnano-utils@5.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssnano@7.1.2(postcss@8.5.6): + dependencies: + cssnano-preset-default: 7.0.10(postcss@8.5.6) + lilconfig: 3.1.3 + postcss: 8.5.6 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.1.3: {} + + csstype@3.2.3: {} + + dataloader@1.4.0: {} + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.28.4 + + dateformat@4.6.3: {} + + dayjs@1.11.13: {} + + db0@0.3.4: {} + + de-indent@1.0.2: {} + + debounce@1.2.1: {} + + debug@4.3.4(supports-color@8.1.1): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 8.1.1 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decamelize@4.0.0: {} + + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + + decode-uri-component@0.2.2: {} + + dedent@1.6.0: {} + + deep-eql@5.0.2: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + optional: true + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-lazy-prop@3.0.0: {} + + defu@6.1.4: {} + + denque@2.1.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + derive-valtio@0.1.0(valtio@1.13.2(@types/react@18.3.27)(react@18.3.1)): + dependencies: + valtio: 1.13.2(@types/react@18.3.27)(react@18.3.1) + + destr@2.0.5: {} + + detect-browser@5.3.0: {} + + detect-indent@6.1.0: {} + + detect-libc@1.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + devalue@5.6.1: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@4.0.2: {} + + diff@5.0.0: {} + + diff@8.0.2: {} + + dijkstrajs@1.0.2: {} + + dir-glob@2.2.2: + dependencies: + path-type: 3.0.0 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-accessibility-api@0.5.16: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@10.1.0: + dependencies: + type-fest: 5.3.1 - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} + dotenv-expand@10.0.0: {} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + dotenv@16.3.1: {} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + dotenv@16.6.1: {} - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dotenv@17.2.3: {} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + dotenv@8.6.0: {} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} + duplexer@0.1.2: {} - pkijs@3.3.3: - resolution: {integrity: sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==} - engines: {node: '>=16.0.0'} + duplexify@4.1.2: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.1 - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: {} - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} + easy-table@1.2.0: + dependencies: + ansi-regex: 5.0.1 + optionalDependencies: + wcwidth: 1.0.1 - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} - engines: {node: ^10 || ^12 || >=14} + eciesjs@0.4.16: + dependencies: + '@ecies/ciphers': 0.2.5(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} + edge-runtime@2.5.9: + dependencies: + '@edge-runtime/format': 2.2.1 + '@edge-runtime/ponyfill': 2.4.2 + '@edge-runtime/vm': 3.2.0 + async-listen: 3.0.1 + mri: 1.2.0 + picocolors: 1.0.0 + pretty-ms: 7.0.1 + signal-exit: 4.0.2 + time-span: 4.0.0 - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.3 - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} - engines: {node: '>=14'} - hasBin: true + ee-first@1.1.1: {} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + electron-to-chromium@1.4.757: {} - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} + electron-to-chromium@1.5.267: {} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + elliptic@6.5.4: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 - pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + elliptic@6.6.0: + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + elliptic@6.6.1: + dependencies: + bn.js: 4.12.2 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 - pvtsutils@1.3.6: - resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + emoji-regex@8.0.0: {} - pvutils@1.1.5: - resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} - engines: {node: '>=16.0.0'} + emoji-regex@9.2.2: {} - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + emojilib@2.4.0: {} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + encode-utf8@1.0.3: {} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true + encodeurl@2.0.0: {} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} - peerDependencies: - react: ^19.2.3 + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + end-of-stream@1.1.0: + dependencies: + once: 1.3.3 - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} - engines: {node: '>=0.10.0'} + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 - read-yaml-file@1.1.0: - resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} - engines: {node: '>=6'} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + engine.io-client@6.6.4(bufferutil@4.1.0)(utf-8-validate@5.0.10): + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + engine.io-parser: 5.2.3 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + engine.io-parser@5.2.3: {} - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} + enhanced-resolve@5.17.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 - registry-auth-token@3.3.2: - resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + enquirer@2.3.6: + dependencies: + ansi-colors: 4.1.3 - registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + entities@4.5.0: {} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} + entities@7.0.0: {} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} + env-paths@2.2.1: {} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true + environment@1.1.0: {} - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true + error-stack-parser-es@1.0.5: {} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + errx@0.1.0: {} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + es-define-property@1.0.1: {} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + es-errors@1.3.0: {} - rimraf@6.1.3: - resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} - engines: {node: 20 || >=22} - hasBin: true + es-module-lexer@1.4.1: {} - rollup@4.53.4: - resolution: {integrity: sha512-YpXaaArg0MvrnJpvduEDYIp7uGOqKXbH9NsHGQ6SxKCOsNAjZF018MmxefFUulVP2KLtiGw1UvZbr+/ekjvlDg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + es-module-lexer@1.7.0: {} - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + es-toolkit@1.33.0: {} - rxjs@6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} + esbuild-android-64@0.14.47: + optional: true - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + esbuild-android-arm64@0.14.47: + optional: true - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} + esbuild-darwin-64@0.14.47: + optional: true - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + esbuild-darwin-arm64@0.14.47: + optional: true - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} + esbuild-freebsd-64@0.14.47: + optional: true - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} + esbuild-freebsd-arm64@0.14.47: + optional: true - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + esbuild-linux-32@0.14.47: + optional: true - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + esbuild-linux-64@0.14.47: + optional: true - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + esbuild-linux-arm64@0.14.47: + optional: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true + esbuild-linux-arm@0.14.47: + optional: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true + esbuild-linux-mips64le@0.14.47: + optional: true - sentence-case@2.1.1: - resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==} + esbuild-linux-ppc64le@0.14.47: + optional: true - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} + esbuild-linux-riscv64@0.14.47: + optional: true - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} + esbuild-linux-s390x@0.14.47: + optional: true - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} + esbuild-netbsd-64@0.14.47: + optional: true - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + esbuild-openbsd-64@0.14.47: + optional: true + + esbuild-sunos-64@0.14.47: + optional: true + + esbuild-windows-32@0.14.47: + optional: true + + esbuild-windows-64@0.14.47: + optional: true + + esbuild-windows-arm64@0.14.47: + optional: true + + esbuild@0.14.47: + optionalDependencies: + esbuild-android-64: 0.14.47 + esbuild-android-arm64: 0.14.47 + esbuild-darwin-64: 0.14.47 + esbuild-darwin-arm64: 0.14.47 + esbuild-freebsd-64: 0.14.47 + esbuild-freebsd-arm64: 0.14.47 + esbuild-linux-32: 0.14.47 + esbuild-linux-64: 0.14.47 + esbuild-linux-arm: 0.14.47 + esbuild-linux-arm64: 0.14.47 + esbuild-linux-mips64le: 0.14.47 + esbuild-linux-ppc64le: 0.14.47 + esbuild-linux-riscv64: 0.14.47 + esbuild-linux-s390x: 0.14.47 + esbuild-netbsd-64: 0.14.47 + esbuild-openbsd-64: 0.14.47 + esbuild-sunos-64: 0.14.47 + esbuild-windows-32: 0.14.47 + esbuild-windows-64: 0.14.47 + esbuild-windows-arm64: 0.14.47 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + esprima@4.0.1: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + etag@1.8.1: {} + + eth-block-tracker@7.1.0: + dependencies: + '@metamask/eth-json-rpc-provider': 1.0.1 + '@metamask/safe-event-emitter': 3.0.0 + '@metamask/utils': 5.0.2 + json-rpc-random-id: 1.0.1 + pify: 3.0.0 + transitivePeerDependencies: + - supports-color + + eth-json-rpc-filters@6.0.1: + dependencies: + '@metamask/safe-event-emitter': 3.0.0 + async-mutex: 0.2.6 + eth-query: 2.1.2 + json-rpc-engine: 6.1.0 + pify: 5.0.0 + + eth-query@2.1.2: + dependencies: + json-rpc-random-id: 1.0.1 + xtend: 4.0.2 + + eth-rpc-errors@4.0.3: + dependencies: + fast-safe-stringify: 2.1.1 + + ethereum-cryptography@0.1.3: + dependencies: + '@types/pbkdf2': 3.1.2 + '@types/secp256k1': 4.0.3 + blakejs: 1.2.1 + browserify-aes: 1.2.0 + bs58check: 2.1.2 + create-hash: 1.2.0 + create-hmac: 1.1.7 + hash.js: 1.1.7 + keccak: 3.0.3 + pbkdf2: 3.1.2 + randombytes: 2.1.0 + safe-buffer: 5.2.1 + scrypt-js: 3.0.1 + secp256k1: 4.0.3 + setimmediate: 1.0.5 + + ethereum-cryptography@1.2.0: + dependencies: + '@noble/hashes': 1.2.0 + '@noble/secp256k1': 1.7.1 + '@scure/bip32': 1.1.5 + '@scure/bip39': 1.1.1 + + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + + ethereumjs-abi@0.6.8: + dependencies: + bn.js: 4.12.0 + ethereumjs-util: 6.2.1 + + ethereumjs-util@6.2.1: + dependencies: + '@types/bn.js': 4.11.6 + bn.js: 4.12.0 + create-hash: 1.2.0 + elliptic: 6.6.0 + ethereum-cryptography: 0.1.3 + ethjs-util: 0.1.6 + rlp: 2.2.7 + + ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.5): + dependencies: + '@adraffy/ens-normalize': 1.10.1 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 22.7.5 + aes-js: 4.0.0-beta.5 + tslib: 2.7.0 + ws: 8.17.1(bufferutil@4.1.0)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ethjs-util@0.1.6: + dependencies: + is-hex-prefixed: 1.0.0 + strip-hex-prefix: 1.0.0 + + event-target-shim@5.0.1: {} + + eventemitter2@6.4.9: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + events-intercept@2.0.0: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + evp_bytestokey@1.0.3: + dependencies: + md5.js: 1.3.5 + safe-buffer: 5.2.1 + + execa@3.2.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + p-finally: 2.0.1 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} + execa@9.1.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 7.0.0 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 5.3.0 + pretty-ms: 9.0.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.0.2 - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} + expect-type@1.2.1: {} - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} + exsolve@1.0.8: {} - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} + extendable-error@0.1.7: {} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} + extension-port-stream@3.0.0: + dependencies: + readable-stream: 4.7.0 + webextension-polyfill: 0.10.0 - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + externality@1.0.2: + dependencies: + enhanced-resolve: 5.18.4 + mlly: 1.8.0 + pathe: 1.1.2 + ufo: 1.6.1 - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} + fast-copy@3.0.2: {} - simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} + fast-deep-equal@3.1.3: {} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} + fast-fifo@1.3.2: {} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 - snake-case@2.1.0: - resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} + fast-npm-meta@0.4.7: {} - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + fast-redact@3.1.2: {} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + fast-safe-stringify@2.1.1: {} - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.2 - spawndamnit@3.0.1: - resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + fastq@1.15.0: + dependencies: + reusify: 1.0.4 - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + fdir@6.1.1(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} + fflate@0.8.2: {} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + figures@6.1.0: + dependencies: + is-unicode-supported: 2.0.0 - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} + file-uri-to-path@1.0.0: {} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} + fill-range@7.0.1: + dependencies: + to-regex-range: 5.0.1 - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} + filter-obj@1.1.0: {} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + fixturez@1.1.0: + dependencies: + fs-extra: 5.0.0 + globby: 7.1.1 + signal-exit: 3.0.7 + tempy: 0.2.1 - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} + flag@5.0.1(react@18.3.1): + dependencies: + '@types/react': 18.3.27 + async-ref: 0.1.6 + react: 18.3.1 - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + flat@5.0.2: {} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} + floating-vue@5.2.2(@nuxt/kit@3.20.2(magicast@0.5.1))(vue@3.5.12(typescript@5.9.3)): + dependencies: + '@floating-ui/dom': 1.1.1 + vue: 3.5.12(typescript@5.9.3) + vue-resize: 2.0.0-alpha.1(vue@3.5.12(typescript@5.9.3)) + optionalDependencies: + '@nuxt/kit': 3.20.2(magicast@0.5.1) - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + focus-trap@7.6.0: + dependencies: + tabbable: 6.2.0 - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + follow-redirects@1.15.2(debug@4.3.4): + optionalDependencies: + debug: 4.3.4(supports-color@8.1.1) - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + fp-ts@1.19.3: {} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + fraction.js@5.3.4: {} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + framer-motion@12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 12.23.23 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - swap-case@1.1.2: - resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} + fresh@2.0.0: {} - syncpack-darwin-arm64@14.0.0: - resolution: {integrity: sha512-mEcku9YkOHfM6JOxhq9McgeLvd4djsJvRwNONZXHeoJ6+yqC96DdxZwFjkw3e8Vn95wsxsrwY5ZjMExs5Gbacw==} - cpu: [arm64] - os: [darwin] + fs-extra@0.30.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 2.4.0 + klaw: 1.3.1 + path-is-absolute: 1.0.1 + rimraf: 2.7.1 - syncpack-darwin-x64@14.0.0: - resolution: {integrity: sha512-qSj3bT3SIZA5NLM5PNVeExapyOrdmptlGSMu0SLVHj9hata/Vqh3xWgwq7wB9uajmlrHljeJ84R/NKAHyzFewA==} - cpu: [x64] - os: [darwin] + fs-extra@11.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 - syncpack-linux-arm64-musl@14.0.0: - resolution: {integrity: sha512-i57Chz0tHP7ybKElPLhoygl6Cab1fxyUKS9xRgezX5NDrNxKUgrvqBfYd7288/QKB4C8+IcYHugFPjZjoFlAIA==} - cpu: [arm64] - os: [linux] + fs-extra@5.0.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 - syncpack-linux-arm64@14.0.0: - resolution: {integrity: sha512-votlkb4P/0Bxd9yhBWCSUOjZwaii+LfFn1tZZVN5dfs8qcjcLeqfdG5zbnlJSzZhS3T+BfzejFW+Z95OxZag0A==} - cpu: [arm64] - os: [linux] + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 - syncpack-linux-x64-musl@14.0.0: - resolution: {integrity: sha512-RKyp+29UlLGE8oYkd3UfwgjWUHN4dLHDyarv0dS6gWVpWM6qHtHS339KBc9JcIS7FD1vNGnQ3GmSaAodxDlkMA==} - cpu: [x64] - os: [linux] + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 - syncpack-linux-x64@14.0.0: - resolution: {integrity: sha512-sZGy4+uL+P/+nI0yc9jwW/R22u9lw0+NXDYC/9ts9eYiWFyO4+luOeosvVKbED1OavUp/GQJqNV+9KHjMwq0Fw==} - cpu: [x64] - os: [linux] + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 - syncpack-windows-arm64@14.0.0: - resolution: {integrity: sha512-aNtr0F6HkA7M0azDuDkt//DlPWlplFa4kmvHXirwDmJQ9u3SAvN3ZItW4ZYS96ZbiV1DgO15jIKBI7rDkzcwrQ==} - cpu: [arm64] - os: [win32] + fs.realpath@1.0.0: {} - syncpack-windows-x64@14.0.0: - resolution: {integrity: sha512-usMH61wlonssfh2Q8SJDiiAcsXVzuPzRl8XdHqdavR3XeCSGBIW9ieJpPfiKvDPWslekufWD+GhLNoH+8vV0Cg==} - cpu: [x64] - os: [win32] + fsevents@2.3.3: + optional: true - syncpack@14.0.0: - resolution: {integrity: sha512-OfAa3Oip5YC9Ad1jEs92Hw08Wy4JfdpdeequIwaJGsQG0tJtb8gpQfCdLuBefsk6n8WiUdt/5qmzcW+BDXtzbQ==} - engines: {node: '>=14.17.0'} - deprecated: 'pnpm users: upgrade to 14.0.2 or newer' - hasBin: true + function-bind@1.1.2: {} - term-size@2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} + fuse.js@7.1.0: {} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + generator-function@2.0.1: {} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + generic-pool@3.4.2: {} - tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + gensync@1.0.0-beta.2: {} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} + get-caller-file@2.0.5: {} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 - tinygradient@1.1.5: - resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + get-nonce@1.0.1: {} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} + get-port-please@3.2.0: {} - title-case@2.1.1: - resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} + get-port@7.1.0: {} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + get-stream@5.2.0: + dependencies: + pump: 3.0.3 - touch@3.1.1: - resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} - hasBin: true + get-stream@6.0.1: {} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true + get-stream@8.0.1: {} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + git-up@8.1.1: + dependencies: + is-ssh: 1.4.1 + parse-url: 9.2.0 - turbo-darwin-64@2.8.10: - resolution: {integrity: sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g==} - cpu: [x64] - os: [darwin] + git-url-parse@16.1.0: + dependencies: + git-up: 8.1.1 - turbo-darwin-arm64@2.8.10: - resolution: {integrity: sha512-sidzowgWL3s5xCHLeqwC9M3s9M0i16W1nuQF3Mc7fPHpZ+YPohvcbVFBB2uoRRHYZg6yBnwD4gyUHKTeXfwtXA==} - cpu: [arm64] - os: [darwin] + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 - turbo-linux-64@2.8.10: - resolution: {integrity: sha512-YK9vcpL3TVtqonB021XwgaQhY9hJJbKKUhLv16osxV0HkcQASQWUqR56yMge7puh6nxU67rQlTq1b7ksR1T3KA==} - cpu: [x64] - os: [linux] + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 - turbo-linux-arm64@2.8.10: - resolution: {integrity: sha512-3+j2tL0sG95iBJTm+6J8/45JsETQABPqtFyYjVjBbi6eVGdtNTiBmHNKrbvXRlQ3ZbUG75bKLaSSDHSEEN+btQ==} - cpu: [arm64] - os: [linux] + glob@13.0.0: + dependencies: + minimatch: 10.1.1 + minipass: 7.1.2 + path-scurry: 2.0.1 - turbo-windows-64@2.8.10: - resolution: {integrity: sha512-hdeF5qmVY/NFgiucf8FW0CWJWtyT2QPm5mIsX0W1DXAVzqKVXGq+Zf+dg4EUngAFKjDzoBeN6ec2Fhajwfztkw==} - cpu: [x64] - os: [win32] + glob@7.2.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 - turbo-windows-arm64@2.8.10: - resolution: {integrity: sha512-QGdr/Q8LWmj+ITMkSvfiz2glf0d7JG0oXVzGL3jxkGqiBI1zXFj20oqVY0qWi+112LO9SVrYdpHS0E/oGFrMbQ==} - cpu: [arm64] - os: [win32] + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 - turbo@2.8.10: - resolution: {integrity: sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ==} - hasBin: true + global-directory@4.0.1: + dependencies: + ini: 4.1.1 - turbo@2.8.21: - resolution: {integrity: sha512-FlJ8OD5Qcp0jTAM7E4a/RhUzRNds2GzKlyxHKA6N247VLy628rrxAGlMpIXSz6VB430+TiQDJ/SMl6PL1lu6wQ==} - hasBin: true + globals@11.12.0: {} - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} + globby@15.0.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} + globby@7.1.1: + dependencies: + array-union: 1.0.2 + dir-glob: 2.2.2 + glob: 7.2.3 + ignore: 3.3.10 + pify: 3.0.0 + slash: 1.0.0 - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} + gopd@1.2.0: {} - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} + graceful-fs@4.2.11: {} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} + graphql@16.8.1: {} - typescript-eslint@8.50.0: - resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true + gzip-size@7.0.0: + dependencies: + duplexer: 0.1.2 - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true + h3@1.15.4: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.1 + uncrypto: 0.1.3 - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} + happy-dom@20.0.2: + dependencies: + '@types/node': 20.19.27 + '@types/whatwg-mimetype': 3.0.2 + whatwg-mimetype: 3.0.0 - undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + hardhat@2.22.3(bufferutil@4.1.0)(ts-node@10.9.1(@types/node@24.0.1)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10): + dependencies: + '@ethersproject/abi': 5.7.0 + '@metamask/eth-sig-util': 4.0.1 + '@nomicfoundation/edr': 0.3.7 + '@nomicfoundation/ethereumjs-common': 4.0.4 + '@nomicfoundation/ethereumjs-tx': 5.0.4 + '@nomicfoundation/ethereumjs-util': 9.0.4 + '@nomicfoundation/solidity-analyzer': 0.1.1 + '@sentry/node': 5.30.0 + '@types/bn.js': 5.1.5 + '@types/lru-cache': 5.1.1 + adm-zip: 0.4.16 + aggregate-error: 3.1.0 + ansi-escapes: 4.3.2 + boxen: 5.1.2 + chalk: 2.4.2 + chokidar: 3.6.0 + ci-info: 2.0.0 + debug: 4.3.4(supports-color@8.1.1) + enquirer: 2.3.6 + env-paths: 2.2.1 + ethereum-cryptography: 1.2.0 + ethereumjs-abi: 0.6.8 + find-up: 2.1.0 + fp-ts: 1.19.3 + fs-extra: 7.0.1 + glob: 7.2.0 + immutable: 4.3.5 + io-ts: 1.10.4 + keccak: 3.0.3 + lodash: 4.17.21 + mnemonist: 0.38.5 + mocha: 10.2.0 + p-map: 4.0.0 + raw-body: 2.5.1 + resolve: 1.17.0 + semver: 6.3.1 + solc: 0.7.3(debug@4.3.4) + source-map-support: 0.5.21 + stacktrace-parser: 0.1.10 + tsort: 0.0.1 + undici: 5.28.3 + uuid: 8.3.2 + ws: 7.5.9(bufferutil@4.1.0)(utf-8-validate@5.0.10) + optionalDependencies: + ts-node: 10.9.1(@types/node@24.0.1)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - c-kzg + - supports-color + - utf-8-validate - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + has-flag@3.0.0: {} - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} + has-flag@4.0.0: {} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + has-symbols@1.1.0: {} - update-check@1.5.4: - resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 - upper-case-first@1.1.2: - resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==} + hash-base@3.1.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + safe-buffer: 5.2.1 - upper-case@1.1.3: - resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + hast-util-to-html@9.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.2 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} - hasBin: true + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + he@1.2.0: {} - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + headers-polyfill@4.0.2: {} - viem@2.42.1: - resolution: {integrity: sha512-NzT/f54jT+b0Um6pYzN/uAGMLg+3twhricAzXS+XH8pVIREzPEh7P25rlhPQnLYiPWzQd9mrFcvnm73Sc8bx+A==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true + help-me@5.0.0: {} - vite@7.3.0: - resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true + highlight.js@10.7.3: {} - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} - 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.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + hookable@5.5.3: {} - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} + html-escaper@2.0.2: {} - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} + html-void-elements@3.0.0: {} - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} + http-errors@1.4.0: + dependencies: + inherits: 2.0.1 + statuses: 1.5.0 - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} + http-errors@1.7.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.2(debug@4.3.4) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} + http-shutdown@1.2.2: {} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + httpxy@0.1.7: {} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} + human-id@1.0.2: {} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + human-id@4.1.3: {} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - 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 + human-signals@1.1.1: {} - 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 + human-signals@2.1.0: {} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + human-signals@5.0.0: {} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + human-signals@7.0.0: {} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + iconv-lite@0.7.1: + dependencies: + safer-buffer: 2.1.2 - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} + idb-keyval@6.2.1: {} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + idb@7.1.1: {} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} + ieee754@1.2.1: {} - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 + ignore@3.3.10: {} - zod@4.2.0: - resolution: {integrity: sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw==} + ignore@5.3.2: {} -snapshots: + ignore@7.0.5: {} - '@0xsequence/tee-verifier@0.1.2': + image-meta@0.2.2: {} + + immutable@4.3.5: {} + + impound@1.0.0: dependencies: - cbor2: 1.12.0 - pkijs: 3.3.3 + exsolve: 1.0.8 + mocked-exports: 0.1.1 + pathe: 2.0.3 + unplugin: 2.3.11 + unplugin-utils: 0.2.5 - '@adraffy/ens-normalize@1.11.1': {} + indent-string@4.0.0: {} - '@babel/code-frame@7.27.1': + inflight@1.0.6: dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 + once: 1.4.0 + wrappy: 1.0.2 - '@babel/compat-data@7.28.5': {} + inherits@2.0.1: {} - '@babel/core@7.28.5': + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@4.1.1: {} + + io-ts@1.10.4: dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 + fp-ts: 1.19.3 + + ioredis@5.8.2: + dependencies: + '@ioredis/commands': 1.4.0 + cluster-key-slot: 1.1.2 + debug: 4.4.3 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + iron-webcrypto@1.2.1: {} + + is-arguments@1.2.0: dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 + call-bound: 1.0.4 + has-tostringtag: 1.0.2 - '@babel/helper-compilation-targets@7.27.2': + is-binary-path@2.1.0: dependencies: - '@babel/compat-data': 7.28.5 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 + binary-extensions: 2.2.0 - '@babel/helper-globals@7.28.0': {} + is-buffer@2.0.5: {} - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - transitivePeerDependencies: - - supports-color + is-callable@1.2.7: {} - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + is-core-module@2.16.1: dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color + hasown: 2.0.2 - '@babel/helper-string-parser@7.27.1': {} + is-docker@2.2.1: {} - '@babel/helper-validator-identifier@7.28.5': {} + is-docker@3.0.0: {} - '@babel/helper-validator-option@7.27.1': {} + is-extglob@2.1.1: {} - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + is-fullwidth-code-point@3.0.0: {} - '@babel/parser@7.28.5': + is-generator-function@1.1.2: dependencies: - '@babel/types': 7.28.5 + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 - '@babel/runtime-corejs3@7.28.4': + is-glob@4.0.3: dependencies: - core-js-pure: 3.47.0 + is-extglob: 2.1.1 - '@babel/runtime@7.28.4': {} + is-hex-prefixed@1.0.0: {} - '@babel/template@7.27.2': + is-inside-container@1.0.0: dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + is-docker: 3.0.0 - '@babel/traverse@7.28.5': + is-installed-globally@1.0.0: dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color + global-directory: 4.0.1 + is-path-inside: 4.0.0 - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + is-module@1.0.0: {} - '@bcoe/v8-coverage@1.0.2': {} + is-node-process@1.2.0: {} - '@changesets/apply-release-plan@7.0.14': + is-number@7.0.0: {} + + is-path-inside@4.0.0: {} + + is-plain-obj@2.1.0: {} + + is-plain-obj@4.1.0: {} + + is-plain-object@5.0.0: {} + + is-reference@1.2.1: dependencies: - '@changesets/config': 3.1.2 - '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.4 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.8.8 - resolve-from: 5.0.0 - semver: 7.7.3 + '@types/estree': 1.0.8 - '@changesets/assemble-release-plan@6.0.9': + is-regex@1.2.1: dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - semver: 7.7.3 + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 - '@changesets/changelog-git@0.2.1': + is-ssh@1.4.1: dependencies: - '@changesets/types': 6.1.0 + protocols: 2.0.2 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} - '@changesets/cli@2.29.8(@types/node@25.3.0)': + is-stream@4.0.1: {} + + is-subdir@1.2.0: dependencies: - '@changesets/apply-release-plan': 7.0.14 - '@changesets/assemble-release-plan': 6.0.9 - '@changesets/changelog-git': 0.2.1 - '@changesets/config': 3.1.2 - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.14 - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.6 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@25.3.0) - '@manypkg/get-packages': 1.1.3 - ansi-colors: 4.1.3 - ci-info: 3.9.0 - enquirer: 2.4.1 - fs-extra: 7.0.1 - mri: 1.2.0 - p-limit: 2.3.0 - package-manager-detector: 0.2.11 - picocolors: 1.1.1 - resolve-from: 5.0.0 - semver: 7.7.3 - spawndamnit: 3.0.1 - term-size: 2.2.1 - transitivePeerDependencies: - - '@types/node' + better-path-resolve: 1.0.0 - '@changesets/config@3.1.2': + is-typed-array@1.1.15: dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/logger': 0.1.1 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.8 + which-typed-array: 1.1.19 - '@changesets/errors@0.2.0': + is-unicode-supported@0.1.0: {} + + is-unicode-supported@2.0.0: {} + + is-what@4.1.16: {} + + is-what@5.5.0: {} + + is-windows@1.0.2: {} + + is-wsl@2.2.0: dependencies: - extendable-error: 0.1.7 + is-docker: 2.2.1 - '@changesets/get-dependents-graph@2.1.3': + is-wsl@3.1.0: dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.1 - semver: 7.7.3 + is-inside-container: 1.0.0 - '@changesets/get-release-plan@4.0.14': + is64bit@2.0.0: dependencies: - '@changesets/assemble-release-plan': 6.0.9 - '@changesets/config': 3.1.2 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.6 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 + system-architecture: 0.1.0 - '@changesets/get-version-range-type@0.4.0': {} + isarray@0.0.1: {} - '@changesets/git@3.0.4': + isarray@1.0.0: {} + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + isows@1.0.4(ws@8.13.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)): dependencies: - '@changesets/errors': 0.2.0 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.8 - spawndamnit: 3.0.1 + ws: 8.13.0(bufferutil@4.0.8)(utf-8-validate@6.0.5) - '@changesets/logger@0.1.1': + isows@1.0.4(ws@8.13.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)): dependencies: - picocolors: 1.1.1 + ws: 8.13.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) - '@changesets/parse@0.4.2': + isows@1.0.4(ws@8.17.1(bufferutil@4.1.0)(utf-8-validate@5.0.10)): dependencies: - '@changesets/types': 6.1.0 - js-yaml: 4.1.1 + ws: 8.17.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@changesets/pre@2.0.2': + isows@1.0.6(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)): dependencies: - '@changesets/errors': 0.2.0 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 + ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@changesets/read@0.6.6': + isows@1.0.7(ws@8.18.2(bufferutil@4.1.0)(utf-8-validate@6.0.5)): dependencies: - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.2 - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - p-filter: 2.1.0 - picocolors: 1.1.1 + ws: 8.18.2(bufferutil@4.1.0)(utf-8-validate@6.0.5) - '@changesets/should-skip-package@0.1.2': + isows@1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)): dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) - '@changesets/types@4.1.0': {} + isows@1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5)): + dependencies: + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5) - '@changesets/types@6.1.0': {} + istanbul-lib-coverage@3.2.2: {} - '@changesets/write@0.4.0': + istanbul-lib-report@3.0.1: dependencies: - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - human-id: 4.1.3 - prettier: 2.8.8 + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 - '@cspotcode/source-map-support@0.8.1': + istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color - '@emnapi/runtime@1.9.1': + istanbul-reports@3.2.0: dependencies: - tslib: 2.8.1 - optional: true + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 - '@esbuild/aix-ppc64@0.27.3': - optional: true + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 - '@esbuild/android-arm64@0.27.3': - optional: true + jiti@1.21.0: {} - '@esbuild/android-arm@0.27.3': - optional: true + jiti@1.21.6: {} - '@esbuild/android-x64@0.27.3': - optional: true + jiti@1.21.7: {} - '@esbuild/darwin-arm64@0.27.3': - optional: true + jiti@2.6.1: {} - '@esbuild/darwin-x64@0.27.3': - optional: true + jose@5.9.6: {} - '@esbuild/freebsd-arm64@0.27.3': - optional: true + joycon@3.1.1: {} - '@esbuild/freebsd-x64@0.27.3': - optional: true + js-base64@3.7.8: {} - '@esbuild/linux-arm64@0.27.3': - optional: true + js-beautify@1.15.1: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 - '@esbuild/linux-arm@0.27.3': - optional: true + js-cookie@3.0.5: {} - '@esbuild/linux-ia32@0.27.3': - optional: true + js-sha3@0.8.0: {} - '@esbuild/linux-loong64@0.27.3': - optional: true + js-tokens@4.0.0: {} - '@esbuild/linux-mips64el@0.27.3': - optional: true + js-tokens@9.0.1: {} - '@esbuild/linux-ppc64@0.27.3': - optional: true + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 - '@esbuild/linux-riscv64@0.27.3': - optional: true + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 - '@esbuild/linux-s390x@0.27.3': - optional: true + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 - '@esbuild/linux-x64@0.27.3': - optional: true + jsesc@2.5.2: {} - '@esbuild/netbsd-arm64@0.27.3': - optional: true + jsesc@3.1.0: {} - '@esbuild/netbsd-x64@0.27.3': - optional: true + json-canonicalize@1.2.0: {} - '@esbuild/openbsd-arm64@0.27.3': - optional: true + json-rpc-engine@6.1.0: + dependencies: + '@metamask/safe-event-emitter': 2.0.0 + eth-rpc-errors: 4.0.3 - '@esbuild/openbsd-x64@0.27.3': - optional: true + json-rpc-random-id@1.0.1: {} - '@esbuild/openharmony-arm64@0.27.3': - optional: true + json-schema-to-ts@1.6.4: + dependencies: + '@types/json-schema': 7.0.15 + ts-toolbelt: 6.15.5 - '@esbuild/sunos-x64@0.27.3': - optional: true + json-schema-traverse@1.0.0: {} - '@esbuild/win32-arm64@0.27.3': - optional: true + json5@2.2.3: {} - '@esbuild/win32-ia32@0.27.3': - optional: true + jsonfile@2.4.0: + optionalDependencies: + graceful-fs: 4.2.11 - '@esbuild/win32-x64@0.27.3': - optional: true + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': + jsonfile@6.2.0: dependencies: - eslint: 9.39.2 - eslint-visitor-keys: 3.4.3 + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 - '@eslint-community/regexpp@4.12.2': {} + jwt-decode@4.0.0: {} - '@eslint/config-array@0.21.1': + keccak256@1.0.6: dependencies: - '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@5.5.0) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + bn.js: 5.2.2 + buffer: 6.0.3 + keccak: 3.0.4 - '@eslint/config-helpers@0.4.2': + keccak@3.0.3: dependencies: - '@eslint/core': 0.17.0 + node-addon-api: 2.0.2 + node-gyp-build: 4.6.0 + readable-stream: 3.6.2 - '@eslint/core@0.17.0': + keccak@3.0.4: dependencies: - '@types/json-schema': 7.0.15 + node-addon-api: 2.0.2 + node-gyp-build: 4.8.4 + readable-stream: 3.6.2 - '@eslint/eslintrc@3.3.3': - dependencies: - ajv: 6.12.6 - debug: 4.4.3(supports-color@5.5.0) - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color + keyvaluestorage-interface@1.0.0: {} - '@eslint/js@9.39.2': {} + klaw@1.3.1: + optionalDependencies: + graceful-fs: 4.2.11 - '@eslint/object-schema@2.1.7': {} + kleur@3.0.3: {} - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 - levn: 0.4.1 + kleur@4.1.5: {} - '@humanfs/core@0.19.1': {} + klona@2.0.6: {} - '@humanfs/node@0.16.7': + knip@5.30.6(@types/node@24.0.1)(typescript@5.8.3): dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.4.3 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} + '@nodelib/fs.walk': 1.2.8 + '@snyk/github-codeowners': 1.1.0 + '@types/node': 24.0.1 + easy-table: 1.2.0 + enhanced-resolve: 5.17.1 + fast-glob: 3.3.2 + jiti: 1.21.6 + js-yaml: 4.1.0 + minimist: 1.2.8 + picocolors: 1.1.0 + picomatch: 4.0.2 + pretty-ms: 9.0.0 + smol-toml: 1.3.0 + strip-json-comments: 5.0.1 + summary: 2.1.0 + typescript: 5.8.3 + zod: 3.25.20 + zod-validation-error: 3.2.0(zod@3.25.20) - '@img/colour@1.1.0': - optional: true + knitwork@1.3.0: {} - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true + kolorist@1.8.0: {} - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true + launch-editor@2.12.0: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 - '@img/sharp-libvips-darwin-x64@1.2.4': + lefthook-darwin-arm64@1.13.6: optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + lefthook-darwin-x64@1.13.6: optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + lefthook-freebsd-arm64@1.13.6: optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + lefthook-freebsd-x64@1.13.6: optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + lefthook-linux-arm64@1.13.6: optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + lefthook-linux-x64@1.13.6: optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + lefthook-openbsd-arm64@1.13.6: optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + lefthook-openbsd-x64@1.13.6: optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + lefthook-windows-arm64@1.13.6: optional: true - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + lefthook-windows-x64@1.13.6: optional: true - '@img/sharp-linux-arm@0.34.5': + lefthook@1.13.6: optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + lefthook-darwin-arm64: 1.13.6 + lefthook-darwin-x64: 1.13.6 + lefthook-freebsd-arm64: 1.13.6 + lefthook-freebsd-x64: 1.13.6 + lefthook-linux-arm64: 1.13.6 + lefthook-linux-x64: 1.13.6 + lefthook-openbsd-arm64: 1.13.6 + lefthook-openbsd-x64: 1.13.6 + lefthook-windows-arm64: 1.13.6 + lefthook-windows-x64: 1.13.6 + + lightningcss-android-arm64@1.30.2: optional: true - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + lightningcss-darwin-arm64@1.30.2: optional: true - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + lightningcss-darwin-x64@1.30.2: optional: true - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + lightningcss-freebsd-x64@1.30.2: optional: true - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + lightningcss-linux-arm64-gnu@1.30.2: optional: true - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + lightningcss-linux-arm64-musl@1.30.2: optional: true - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.9.1 + lightningcss-linux-x64-gnu@1.30.2: optional: true - '@img/sharp-win32-arm64@0.34.5': + lightningcss-linux-x64-musl@1.30.2: optional: true - '@img/sharp-win32-ia32@0.34.5': + lightningcss-win32-arm64-msvc@1.30.2: optional: true - '@img/sharp-win32-x64@0.34.5': + lightningcss-win32-x64-msvc@1.30.2: optional: true - '@inquirer/external-editor@1.0.3(@types/node@25.3.0)': + lightningcss@1.30.2: dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.1 + detect-libc: 2.1.2 optionalDependencies: - '@types/node': 25.3.0 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lilconfig@3.1.3: {} + + listhen@1.7.2: + dependencies: + '@parcel/watcher': 2.5.1 + '@parcel/watcher-wasm': 2.4.1 + citty: 0.1.6 + clipboardy: 4.0.0 + consola: 3.4.2 + crossws: 0.2.4 + defu: 6.1.4 + get-port-please: 3.2.0 + h3: 1.15.4 + http-shutdown: 1.2.2 + jiti: 1.21.7 + mlly: 1.8.0 + node-forge: 1.3.1 + pathe: 1.1.2 + std-env: 3.10.0 + ufo: 1.6.1 + untun: 0.1.3 + uqr: 0.1.2 + transitivePeerDependencies: + - uWebSockets.js + + listhen@1.9.0: + dependencies: + '@parcel/watcher': 2.5.1 + '@parcel/watcher-wasm': 2.5.1 + citty: 0.1.6 + clipboardy: 4.0.0 + consola: 3.4.2 + crossws: 0.3.5 + defu: 6.1.4 + get-port-please: 3.2.0 + h3: 1.15.4 + http-shutdown: 1.2.2 + jiti: 2.6.1 + mlly: 1.8.0 + node-forge: 1.3.3 + pathe: 1.1.2 + std-env: 3.10.0 + ufo: 1.6.1 + untun: 0.1.3 + uqr: 0.1.2 - '@jridgewell/remapping@2.3.5': + lit-element@4.2.0: dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} + '@lit-labs/ssr-dom-shim': 1.3.0 + '@lit/reactive-element': 2.1.0 + lit-html: 3.3.0 - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': + lit-html@3.3.0: dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@types/trusted-types': 2.0.3 - '@jridgewell/trace-mapping@0.3.9': + lit@3.1.0: dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@lit/reactive-element': 2.1.0 + lit-element: 4.2.0 + lit-html: 3.3.0 - '@manypkg/find-root@1.1.0': - dependencies: - '@babel/runtime': 7.28.4 - '@types/node': 12.20.55 - find-up: 4.1.0 - fs-extra: 8.1.0 + load-tsconfig@0.2.5: {} - '@manypkg/get-packages@1.1.3': + local-pkg@0.5.0: dependencies: - '@babel/runtime': 7.28.4 - '@changesets/types': 4.1.0 - '@manypkg/find-root': 1.1.0 - fs-extra: 8.1.0 - globby: 11.1.0 - read-yaml-file: 1.1.0 - - '@next/env@15.5.14': {} + mlly: 1.8.0 + pkg-types: 1.3.1 - '@next/eslint-plugin-next@15.5.9': + local-pkg@0.5.1: dependencies: - fast-glob: 3.3.1 - - '@next/swc-darwin-arm64@15.5.14': - optional: true - - '@next/swc-darwin-x64@15.5.14': - optional: true - - '@next/swc-linux-arm64-gnu@15.5.14': - optional: true - - '@next/swc-linux-arm64-musl@15.5.14': - optional: true - - '@next/swc-linux-x64-gnu@15.5.14': - optional: true - - '@next/swc-linux-x64-musl@15.5.14': - optional: true - - '@next/swc-win32-arm64-msvc@15.5.14': - optional: true + mlly: 1.8.0 + pkg-types: 1.3.1 - '@next/swc-win32-x64-msvc@15.5.14': - optional: true - - '@noble/ciphers@1.3.0': {} - - '@noble/curves@1.9.1': + local-pkg@1.1.2: dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.4.0': {} - - '@noble/hashes@1.8.0': {} + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 - '@nodelib/fs.scandir@2.1.5': + locate-path@2.0.0: dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + p-locate: 2.0.0 + path-exists: 3.0.0 - '@nodelib/fs.stat@2.0.5': {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 - '@nodelib/fs.walk@1.2.8': + locate-path@6.0.0: dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + p-locate: 5.0.0 - '@rollup/rollup-android-arm-eabi@4.53.4': - optional: true + lodash.defaults@4.2.0: {} - '@rollup/rollup-android-arm64@4.53.4': - optional: true + lodash.isarguments@3.1.0: {} - '@rollup/rollup-darwin-arm64@4.53.4': - optional: true + lodash.memoize@4.1.2: {} - '@rollup/rollup-darwin-x64@4.53.4': - optional: true + lodash.startcase@4.4.0: {} - '@rollup/rollup-freebsd-arm64@4.53.4': - optional: true + lodash.uniq@4.5.0: {} - '@rollup/rollup-freebsd-x64@4.53.4': - optional: true + lodash@4.17.21: {} - '@rollup/rollup-linux-arm-gnueabihf@4.53.4': - optional: true + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 - '@rollup/rollup-linux-arm-musleabihf@4.53.4': - optional: true + lokijs@1.5.12: {} - '@rollup/rollup-linux-arm64-gnu@4.53.4': - optional: true + longest-streak@3.1.0: {} - '@rollup/rollup-linux-arm64-musl@4.53.4': - optional: true + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 - '@rollup/rollup-linux-loong64-gnu@4.53.4': - optional: true + loupe@3.1.3: {} - '@rollup/rollup-linux-ppc64-gnu@4.53.4': - optional: true + lru-cache@10.2.2: {} - '@rollup/rollup-linux-riscv64-gnu@4.53.4': - optional: true + lru-cache@10.4.3: {} - '@rollup/rollup-linux-riscv64-musl@4.53.4': - optional: true + lru-cache@11.2.4: {} - '@rollup/rollup-linux-s390x-gnu@4.53.4': - optional: true + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 - '@rollup/rollup-linux-x64-gnu@4.53.4': - optional: true + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 - '@rollup/rollup-linux-x64-musl@4.53.4': - optional: true + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 - '@rollup/rollup-openharmony-arm64@4.53.4': - optional: true + lru_map@0.3.3: {} - '@rollup/rollup-win32-arm64-msvc@4.53.4': - optional: true + lz-string@1.5.0: {} - '@rollup/rollup-win32-ia32-msvc@4.53.4': - optional: true + magic-regexp@0.10.0: + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + mlly: 1.8.0 + regexp-tree: 0.1.27 + type-level-regexp: 0.1.17 + ufo: 1.6.1 + unplugin: 2.3.11 - '@rollup/rollup-win32-x64-gnu@4.53.4': - optional: true + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 - '@rollup/rollup-win32-x64-msvc@4.53.4': - optional: true + magic-string@0.30.10: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 - '@scure/base@1.2.6': {} + magic-string@0.30.12: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 - '@scure/bip32@1.7.0': + magic-string@0.30.17: dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 + '@jridgewell/sourcemap-codec': 1.5.0 - '@scure/bip39@1.6.0': + magic-string@0.30.21: dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 + '@jridgewell/sourcemap-codec': 1.5.5 - '@standard-schema/spec@1.0.0': {} + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 - '@swc/helpers@0.5.15': + magicast@0.5.1: dependencies: - tslib: 2.8.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 - '@tootallnate/quickjs-emscripten@0.23.0': {} + make-dir@4.0.0: + dependencies: + semver: 7.7.3 - '@tsconfig/node10@1.0.12': {} + make-error@1.3.6: {} - '@tsconfig/node12@1.0.11': {} + mark.js@8.11.1: {} - '@tsconfig/node14@1.0.3': {} + markdown-table@3.0.3: {} - '@tsconfig/node16@1.0.4': {} + marked-terminal@7.1.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.0.0 + chalk: 5.3.0 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.1.3 + supports-hyperlinks: 3.0.0 - '@turbo/darwin-64@2.8.21': - optional: true + marked@9.1.6: {} - '@turbo/darwin-arm64@2.8.21': - optional: true + math-intrinsics@1.1.0: {} - '@turbo/gen@1.13.4(@types/node@25.3.0)(typescript@5.9.3)': + md5.js@1.3.5: dependencies: - '@turbo/workspaces': 1.13.4(@types/node@25.3.0) - chalk: 2.4.2 - commander: 10.0.1 - fs-extra: 10.1.0 - inquirer: 8.2.7(@types/node@25.3.0) - minimatch: 9.0.5 - node-plop: 0.26.3 - proxy-agent: 6.5.0 - ts-node: 10.9.2(@types/node@25.3.0)(typescript@5.9.3) - update-check: 1.5.4 - validate-npm-package-name: 5.0.1 + hash-base: 3.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + mdast-util-find-and-replace@3.0.1: + dependencies: + '@types/mdast': 4.0.3 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.3 + '@types/unist': 3.0.2 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - supports-color - - typescript - - '@turbo/linux-64@2.8.21': - optional: true - '@turbo/linux-arm64@2.8.21': - optional: true + mdast-util-gfm-autolink-literal@2.0.0: + dependencies: + '@types/mdast': 4.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.1 + micromark-util-character: 2.1.0 - '@turbo/windows-64@2.8.21': - optional: true + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.3 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.0 + micromark-util-normalize-identifier: 2.0.0 + transitivePeerDependencies: + - supports-color - '@turbo/windows-arm64@2.8.21': - optional: true + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.3 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color - '@turbo/workspaces@1.13.4(@types/node@25.3.0)': + mdast-util-gfm-table@2.0.0: dependencies: - chalk: 2.4.2 - commander: 10.0.1 - execa: 5.1.1 - fast-glob: 3.3.3 - fs-extra: 10.1.0 - gradient-string: 2.0.2 - inquirer: 8.2.7(@types/node@25.3.0) - js-yaml: 4.1.1 - ora: 4.1.1 - rimraf: 3.0.2 - semver: 7.7.3 - update-check: 1.5.4 + '@types/mdast': 4.0.3 + devlop: 1.1.0 + markdown-table: 3.0.3 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - - '@types/node' + - supports-color - '@types/chai@5.2.3': + mdast-util-gfm-task-list-item@2.0.0: dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 + '@types/mdast': 4.0.3 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color - '@types/deep-eql@4.0.2': {} + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.0 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color - '@types/estree@1.0.8': {} + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.3 + unist-util-is: 6.0.0 - '@types/glob@7.2.0': + mdast-util-to-hast@13.1.0: dependencies: - '@types/minimatch': 6.0.0 - '@types/node': 25.3.0 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.3 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 - '@types/inquirer@6.5.0': + mdast-util-to-hast@13.2.0: dependencies: - '@types/through': 0.0.33 - rxjs: 6.6.7 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.3 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 - '@types/json-schema@7.0.15': {} + mdast-util-to-markdown@2.1.0: + dependencies: + '@types/mdast': 4.0.3 + '@types/unist': 3.0.2 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 - '@types/minimatch@6.0.0': + mdast-util-to-string@4.0.0: dependencies: - minimatch: 9.0.5 + '@types/mdast': 4.0.3 - '@types/node@12.20.55': {} + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + mdn-data@2.12.2: {} + + memorystream@0.3.1: {} - '@types/node@25.3.0': + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micro-ftch@0.3.1: {} + + micro@9.3.5-canary.3: dependencies: - undici-types: 7.18.2 + arg: 4.1.0 + content-type: 1.0.4 + raw-body: 2.4.1 - '@types/react-dom@19.2.3(@types/react@19.2.7)': + micromark-core-commonmark@2.0.1: dependencies: - '@types/react': 19.2.7 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/react@19.2.7': + micromark-factory-destination@2.0.0: dependencies: - csstype: 3.2.3 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/through@0.0.33': + micromark-factory-label@2.0.0: dependencies: - '@types/node': 25.3.0 + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/tinycolor2@1.4.6': {} + micromark-factory-space@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 - '@types/whatwg-mimetype@3.0.2': {} + micromark-factory-title@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/ws@8.18.1': + micromark-factory-whitespace@2.0.0: dependencies: - '@types/node': 25.3.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/yargs-parser@21.0.3': {} + micromark-util-character@2.1.0: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@types/yargs@17.0.35': + micromark-util-chunked@2.0.0: dependencies: - '@types/yargs-parser': 21.0.3 + micromark-util-symbol: 2.0.0 - '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': + micromark-util-classify-character@2.0.0: dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.0 - eslint: 9.39.2 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - '@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3)': + micromark-util-combine-extensions@2.0.0: dependencies: - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@5.5.0) - eslint: 9.39.2 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 - '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': + micromark-util-decode-numeric-character-reference@2.0.1: dependencies: - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 - debug: 4.4.3(supports-color@5.5.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + micromark-util-symbol: 2.0.0 - '@typescript-eslint/scope-manager@8.50.0': + micromark-util-decode-string@2.0.0: dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 - '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': + micromark-util-encode@2.0.0: {} + + micromark-util-html-tag-name@2.0.0: {} + + micromark-util-normalize-identifier@2.0.0: dependencies: - typescript: 5.9.3 + micromark-util-symbol: 2.0.0 - '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': + micromark-util-resolve-all@2.0.0: dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - debug: 4.4.3(supports-color@5.5.0) - eslint: 9.39.2 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + micromark-util-types: 2.0.0 - '@typescript-eslint/types@8.50.0': {} + micromark-util-sanitize-uri@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 - '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': + micromark-util-subtokenize@2.0.1: dependencies: - '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3(supports-color@5.5.0) - minimatch: 9.0.5 - semver: 7.7.3 - tinyglobby: 0.2.15 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-symbol@2.0.0: {} - '@typescript-eslint/utils@8.50.0(eslint@9.39.2)(typescript@5.9.3)': + micromark-util-types@2.0.0: {} + + micromark@4.0.0: dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - eslint: 9.39.2 - typescript: 5.9.3 + '@types/debug': 4.1.7 + debug: 4.4.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.50.0': + micromatch@4.0.5: dependencies: - '@typescript-eslint/types': 8.50.0 - eslint-visitor-keys: 4.2.1 + braces: 3.0.2 + picomatch: 2.3.1 - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9))': + micromatch@4.0.8: dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.18 - ast-v8-to-istanbul: 0.3.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.1 - obug: 2.1.1 - std-env: 3.10.0 - tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.3.0)(happy-dom@20.8.9) + braces: 3.0.3 + picomatch: 2.3.1 - '@vitest/expect@4.0.18': - dependencies: - '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.1 - tinyrainbow: 3.0.3 + mime-db@1.52.0: {} - '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@25.3.0))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.0(@types/node@25.3.0) + mime-db@1.54.0: {} - '@vitest/pretty-format@4.0.18': + mime-types@2.1.35: dependencies: - tinyrainbow: 3.0.3 + mime-db: 1.52.0 - '@vitest/runner@4.0.18': + mime-types@3.0.2: dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 + mime-db: 1.54.0 - '@vitest/snapshot@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 + mime@3.0.0: {} - '@vitest/spy@4.0.18': {} + mime@4.1.0: {} - '@vitest/utils@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 + mimic-fn@2.1.0: {} - abitype@1.1.0(typescript@5.9.3)(zod@4.2.0): - optionalDependencies: - typescript: 5.9.3 - zod: 4.2.0 + mimic-fn@4.0.0: {} - abitype@1.2.2(typescript@5.9.3)(zod@4.2.0): - optionalDependencies: - typescript: 5.9.3 - zod: 4.2.0 + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} - acorn-jsx@5.3.2(acorn@8.15.0): + minimatch@10.1.1: dependencies: - acorn: 8.15.0 + '@isaacs/brace-expansion': 5.0.0 - acorn-walk@8.3.4: + minimatch@3.1.2: dependencies: - acorn: 8.15.0 + brace-expansion: 1.1.11 - acorn@8.15.0: {} + minimatch@5.0.1: + dependencies: + brace-expansion: 2.0.1 - agent-base@7.1.4: {} + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 - aggregate-error@3.1.0: + minimatch@9.0.1: dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 + brace-expansion: 2.0.1 - ajv@6.12.6: + minimatch@9.0.4: dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 + brace-expansion: 2.0.1 - ansi-colors@4.1.3: {} + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 - ansi-escapes@4.3.2: + minimist@1.2.8: {} + + minipass@3.3.6: dependencies: - type-fest: 0.21.3 + yallist: 4.0.0 - ansi-regex@5.0.1: {} + minipass@5.0.0: {} - ansi-regex@6.2.2: {} + minipass@7.1.2: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + minisearch@7.1.0: {} - ansi-styles@4.3.0: + minizlib@2.1.2: dependencies: - color-convert: 2.0.1 + minipass: 3.3.6 + yallist: 4.0.0 - ansi-styles@6.2.3: {} + minizlib@3.0.1: + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 - anymatch@3.1.3: + minizlib@3.1.0: dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 + minipass: 7.1.2 - arg@4.1.3: {} + mipd@0.0.7(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 + mipd@0.0.7(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 - argparse@2.0.1: {} + mitt@3.0.1: {} - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 + mkdirp@1.0.4: {} - array-includes@3.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 + mkdirp@3.0.1: {} - array-union@2.1.0: {} + mlly@1.7.0: + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.3.1 + ufo: 1.6.1 - array.prototype.findlast@1.2.5: + mlly@1.8.0: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 - array.prototype.flat@1.3.3: + mnemonist@0.38.5: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 + obliterator: 2.0.4 - array.prototype.flatmap@1.3.3: + mocha@10.2.0: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + + mocked-exports@0.1.1: {} - array.prototype.tosorted@1.1.4: + motion-dom@12.23.23: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 + motion-utils: 12.23.6 - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 + motion-utils@12.23.6: {} - asn1js@3.0.7: + motion@12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - pvtsutils: 1.3.6 - pvutils: 1.1.5 + framer-motion: 12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: 2.8.1 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - assertion-error@2.0.1: {} + mri@1.2.0: {} - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 + mrmime@2.0.1: {} - ast-v8-to-istanbul@0.3.11: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 10.0.0 + ms@2.1.1: {} - async-function@1.0.0: {} + ms@2.1.2: {} - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 + ms@2.1.3: {} - balanced-match@1.0.2: {} + msw@2.4.9(typescript@5.8.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 3.1.6 + '@mswjs/interceptors': 0.35.9 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.4 + chalk: 4.1.2 + graphql: 16.8.1 + headers-polyfill: 4.0.2 + is-node-process: 1.2.0 + outvariant: 1.4.2 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.18.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + optional: true - balanced-match@4.0.4: {} + msw@2.4.9(typescript@5.9.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 3.1.6 + '@mswjs/interceptors': 0.35.9 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.4 + chalk: 4.1.2 + graphql: 16.8.1 + headers-polyfill: 4.0.2 + is-node-process: 1.2.0 + outvariant: 1.4.2 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.18.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 - base64-js@1.5.1: {} + muggle-string@0.4.1: {} - baseline-browser-mapping@2.9.7: {} + multiformats@9.9.0: {} - basic-ftp@5.0.5: {} + mute-stream@1.0.0: {} - better-path-resolve@1.0.0: + mz@2.7.0: dependencies: - is-windows: 1.0.2 + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 - binary-extensions@2.3.0: {} + nanoid@3.3.11: {} - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 + nanoid@3.3.3: {} - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + nanoid@3.3.7: {} - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 + nanoid@5.1.6: {} - brace-expansion@5.0.3: + nanospinner@1.2.2: dependencies: - balanced-match: 4.0.4 + picocolors: 1.1.1 - braces@3.0.3: - dependencies: - fill-range: 7.1.1 + nanotar@0.2.0: {} - browserslist@4.28.1: + next@15.4.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - baseline-browser-mapping: 2.9.7 - caniuse-lite: 1.0.30001781 - electron-to-chromium: 1.5.267 - node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + '@next/env': 15.4.10 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001761 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.6(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.8 + '@next/swc-darwin-x64': 15.4.8 + '@next/swc-linux-arm64-gnu': 15.4.8 + '@next/swc-linux-arm64-musl': 15.4.8 + '@next/swc-linux-x64-gnu': 15.4.8 + '@next/swc-linux-x64-musl': 15.4.8 + '@next/swc-win32-arm64-msvc': 15.4.8 + '@next/swc-win32-x64-msvc': 15.4.8 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nitropack@2.12.9(@vercel/blob@1.0.2)(encoding@0.1.13)(idb-keyval@6.2.1)(rolldown@1.0.0-beta.35): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.1 + '@rollup/plugin-alias': 5.1.1(rollup@4.54.0) + '@rollup/plugin-commonjs': 28.0.9(rollup@4.54.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.54.0) + '@rollup/plugin-json': 6.1.0(rollup@4.54.0) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.54.0) + '@rollup/plugin-replace': 6.0.3(rollup@4.54.0) + '@rollup/plugin-terser': 0.4.4(rollup@4.54.0) + '@vercel/nft': 0.30.4(encoding@0.1.13)(rollup@4.54.0) + archiver: 7.0.1 + c12: 3.3.3(magicast@0.5.1) + chokidar: 4.0.3 + citty: 0.1.6 + compatx: 0.2.0 + confbox: 0.2.2 + consola: 3.4.2 + cookie-es: 2.0.0 + croner: 9.1.0 + crossws: 0.3.5 + db0: 0.3.4 + defu: 6.1.4 + destr: 2.0.5 + dot-prop: 10.1.0 + esbuild: 0.25.12 + escape-string-regexp: 5.0.0 + etag: 1.8.1 + exsolve: 1.0.8 + globby: 15.0.0 + gzip-size: 7.0.0 + h3: 1.15.4 + hookable: 5.5.3 + httpxy: 0.1.7 + ioredis: 5.8.2 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + listhen: 1.9.0 + magic-string: 0.30.21 + magicast: 0.5.1 + mime: 4.1.0 + mlly: 1.8.0 + node-fetch-native: 1.6.7 + node-mock-http: 1.0.4 + ofetch: 1.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + pretty-bytes: 7.1.0 + radix3: 1.1.2 + rollup: 4.54.0 + rollup-plugin-visualizer: 6.0.5(rolldown@1.0.0-beta.35)(rollup@4.54.0) + scule: 1.3.0 + semver: 7.7.3 + serve-placeholder: 2.0.2 + serve-static: 2.2.1 + source-map: 0.7.6 + std-env: 3.10.0 + ufo: 1.6.1 + ultrahtml: 1.6.0 + uncrypto: 0.1.3 + unctx: 2.5.0 + unenv: 2.0.0-rc.24 + unimport: 5.6.0 + unplugin-utils: 0.3.1 + unstorage: 1.17.3(@vercel/blob@1.0.2)(db0@0.3.4)(idb-keyval@6.2.1)(ioredis@5.8.2) + untyped: 2.0.0 + unwasm: 0.3.11 + youch: 4.1.0-beta.13 + youch-core: 0.3.3 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - bare-abort-controller + - better-sqlite3 + - drizzle-orm + - encoding + - idb-keyval + - mysql2 + - react-native-b4a + - rolldown + - sqlite3 + - supports-color + - uploadthing + + node-addon-api@2.0.2: {} - buffer@5.7.1: + node-addon-api@7.1.1: {} + + node-emoji@2.1.3: dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 - bytestreamjs@2.0.1: {} + node-fetch-native@1.6.4: {} - call-bind-apply-helpers@1.0.2: + node-fetch-native@1.6.7: {} + + node-fetch@2.6.7(encoding@0.1.13): dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 - call-bind@1.0.8: + node-fetch@2.6.9(encoding@0.1.13): dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 - call-bound@1.0.4: + node-fetch@2.7.0(encoding@0.1.13): dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 - callsites@3.1.0: {} + node-forge@1.3.1: {} - camel-case@3.0.0: - dependencies: - no-case: 2.3.2 - upper-case: 1.1.3 + node-forge@1.3.3: {} - caniuse-lite@1.0.30001781: {} + node-gyp-build@4.6.0: {} - cbor2@1.12.0: {} + node-gyp-build@4.8.4: {} - chai@6.2.1: {} + node-mock-http@1.0.4: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + node-releases@2.0.14: {} + + node-releases@2.0.27: {} - chalk@3.0.0: + nopt@7.2.1: dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + abbrev: 2.0.0 - chalk@4.1.2: + nopt@8.1.0: dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + abbrev: 3.0.1 - change-case@3.1.0: - dependencies: - camel-case: 3.0.0 - constant-case: 2.0.0 - dot-case: 2.1.1 - header-case: 1.0.1 - is-lower-case: 1.1.3 - is-upper-case: 1.1.2 - lower-case: 1.1.4 - lower-case-first: 1.0.2 - no-case: 2.3.2 - param-case: 2.1.1 - pascal-case: 2.0.1 - path-case: 2.1.1 - sentence-case: 2.1.1 - snake-case: 2.1.0 - swap-case: 1.1.2 - title-case: 2.1.1 - upper-case: 1.1.3 - upper-case-first: 1.1.2 + normalize-path@3.0.0: {} - chardet@0.7.0: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 - chardet@2.1.1: {} + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nuxt@3.19.0(@biomejs/biome@1.9.4)(@parcel/watcher@2.5.1)(@types/node@24.0.1)(@vercel/blob@1.0.2)(@vue/compiler-sfc@3.5.26)(bufferutil@4.1.0)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.1)(ioredis@5.8.2)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(yaml@2.8.2): + dependencies: + '@nuxt/cli': 3.31.3(cac@6.7.14)(magicast@0.5.1) + '@nuxt/devalue': 2.0.2 + '@nuxt/devtools': 2.7.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@nuxt/kit': 3.19.0(magicast@0.5.1) + '@nuxt/schema': 3.19.0 + '@nuxt/telemetry': 2.6.6(magicast@0.5.1) + '@nuxt/vite-builder': 3.19.0(@biomejs/biome@1.9.4)(@types/node@24.0.1)(lightningcss@1.30.2)(magicast@0.5.1)(rolldown@1.0.0-beta.35)(rollup@4.54.0)(terser@5.44.1)(tsx@4.19.2)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2) + '@unhead/vue': 2.1.1(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + c12: 3.3.3(magicast@0.5.1) + chokidar: 4.0.3 + compatx: 0.2.0 + consola: 3.4.2 + cookie-es: 2.0.0 + defu: 6.1.4 + destr: 2.0.5 + devalue: 5.6.1 + errx: 0.1.0 + esbuild: 0.25.12 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + exsolve: 1.0.8 + h3: 1.15.4 + hookable: 5.5.3 + ignore: 7.0.5 + impound: 1.0.0 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + mocked-exports: 0.1.1 + nanotar: 0.2.0 + nitropack: 2.12.9(@vercel/blob@1.0.2)(encoding@0.1.13)(idb-keyval@6.2.1)(rolldown@1.0.0-beta.35) + nypm: 0.6.2 + ofetch: 1.5.1 + ohash: 2.0.11 + on-change: 5.0.1 + oxc-minify: 0.86.0 + oxc-parser: 0.86.0 + oxc-transform: 0.86.0 + oxc-walker: 0.4.0(oxc-parser@0.86.0) + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + radix3: 1.1.2 + scule: 1.3.0 + semver: 7.7.3 + std-env: 3.10.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.14 + ufo: 1.6.1 + ultrahtml: 1.6.0 + uncrypto: 0.1.3 + unctx: 2.5.0 + unimport: 5.6.0 + unplugin: 2.3.11 + unplugin-vue-router: 0.15.0(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)) + unstorage: 1.17.3(@vercel/blob@1.0.2)(db0@0.3.4)(idb-keyval@6.2.1)(ioredis@5.8.2) + untyped: 2.0.0 + vue: 3.5.26(typescript@5.9.3) + vue-bundle-renderer: 2.2.0 + vue-devtools-stub: 0.1.0 + vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) + optionalDependencies: + '@parcel/watcher': 2.5.1 + '@types/node': 24.0.1 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@biomejs/biome' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vue/compiler-sfc' + - aws4fetch + - bare-abort-controller + - better-sqlite3 + - bufferutil + - cac + - commander + - db0 + - drizzle-orm + - encoding + - eslint + - idb-keyval + - ioredis + - less + - lightningcss + - magicast + - meow + - mysql2 + - optionator + - react-native-b4a + - rolldown + - rollup + - sass + - sass-embedded + - sqlite3 + - stylelint + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - utf-8-validate + - vite + - vls + - vti + - vue-tsc + - xml2js + - yaml - chokidar@3.6.0: + nypm@0.6.2: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.2 - ci-info@3.9.0: {} + obj-multiplex@1.0.0: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + readable-stream: 2.3.8 - clean-stack@2.2.0: {} + object-assign@4.1.1: {} + + obliterator@2.0.4: {} - cli-cursor@3.1.0: + ofetch@1.5.1: dependencies: - restore-cursor: 3.1.0 + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.1 - cli-spinners@2.9.2: {} + ohash@2.0.11: {} - cli-width@3.0.0: {} + on-change@5.0.1: {} - client-only@0.0.1: {} + on-exit-leak-free@0.2.0: {} - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + on-exit-leak-free@2.1.2: {} - cliui@9.0.1: + on-finished@2.4.1: dependencies: - string-width: 7.2.0 - strip-ansi: 7.1.2 - wrap-ansi: 9.0.2 + ee-first: 1.1.1 - clone@1.0.4: {} - - color-convert@1.9.3: + once@1.3.3: dependencies: - color-name: 1.1.3 + wrappy: 1.0.2 - color-convert@2.0.1: + once@1.4.0: dependencies: - color-name: 1.1.4 + wrappy: 1.0.2 - color-name@1.1.3: {} + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 - color-name@1.1.4: {} + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 - commander@10.0.1: {} + oniguruma-to-js@0.4.3: + dependencies: + regex: 4.4.0 - concat-map@0.0.1: {} + open@10.2.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 - concurrently@9.2.1: + open@8.4.2: dependencies: - chalk: 4.1.2 - rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 - constant-case@2.0.0: + openapi-fetch@0.13.8: dependencies: - snake-case: 2.1.0 - upper-case: 1.1.3 + openapi-typescript-helpers: 0.0.15 - convert-source-map@2.0.0: {} + openapi-typescript-helpers@0.0.15: {} - core-js-pure@3.47.0: {} + opener@1.5.2: {} - create-require@1.1.1: {} + os-paths@4.4.0: {} - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + os-tmpdir@1.0.2: {} - csstype@3.2.3: {} + outdent@0.5.0: {} + + outvariant@1.4.2: {} - data-uri-to-buffer@6.0.2: {} + outvariant@1.4.3: {} - data-view-buffer@1.0.2: + ox@0.11.1(typescript@5.8.3)(zod@3.25.20): dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.8.3)(zod@3.25.20) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - zod - data-view-byte-length@1.0.2: + ox@0.11.1(typescript@5.9.3)(zod@3.22.4): dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@3.22.4) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod - data-view-byte-offset@1.0.1: + ox@0.11.1(typescript@5.9.3)(zod@3.25.20): dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.20) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - zod - debug@4.4.3(supports-color@5.5.0): + ox@0.6.7(typescript@5.9.3)(zod@3.25.20): dependencies: - ms: 2.1.3 + '@adraffy/ens-normalize': 1.11.1 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + abitype: 1.0.8(typescript@5.9.3)(zod@3.25.20) + eventemitter3: 5.0.1 optionalDependencies: - supports-color: 5.5.0 + typescript: 5.9.3 + transitivePeerDependencies: + - zod + + ox@0.7.2(typescript@5.8.3)(zod@3.25.20): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.8.3)(zod@3.25.20) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - zod + + ox@0.8.1(typescript@5.8.3)(zod@3.25.20): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.8.3)(zod@3.25.20) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - zod + + oxc-minify@0.86.0: + optionalDependencies: + '@oxc-minify/binding-android-arm64': 0.86.0 + '@oxc-minify/binding-darwin-arm64': 0.86.0 + '@oxc-minify/binding-darwin-x64': 0.86.0 + '@oxc-minify/binding-freebsd-x64': 0.86.0 + '@oxc-minify/binding-linux-arm-gnueabihf': 0.86.0 + '@oxc-minify/binding-linux-arm-musleabihf': 0.86.0 + '@oxc-minify/binding-linux-arm64-gnu': 0.86.0 + '@oxc-minify/binding-linux-arm64-musl': 0.86.0 + '@oxc-minify/binding-linux-riscv64-gnu': 0.86.0 + '@oxc-minify/binding-linux-s390x-gnu': 0.86.0 + '@oxc-minify/binding-linux-x64-gnu': 0.86.0 + '@oxc-minify/binding-linux-x64-musl': 0.86.0 + '@oxc-minify/binding-wasm32-wasi': 0.86.0 + '@oxc-minify/binding-win32-arm64-msvc': 0.86.0 + '@oxc-minify/binding-win32-x64-msvc': 0.86.0 + + oxc-parser@0.86.0: + dependencies: + '@oxc-project/types': 0.86.0 + optionalDependencies: + '@oxc-parser/binding-android-arm64': 0.86.0 + '@oxc-parser/binding-darwin-arm64': 0.86.0 + '@oxc-parser/binding-darwin-x64': 0.86.0 + '@oxc-parser/binding-freebsd-x64': 0.86.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.86.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.86.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.86.0 + '@oxc-parser/binding-linux-arm64-musl': 0.86.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.86.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.86.0 + '@oxc-parser/binding-linux-x64-gnu': 0.86.0 + '@oxc-parser/binding-linux-x64-musl': 0.86.0 + '@oxc-parser/binding-wasm32-wasi': 0.86.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.86.0 + '@oxc-parser/binding-win32-x64-msvc': 0.86.0 + + oxc-transform@0.86.0: + optionalDependencies: + '@oxc-transform/binding-android-arm64': 0.86.0 + '@oxc-transform/binding-darwin-arm64': 0.86.0 + '@oxc-transform/binding-darwin-x64': 0.86.0 + '@oxc-transform/binding-freebsd-x64': 0.86.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.86.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.86.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.86.0 + '@oxc-transform/binding-linux-arm64-musl': 0.86.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.86.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.86.0 + '@oxc-transform/binding-linux-x64-gnu': 0.86.0 + '@oxc-transform/binding-linux-x64-musl': 0.86.0 + '@oxc-transform/binding-wasm32-wasi': 0.86.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.86.0 + '@oxc-transform/binding-win32-x64-msvc': 0.86.0 + + oxc-walker@0.4.0(oxc-parser@0.86.0): + dependencies: + estree-walker: 3.0.3 + magic-regexp: 0.10.0 + oxc-parser: 0.86.0 - deep-extend@0.6.0: {} + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 - deep-is@0.1.4: {} + p-finally@2.0.1: {} - defaults@1.0.4: + p-limit@1.3.0: dependencies: - clone: 1.0.4 + p-try: 1.0.0 - define-data-property@1.1.4: + p-limit@2.3.0: dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 + p-try: 2.2.0 - define-properties@1.2.1: + p-limit@3.1.0: dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 + yocto-queue: 0.1.0 - degenerator@5.0.1: + p-locate@2.0.0: dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 + p-limit: 1.3.0 - del@5.1.0: + p-locate@4.1.0: dependencies: - globby: 10.0.2 - graceful-fs: 4.2.11 - is-glob: 4.0.3 - is-path-cwd: 2.2.0 - is-path-inside: 3.0.3 - p-map: 3.0.0 - rimraf: 3.0.2 - slash: 3.0.0 - - detect-indent@6.1.0: {} + p-limit: 2.3.0 - detect-libc@2.1.2: - optional: true + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 - diff@4.0.2: {} + p-map@2.1.0: {} - dir-glob@3.0.1: + p-map@4.0.0: dependencies: - path-type: 4.0.0 + aggregate-error: 3.1.0 - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 + p-try@1.0.0: {} - dot-case@2.1.1: - dependencies: - no-case: 2.3.2 + p-try@2.2.0: {} - dotenv@16.0.3: {} + package-json-from-dist@1.0.1: {} - dotenv@17.3.1: {} + package-manager-detector@0.2.0: {} - dunder-proto@1.0.1: + package-manager-detector@0.2.11: dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 + quansync: 0.2.11 - electron-to-chromium@1.5.267: {} + package-manager-detector@1.3.0: {} - emoji-regex@10.6.0: {} + package-manager-detector@1.6.0: {} - emoji-regex@8.0.0: {} + parse-ms@2.1.0: {} - enquirer@2.4.1: + parse-ms@4.0.0: {} + + parse-path@7.1.0: dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 + protocols: 2.0.2 - entities@7.0.1: {} + parse-url@9.2.0: + dependencies: + '@types/parse-path': 7.1.0 + parse-path: 7.1.0 - es-abstract@1.24.1: + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 + parse5: 6.0.1 - es-define-property@1.0.1: {} + parse5@5.1.1: {} - es-errors@1.3.0: {} + parse5@6.0.1: {} - es-iterator-helpers@1.2.2: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 + parseurl@1.3.3: {} - es-module-lexer@1.7.0: {} + path-browserify@1.0.1: {} - es-object-atoms@1.1.1: + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-match@1.2.4: dependencies: - es-errors: 1.3.0 + http-errors: 1.4.0 + path-to-regexp: 1.9.0 + + path-parse@1.0.7: {} - es-set-tostringtag@2.1.0: + path-scurry@1.11.1: dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 + lru-cache: 10.4.3 + minipass: 7.1.2 - es-shim-unscopables@1.1.0: + path-scurry@2.0.1: dependencies: - hasown: 2.0.2 + lru-cache: 11.2.4 + minipass: 7.1.2 - es-to-primitive@1.3.0: + path-to-regexp@1.9.0: dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 + isarray: 0.0.1 - escalade@3.2.0: {} + path-to-regexp@6.1.0: {} - escape-string-regexp@1.0.5: {} + path-to-regexp@6.3.0: {} - escape-string-regexp@4.0.0: {} + path-to-regexp@8.3.0: {} - escodegen@2.1.0: + path-type@3.0.0: dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 + pify: 3.0.0 - eslint-config-prettier@10.1.8(eslint@9.39.2): - dependencies: - eslint: 9.39.2 + path-type@4.0.0: {} - eslint-plugin-only-warn@1.1.0: {} + path-type@6.0.0: {} - eslint-plugin-react-hooks@7.0.1(eslint@9.39.2): - dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 - eslint: 9.39.2 - hermes-parser: 0.25.1 - zod: 4.2.0 - zod-validation-error: 4.0.2(zod@4.2.0) - transitivePeerDependencies: - - supports-color + pathe@1.1.2: {} - eslint-plugin-react@7.37.5(eslint@9.39.2): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.2 - eslint: 9.39.2 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 + pathe@2.0.3: {} - eslint-plugin-turbo@2.6.3(eslint@9.39.2)(turbo@2.8.21): - dependencies: - dotenv: 16.0.3 - eslint: 9.39.2 - turbo: 2.8.21 + pathval@2.0.0: {} - eslint-scope@8.4.0: + pbkdf2@3.1.2: dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 + create-hash: 1.2.0 + create-hmac: 1.1.7 + ripemd160: 2.0.2 + safe-buffer: 5.2.1 + sha.js: 2.4.11 - eslint-visitor-keys@3.4.3: {} + pend@1.2.0: {} - eslint-visitor-keys@4.2.1: {} + perfect-debounce@1.0.0: {} - eslint@9.39.2: - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) - '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 - '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@5.5.0) - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color + perfect-debounce@2.0.0: {} - espree@10.4.0: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 + picocolors@1.0.0: {} - esprima@4.0.1: {} + picocolors@1.1.0: {} - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 + picocolors@1.1.1: {} - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 + picomatch@2.3.1: {} - estraverse@5.3.0: {} + picomatch@4.0.2: {} - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 + picomatch@4.0.3: {} - esutils@2.0.3: {} + pify@3.0.0: {} - eventemitter3@5.0.1: {} + pify@4.0.1: {} - execa@5.1.1: + pify@5.0.0: {} + + pino-abstract-transport@0.5.0: dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + duplexify: 4.1.2 + split2: 4.2.0 - expect-type@1.3.0: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 - extendable-error@0.1.7: {} + pino-pretty@13.0.0: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.2 + secure-json-parse: 2.7.0 + sonic-boom: 4.2.0 + strip-json-comments: 3.1.1 - external-editor@3.1.0: + pino-std-serializers@4.0.0: {} + + pino@7.11.0: dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 0.2.0 + pino-abstract-transport: 0.5.0 + pino-std-serializers: 4.0.0 + process-warning: 1.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.1.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 2.8.0 + thread-stream: 0.15.2 - fake-indexeddb@6.2.5: {} + pkg-types@1.1.0: + dependencies: + confbox: 0.1.7 + mlly: 1.8.0 + pathe: 1.1.2 - fast-deep-equal@3.1.3: {} + pkg-types@1.1.1: + dependencies: + confbox: 0.1.7 + mlly: 1.7.0 + pathe: 1.1.2 - fast-glob@3.3.1: + pkg-types@1.3.1: dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 - fast-glob@3.3.3: + pkg-types@2.3.0: dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 - fast-json-stable-stringify@2.1.0: {} + pngjs@5.0.0: {} - fast-levenshtein@2.0.6: {} + pony-cause@2.1.11: {} + + possible-typed-array-names@1.1.0: {} - fastq@1.19.1: + postcss-calc@10.1.1(postcss@8.5.6): dependencies: - reusify: 1.1.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 + postcss-colormin@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - figures@3.2.0: + postcss-convert-values@7.0.8(postcss@8.5.6): dependencies: - escape-string-regexp: 1.0.5 + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - file-entry-cache@8.0.0: + postcss-discard-comments@7.0.5(postcss@8.5.6): dependencies: - flat-cache: 4.0.1 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - fill-range@7.1.1: + postcss-discard-duplicates@7.0.2(postcss@8.5.6): dependencies: - to-regex-range: 5.0.1 + postcss: 8.5.6 - find-up@4.1.0: + postcss-discard-empty@7.0.1(postcss@8.5.6): dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + postcss: 8.5.6 - find-up@5.0.0: + postcss-discard-overridden@7.0.1(postcss@8.5.6): dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 + postcss: 8.5.6 - flat-cache@4.0.1: + postcss-merge-longhand@7.0.5(postcss@8.5.6): dependencies: - flatted: 3.3.3 - keyv: 4.5.4 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + stylehacks: 7.0.7(postcss@8.5.6) - flatted@3.3.3: {} + postcss-merge-rules@7.0.7(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - for-each@0.3.5: + postcss-minify-font-values@7.0.1(postcss@8.5.6): dependencies: - is-callable: 1.2.7 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - fs-extra@10.1.0: + postcss-minify-gradients@7.0.1(postcss@8.5.6): dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 + colord: 2.9.3 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - fs-extra@7.0.1: + postcss-minify-params@7.0.5(postcss@8.5.6): dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 + browserslist: 4.28.1 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - fs-extra@8.1.0: + postcss-minify-selectors@7.0.5(postcss@8.5.6): dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 + cssesc: 3.0.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - fs.realpath@1.0.0: {} + postcss-normalize-charset@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 - fsevents@2.3.3: - optional: true + postcss-normalize-display-values@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - function-bind@1.1.2: {} + postcss-normalize-positions@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - function.prototype.name@1.1.8: + postcss-normalize-repeat-style@7.0.1(postcss@8.5.6): dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - functions-have-names@1.2.3: {} + postcss-normalize-string@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - generator-function@2.0.1: {} + postcss-normalize-timing-functions@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - gensync@1.0.0-beta.2: {} + postcss-normalize-unicode@7.0.5(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - get-caller-file@2.0.5: {} + postcss-normalize-url@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - get-east-asian-width@1.4.0: {} + postcss-normalize-whitespace@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - get-intrinsic@1.3.0: + postcss-ordered-values@7.0.2(postcss@8.5.6): dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 + cssnano-utils: 5.0.1(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - get-proto@1.0.1: + postcss-reduce-initial@7.0.5(postcss@8.5.6): dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + browserslist: 4.28.1 + caniuse-api: 3.0.0 + postcss: 8.5.6 - get-stream@6.0.1: {} + postcss-reduce-transforms@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 - get-symbol-description@1.1.0: + postcss-selector-parser@7.1.1: dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 + cssesc: 3.0.0 + util-deprecate: 1.0.2 - get-uri@6.0.5: + postcss-svgo@7.1.0(postcss@8.5.6): dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + svgo: 4.0.0 - glob-parent@5.1.2: + postcss-unique-selectors@7.0.4(postcss@8.5.6): dependencies: - is-glob: 4.0.3 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - glob-parent@6.0.2: + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: dependencies: - is-glob: 4.0.3 + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 - glob@13.0.6: + postcss@8.4.47: dependencies: - minimatch: 10.2.2 - minipass: 7.1.3 - path-scurry: 2.0.2 + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 - glob@7.2.3: + postcss@8.5.6: dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 - globals@14.0.0: {} + preact@10.17.1: {} - globals@16.5.0: {} + preact@10.24.3: {} - globalthis@1.0.4: + prettier-plugin-solidity@2.2.1(prettier@3.7.4): dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 + '@nomicfoundation/slang': 1.3.1 + '@solidity-parser/parser': 0.20.2 + prettier: 3.7.4 + semver: 7.7.3 - globby@10.0.2: - dependencies: - '@types/glob': 7.2.0 - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - glob: 7.2.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 + prettier@2.8.8: {} - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 + prettier@3.0.3: {} - gopd@1.2.0: {} + prettier@3.7.4: {} - graceful-fs@4.2.11: {} + pretty-bytes@7.1.0: {} - gradient-string@2.0.2: + pretty-format@27.5.1: dependencies: - chalk: 4.1.2 - tinygradient: 1.1.5 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 - handlebars@4.7.8: + pretty-ms@7.0.1: dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 + parse-ms: 2.1.0 - happy-dom@20.8.9: + pretty-ms@9.0.0: dependencies: - '@types/node': 25.3.0 - '@types/whatwg-mimetype': 3.0.2 - '@types/ws': 8.18.1 - entities: 7.0.1 - whatwg-mimetype: 3.0.0 - ws: 8.20.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate + parse-ms: 4.0.0 - has-bigints@1.1.0: {} + process-nextick-args@2.0.1: {} - has-flag@3.0.0: {} + process-warning@1.0.0: {} - has-flag@4.0.0: {} + process@0.11.10: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 + promisepipe@3.0.0: {} - has-proto@1.2.0: + prompts@2.4.2: dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} + kleur: 3.0.3 + sisteransi: 1.0.5 - has-tostringtag@1.0.2: + prool@0.0.23: dependencies: - has-symbols: 1.1.0 + change-case: 5.4.4 + eventemitter3: 5.0.1 + execa: 9.1.0 + get-port: 7.1.0 + http-proxy: 1.18.1 + tar: 7.2.0 + transitivePeerDependencies: + - debug - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 + property-information@6.5.0: {} - header-case@1.0.1: - dependencies: - no-case: 2.3.2 - upper-case: 1.1.3 + proto-list@1.2.4: {} - hermes-estree@0.25.1: {} + protocols@2.0.2: {} - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 + proxy-compare@2.6.0: {} - html-escaper@2.0.2: {} + pseudomap@1.0.2: {} + + psl@1.9.0: {} - http-proxy-agent@7.0.2: + publint@0.3.12: dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color + '@publint/pack': 0.1.2 + package-manager-detector: 1.3.0 + picocolors: 1.1.1 + sade: 1.8.1 - https-proxy-agent@7.0.6: + pump@3.0.2: dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color + end-of-stream: 1.4.4 + once: 1.4.0 - human-id@4.1.3: {} + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 - human-signals@2.1.0: {} + punycode@2.3.1: {} - iconv-lite@0.4.24: + qrcode@1.5.3: dependencies: - safer-buffer: 2.1.2 + dijkstrajs: 1.0.2 + encode-utf8: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 - iconv-lite@0.7.1: - dependencies: - safer-buffer: 2.1.2 + quansync@0.2.11: {} - idb@8.0.3: {} + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 - ieee754@1.2.1: {} + querystringify@2.2.0: {} - ignore-by-default@1.0.1: {} + queue-microtask@1.2.3: {} - ignore@5.3.2: {} + quick-format-unescaped@4.0.4: {} - ignore@7.0.5: {} + radix3@1.1.2: {} - import-fresh@3.3.1: + randombytes@2.1.0: dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} + safe-buffer: 5.2.1 - indent-string@4.0.0: {} + range-parser@1.2.1: {} - inflight@1.0.6: + raw-body@2.4.1: dependencies: - once: 1.4.0 - wrappy: 1.0.2 + bytes: 3.1.0 + http-errors: 1.7.3 + iconv-lite: 0.4.24 + unpipe: 1.0.0 - inherits@2.0.4: {} + raw-body@2.5.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 - ini@1.3.8: {} + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 - inquirer@7.3.3: + react-apple-signin-auth@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - run-async: 2.4.1 - rxjs: 6.6.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - inquirer@8.2.7(@types/node@25.3.0): + react-dom@18.3.1(react@18.3.1): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@25.3.0) - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - ora: 5.4.1 - run-async: 2.4.1 - rxjs: 7.8.2 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - wrap-ansi: 6.2.0 - transitivePeerDependencies: - - '@types/node' + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 - internal-slot@1.1.0: + react-hook-form@7.69.0(react@18.3.1): dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + react: 18.3.1 - ip-address@10.1.0: {} + react-is@17.0.2: {} - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 + react-refresh@0.14.0: {} - is-async-function@2.1.1: + react-remove-scroll-bar@2.3.8(@types/react@18.3.27)(react@18.3.1): dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 - is-bigint@1.1.0: + react-remove-scroll@2.7.2(@types/react@18.3.27)(react@18.3.1): dependencies: - has-bigints: 1.1.0 + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.27)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 - is-binary-path@2.1.0: + react-style-singleton@2.2.3(@types/react@18.3.27)(react@18.3.1): dependencies: - binary-extensions: 2.3.0 + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 - is-boolean-object@1.2.2: + react@18.3.1: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + loose-envify: 1.4.0 - is-callable@1.2.7: {} + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 - is-core-module@2.16.1: + readable-stream@2.3.8: dependencies: - hasown: 2.0.2 + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 - is-data-view@1.0.2: + readable-stream@3.6.2: dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 - is-date-object@1.1.0: + readable-stream@4.7.0: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 - is-extglob@2.1.1: {} + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 - is-finalizationregistry@1.1.1: + readdirp@3.6.0: dependencies: - call-bound: 1.0.4 + picomatch: 2.3.1 - is-fullwidth-code-point@3.0.0: {} + readdirp@4.1.2: {} - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 + readdirp@5.0.0: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 + real-require@0.1.0: {} - is-interactive@1.0.0: {} + redis-errors@1.2.0: {} - is-lower-case@1.1.3: + redis-parser@3.0.0: dependencies: - lower-case: 1.1.4 + redis-errors: 1.2.0 - is-map@2.0.3: {} + regenerator-runtime@0.14.0: {} - is-negative-zero@2.0.3: {} + regex@4.4.0: {} - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + regexp-tree@0.1.27: {} - is-number@7.0.0: {} + remove-accents@0.5.0: {} - is-path-cwd@2.2.0: {} + require-directory@2.1.1: {} - is-path-inside@3.0.3: {} + require-from-string@2.0.2: {} - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 + require-main-filename@2.0.0: {} - is-set@2.0.3: {} + requires-port@1.0.0: {} - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 + resolve-from@5.0.0: {} - is-stream@2.0.1: {} + resolve-pkg-maps@1.0.0: {} - is-string@1.1.1: + resolve@1.17.0: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + path-parse: 1.0.7 - is-subdir@1.2.0: + resolve@1.22.11: dependencies: - better-path-resolve: 1.0.0 + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 - is-symbol@1.1.1: + retry@0.13.1: {} + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + rimraf@2.7.1: dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 + glob: 7.2.0 - is-typed-array@1.1.15: + rimraf@5.0.10: dependencies: - which-typed-array: 1.1.19 + glob: 10.5.0 - is-unicode-supported@0.1.0: {} + rimraf@6.1.2: + dependencies: + glob: 13.0.0 + package-json-from-dist: 1.0.1 - is-upper-case@1.1.2: + ripemd160@2.0.2: dependencies: - upper-case: 1.1.3 + hash-base: 3.1.0 + inherits: 2.0.4 - is-weakmap@2.0.2: {} + rlp@2.2.7: + dependencies: + bn.js: 5.2.2 - is-weakref@1.1.1: + rolldown@1.0.0-beta.35: dependencies: - call-bound: 1.0.4 + '@oxc-project/runtime': 0.82.3 + '@oxc-project/types': 0.82.3 + '@rolldown/pluginutils': 1.0.0-beta.35 + ansis: 4.2.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.35 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.35 + '@rolldown/binding-darwin-x64': 1.0.0-beta.35 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.35 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.35 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.35 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.35 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.35 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.35 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.35 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.35 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.35 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.35 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.35 + + rollup-plugin-visualizer@6.0.5(rolldown@1.0.0-beta.35)(rollup@4.54.0): + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 17.7.2 + optionalDependencies: + rolldown: 1.0.0-beta.35 + rollup: 4.54.0 - is-weakset@2.0.4: + rollup@4.54.0: dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 - is-windows@1.0.2: {} + run-applescript@7.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 - isarray@2.0.5: {} + sade@1.8.1: + dependencies: + mri: 1.2.0 - isbinaryfile@4.0.10: {} + safe-buffer@5.1.2: {} - isexe@2.0.0: {} + safe-buffer@5.2.1: {} - isows@1.0.7(ws@8.18.3): + safe-regex-test@1.1.0: dependencies: - ws: 8.18.3 + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 - istanbul-lib-coverage@3.2.2: {} + safe-stable-stringify@2.4.3: {} - istanbul-lib-report@3.0.1: + safer-buffer@2.1.2: {} + + sax@1.4.3: {} + + scheduler@0.23.2: dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 + loose-envify: 1.4.0 - istanbul-reports@3.2.0: + scrypt-js@3.0.1: {} + + scule@1.3.0: {} + + secp256k1@4.0.3: dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 + elliptic: 6.6.1 + node-addon-api: 2.0.2 + node-gyp-build: 4.8.4 + + secure-json-parse@2.7.0: {} + + semver@5.7.1: {} - iterator.prototype@1.1.5: + semver@6.3.1: {} + + semver@7.5.4: dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 + lru-cache: 6.0.0 - js-tokens@10.0.0: {} + semver@7.6.2: {} - js-tokens@4.0.0: {} + semver@7.7.3: {} - js-yaml@3.14.2: + send@1.2.1: dependencies: - argparse: 1.0.10 - esprima: 4.0.1 + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color - js-yaml@4.1.1: + serialize-javascript@6.0.0: dependencies: - argparse: 2.0.1 + randombytes: 2.1.0 - jsesc@3.1.0: {} + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 - json-buffer@3.0.1: {} + serve-placeholder@2.0.2: + dependencies: + defu: 6.1.4 - json-canonicalize@2.0.0: {} + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color - json-schema-traverse@0.4.1: {} + set-blocking@2.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 - json5@2.2.3: {} + setimmediate@1.0.5: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 + setprototypeof@1.1.1: {} - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 + setprototypeof@1.2.0: {} - jsx-ast-utils@3.3.5: + sha.js@2.4.11: dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 + inherits: 2.0.4 + safe-buffer: 5.2.1 - jwt-decode@4.0.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true - keyv@4.5.4: + shebang-command@1.2.0: dependencies: - json-buffer: 3.0.1 + shebang-regex: 1.0.0 - lefthook-darwin-arm64@2.1.1: - optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 - lefthook-darwin-x64@2.1.1: - optional: true + shebang-regex@1.0.0: {} - lefthook-freebsd-arm64@2.1.1: - optional: true + shebang-regex@3.0.0: {} - lefthook-freebsd-x64@2.1.1: - optional: true + shell-quote@1.8.3: {} - lefthook-linux-arm64@2.1.1: + sherif-darwin-arm64@1.0.0: optional: true - lefthook-linux-x64@2.1.1: + sherif-darwin-x64@1.0.0: optional: true - lefthook-openbsd-arm64@2.1.1: + sherif-linux-arm64@1.0.0: optional: true - lefthook-openbsd-x64@2.1.1: + sherif-linux-x64@1.0.0: optional: true - lefthook-windows-arm64@2.1.1: + sherif-windows-arm64@1.0.0: optional: true - lefthook-windows-x64@2.1.1: + sherif-windows-x64@1.0.0: optional: true - lefthook@2.1.1: + sherif@1.0.0: optionalDependencies: - lefthook-darwin-arm64: 2.1.1 - lefthook-darwin-x64: 2.1.1 - lefthook-freebsd-arm64: 2.1.1 - lefthook-freebsd-x64: 2.1.1 - lefthook-linux-arm64: 2.1.1 - lefthook-linux-x64: 2.1.1 - lefthook-openbsd-arm64: 2.1.1 - lefthook-openbsd-x64: 2.1.1 - lefthook-windows-arm64: 2.1.1 - lefthook-windows-x64: 2.1.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 + sherif-darwin-arm64: 1.0.0 + sherif-darwin-x64: 1.0.0 + sherif-linux-arm64: 1.0.0 + sherif-linux-x64: 1.0.0 + sherif-windows-arm64: 1.0.0 + sherif-windows-x64: 1.0.0 + + shiki@1.22.2: + dependencies: + '@shikijs/core': 1.22.2 + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 + siginfo@2.0.0: {} - lodash.get@4.4.2: {} + signal-exit@3.0.7: {} - lodash.merge@4.6.2: {} + signal-exit@4.0.2: {} - lodash.startcase@4.4.0: {} + signal-exit@4.1.0: {} - lodash@4.17.21: {} + simple-git-hooks@2.11.1: {} - log-symbols@3.0.0: + simple-git@3.30.0: dependencies: - chalk: 2.4.2 + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - log-symbols@4.1.0: + sirv@2.0.4: dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 - loose-envify@1.4.0: + sirv@3.0.2: dependencies: - js-tokens: 4.0.0 + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sisteransi@1.0.5: {} - lower-case-first@1.0.2: + skin-tone@2.0.0: dependencies: - lower-case: 1.1.4 + unicode-emoji-modifier-base: 1.0.0 - lower-case@1.1.4: {} + slash@1.0.0: {} - lru-cache@11.2.4: {} + slash@3.0.0: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 + slash@5.1.0: {} - lru-cache@7.18.3: {} + smob@1.5.0: {} - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + smol-toml@1.3.0: {} - magicast@0.5.1: + socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - source-map-js: 1.2.1 + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + engine.io-client: 6.6.4(bufferutil@4.1.0)(utf-8-validate@5.0.10) + socket.io-parser: 4.2.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate - make-dir@4.0.0: + socket.io-parser@4.2.5: dependencies: - semver: 7.7.4 - - make-error@1.3.6: {} - - math-intrinsics@1.1.0: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} + '@socket.io/component-emitter': 3.1.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - micromatch@4.0.8: + solc@0.7.3(debug@4.3.4): dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mimic-fn@2.1.0: {} + command-exists: 1.2.9 + commander: 3.0.2 + follow-redirects: 1.15.2(debug@4.3.4) + fs-extra: 0.30.0 + js-sha3: 0.8.0 + memorystream: 0.3.1 + require-from-string: 2.0.2 + semver: 5.7.1 + tmp: 0.0.33 + transitivePeerDependencies: + - debug - minimatch@10.2.2: + sonic-boom@2.8.0: dependencies: - brace-expansion: 5.0.3 + atomic-sleep: 1.0.0 - minimatch@3.1.2: + sonic-boom@4.2.0: dependencies: - brace-expansion: 1.1.12 + atomic-sleep: 1.0.0 - minimatch@9.0.5: + source-map-js@1.2.1: {} + + source-map-support@0.5.21: dependencies: - brace-expansion: 2.0.2 + buffer-from: 1.1.2 + source-map: 0.6.1 - minimist@1.2.8: {} + source-map@0.6.1: {} - minipass@7.1.3: {} + source-map@0.7.6: {} - mipd@0.0.7(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 + space-separated-tokens@2.0.2: {} - mkdirp@0.5.6: + spawndamnit@2.0.0: dependencies: - minimist: 1.2.8 + cross-spawn: 5.1.0 + signal-exit: 3.0.7 - mri@1.2.0: {} + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 - ms@2.1.3: {} + speakingurl@14.0.1: {} - mute-stream@0.0.8: {} + split-on-first@1.1.0: {} - nanoid@3.3.11: {} + split2@4.2.0: {} - natural-compare@1.4.0: {} + sprintf-js@1.0.3: {} - neo-async@2.6.2: {} + srvx@0.8.9: + dependencies: + cookie-es: 2.0.0 - netmask@2.0.2: {} + srvx@0.9.8: {} - next@15.5.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@next/env': 15.5.14 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001781 - postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(react@19.2.3) - optionalDependencies: - '@next/swc-darwin-arm64': 15.5.14 - '@next/swc-darwin-x64': 15.5.14 - '@next/swc-linux-arm64-gnu': 15.5.14 - '@next/swc-linux-arm64-musl': 15.5.14 - '@next/swc-linux-x64-gnu': 15.5.14 - '@next/swc-linux-x64-musl': 15.5.14 - '@next/swc-win32-arm64-msvc': 15.5.14 - '@next/swc-win32-x64-msvc': 15.5.14 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros + stackback@0.0.2: {} - no-case@2.3.2: + stacktrace-parser@0.1.10: dependencies: - lower-case: 1.1.4 + type-fest: 0.7.1 - node-plop@0.26.3: - dependencies: - '@babel/runtime-corejs3': 7.28.4 - '@types/inquirer': 6.5.0 - change-case: 3.1.0 - del: 5.1.0 - globby: 10.0.2 - handlebars: 4.7.8 - inquirer: 7.3.3 - isbinaryfile: 4.0.10 - lodash.get: 4.4.2 - mkdirp: 0.5.6 - resolve: 1.22.11 + standard-as-callback@2.1.0: {} - node-releases@2.0.27: {} + stat-mode@0.3.0: {} - nodemon@3.1.14: - dependencies: - chokidar: 3.6.0 - debug: 4.4.3(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 10.2.2 - pstree.remy: 1.1.8 - semver: 7.7.4 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.1 - undefsafe: 2.0.5 + statuses@1.5.0: {} - normalize-path@3.0.0: {} + statuses@2.0.1: {} - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 + statuses@2.0.2: {} - object-assign@4.1.1: {} + std-env@3.10.0: {} - object-inspect@1.13.4: {} + std-env@3.7.0: {} - object-keys@1.1.1: {} + std-env@3.9.0: {} - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 + stream-shift@1.0.1: {} - object.entries@1.1.9: + stream-to-array@2.3.0: dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 + any-promise: 1.3.0 - object.fromentries@2.0.8: + stream-to-promise@2.2.0: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + any-promise: 1.3.0 + end-of-stream: 1.1.0 + stream-to-array: 2.3.0 - object.values@1.2.1: + streamx@2.23.0: dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a - obug@2.1.1: {} + strict-event-emitter@0.5.1: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 + strict-uri-encode@2.0.0: {} - onetime@5.1.2: + string-width@4.2.3: dependencies: - mimic-fn: 2.1.0 + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 - optionator@0.9.4: + string-width@5.1.2: dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 - ora@4.1.1: + string_decoder@1.1.1: dependencies: - chalk: 3.0.0 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - log-symbols: 3.0.0 - mute-stream: 0.0.8 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 + safe-buffer: 5.1.2 - ora@5.4.1: + string_decoder@1.3.0: dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - - os-tmpdir@1.0.2: {} - - outdent@0.5.0: {} + safe-buffer: 5.2.1 - own-keys@1.0.1: + stringify-entities@4.0.4: dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 - ox@0.9.17(typescript@5.9.3)(zod@4.2.0): + strip-ansi@6.0.1: 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.2(typescript@5.9.3)(zod@4.2.0) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - zod + ansi-regex: 5.0.1 - p-filter@2.1.0: + strip-ansi@7.1.2: dependencies: - p-map: 2.1.0 + ansi-regex: 6.2.2 - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 + strip-bom@3.0.0: {} - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 + strip-final-newline@2.0.0: {} - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 + strip-final-newline@3.0.0: {} - p-locate@5.0.0: + strip-final-newline@4.0.0: {} + + strip-hex-prefix@1.0.0: dependencies: - p-limit: 3.1.0 + is-hex-prefixed: 1.0.0 - p-map@2.1.0: {} + strip-json-comments@3.1.1: {} + + strip-json-comments@5.0.1: {} + + strip-literal@1.3.0: + dependencies: + acorn: 8.11.3 - p-map@3.0.0: + strip-literal@3.1.0: dependencies: - aggregate-error: 3.1.0 + js-tokens: 9.0.1 - p-try@2.2.0: {} + strnum@2.1.2: {} - pac-proxy-agent@7.2.0: + structured-clone-es@1.0.0: {} + + styled-jsx@5.1.6(react@18.3.1): dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - get-uri: 6.0.5 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color + client-only: 0.0.1 + react: 18.3.1 - pac-resolver@7.0.1: + stylehacks@7.0.7(postcss@8.5.6): dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 + browserslist: 4.28.1 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - package-json-from-dist@1.0.1: {} + summary@2.1.0: {} - package-manager-detector@0.2.11: + superjson@2.2.1: dependencies: - quansync: 0.2.11 + copy-anything: 3.0.5 - param-case@2.1.1: + superjson@2.2.6: dependencies: - no-case: 2.3.2 + copy-anything: 4.0.5 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 + superstruct@1.0.3: {} + + supports-color@10.2.2: {} - pascal-case@2.0.1: + supports-color@5.5.0: dependencies: - camel-case: 3.0.0 - upper-case-first: 1.1.2 + has-flag: 3.0.0 - path-case@2.1.1: + supports-color@7.2.0: dependencies: - no-case: 2.3.2 + has-flag: 4.0.0 - path-exists@4.0.0: {} + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 - path-is-absolute@1.0.1: {} + supports-color@9.4.0: {} - path-key@3.1.1: {} + supports-hyperlinks@3.0.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 - path-parse@1.0.7: {} + supports-preserve-symlinks-flag@1.0.0: {} - path-scurry@2.0.2: + svgo@4.0.0: dependencies: - lru-cache: 11.2.4 - minipass: 7.1.3 + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.3 - path-type@4.0.0: {} + system-architecture@0.1.0: {} - pathe@2.0.3: {} + tabbable@6.2.0: {} - picocolors@1.1.1: {} + tagged-tag@1.0.0: {} - picomatch@2.3.1: {} + tailwind-merge@3.4.0: {} - picomatch@4.0.3: {} + tailwindcss@4.1.18: {} - pify@4.0.1: {} + tapable@2.2.1: {} + + tapable@2.3.0: {} - pkijs@3.3.3: + tar-stream@3.1.7: dependencies: - '@noble/hashes': 1.4.0 - asn1js: 3.0.7 - bytestreamjs: 2.0.1 - pvtsutils: 1.3.6 - pvutils: 1.1.5 - tslib: 2.8.1 + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a - possible-typed-array-names@1.1.0: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 - postcss@8.4.31: + tar@7.2.0: dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 - postcss@8.5.8: + tar@7.5.2: dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 - prelude-ls@1.2.1: {} + temp-dir@1.0.0: {} - prettier@2.8.8: {} + tempy@0.2.1: + dependencies: + temp-dir: 1.0.0 + unique-string: 1.0.0 - prettier@3.8.1: {} + term-size@2.2.1: {} - prop-types@15.8.1: + terser@5.44.1: dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 - proxy-agent@6.5.0: + test-exclude@7.0.1: dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 9.0.5 + + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 transitivePeerDependencies: - - supports-color + - react-native-b4a + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 - proxy-from-env@1.1.0: {} + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 - pstree.remy@1.1.8: {} + thread-stream@0.15.2: + dependencies: + real-require: 0.1.0 - punycode@2.3.1: {} + throttleit@2.1.0: {} - pvtsutils@1.3.6: + time-span@4.0.0: dependencies: - tslib: 2.8.1 + convert-hrtime: 3.0.0 - pvutils@1.1.5: {} + tiny-invariant@1.3.3: {} - quansync@0.2.11: {} + tinybench@2.9.0: {} - queue-microtask@1.2.3: {} + tinyexec@0.3.2: {} + + tinyexec@1.0.2: {} - rc@1.2.8: + tinyglobby@0.2.14: dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - react-dom@19.2.3(react@19.2.3): + tinyglobby@0.2.15: dependencies: - react: 19.2.3 - scheduler: 0.27.0 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - react-is@16.13.1: {} + tinypool@1.0.2: {} - react@19.2.3: {} + tinyrainbow@1.2.0: {} - read-yaml-file@1.1.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.2 - pify: 4.0.1 - strip-bom: 3.0.0 + tinyrainbow@2.0.0: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 + tinyspy@3.0.2: {} - readdirp@3.6.0: + tmp@0.0.33: dependencies: - picomatch: 2.3.1 + os-tmpdir: 1.0.2 - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 + to-fast-properties@2.0.0: {} - regexp.prototype.flags@1.5.4: + to-regex-range@5.0.1: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 + is-number: 7.0.0 - registry-auth-token@3.3.2: - dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 + toidentifier@1.0.0: {} - registry-url@3.1.0: + toidentifier@1.0.1: {} + + totalist@3.0.1: {} + + tough-cookie@4.1.4: dependencies: - rc: 1.2.8 + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 - require-directory@2.1.1: {} + tr46@0.0.3: {} - resolve-from@4.0.0: {} + tree-kill@1.2.2: {} - resolve-from@5.0.0: {} + trim-lines@3.0.1: {} - resolve@1.22.11: + ts-essentials@7.0.3(typescript@5.8.3): dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + typescript: 5.8.3 - resolve@2.0.0-next.5: + ts-morph@12.0.0: dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + '@ts-morph/common': 0.11.1 + code-block-writer: 10.1.1 - restore-cursor@3.1.0: + ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5): dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - - reusify@1.1.0: {} + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.11 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 - rimraf@3.0.2: + ts-node@10.9.1(@types/node@24.0.1)(typescript@5.9.3): dependencies: - glob: 7.2.3 + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.0.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true - rimraf@6.1.3: - dependencies: - glob: 13.0.6 - package-json-from-dist: 1.0.1 + ts-toolbelt@6.15.5: {} + + tslib@1.14.1: {} + + tslib@2.7.0: {} + + tslib@2.8.1: {} - rollup@4.53.4: + tsort@0.0.1: {} + + tsx@4.19.2: dependencies: - '@types/estree': 1.0.8 + esbuild: 0.23.1 + get-tsconfig: 4.13.0 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.4 - '@rollup/rollup-android-arm64': 4.53.4 - '@rollup/rollup-darwin-arm64': 4.53.4 - '@rollup/rollup-darwin-x64': 4.53.4 - '@rollup/rollup-freebsd-arm64': 4.53.4 - '@rollup/rollup-freebsd-x64': 4.53.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.4 - '@rollup/rollup-linux-arm-musleabihf': 4.53.4 - '@rollup/rollup-linux-arm64-gnu': 4.53.4 - '@rollup/rollup-linux-arm64-musl': 4.53.4 - '@rollup/rollup-linux-loong64-gnu': 4.53.4 - '@rollup/rollup-linux-ppc64-gnu': 4.53.4 - '@rollup/rollup-linux-riscv64-gnu': 4.53.4 - '@rollup/rollup-linux-riscv64-musl': 4.53.4 - '@rollup/rollup-linux-s390x-gnu': 4.53.4 - '@rollup/rollup-linux-x64-gnu': 4.53.4 - '@rollup/rollup-linux-x64-musl': 4.53.4 - '@rollup/rollup-openharmony-arm64': 4.53.4 - '@rollup/rollup-win32-arm64-msvc': 4.53.4 - '@rollup/rollup-win32-ia32-msvc': 4.53.4 - '@rollup/rollup-win32-x64-gnu': 4.53.4 - '@rollup/rollup-win32-x64-msvc': 4.53.4 fsevents: 2.3.3 - run-async@2.4.1: {} + turbo-darwin-64@2.7.2: + optional: true - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 + turbo-darwin-arm64@2.7.2: + optional: true - rxjs@6.6.7: - dependencies: - tslib: 1.14.1 + turbo-linux-64@2.7.2: + optional: true + + turbo-linux-arm64@2.7.2: + optional: true + + turbo-windows-64@2.7.2: + optional: true + + turbo-windows-arm64@2.7.2: + optional: true - rxjs@7.8.2: + turbo@2.7.2: + optionalDependencies: + turbo-darwin-64: 2.7.2 + turbo-darwin-arm64: 2.7.2 + turbo-linux-64: 2.7.2 + turbo-linux-arm64: 2.7.2 + turbo-windows-64: 2.7.2 + turbo-windows-arm64: 2.7.2 + + tweetnacl-util@0.15.1: {} + + tweetnacl@1.0.3: {} + + twoslash-protocol@0.2.12: {} + + twoslash-vue@0.2.12(typescript@5.9.3): dependencies: - tslib: 2.8.1 + '@vue/language-core': 2.1.10(typescript@5.9.3) + twoslash: 0.2.12(typescript@5.9.3) + twoslash-protocol: 0.2.12 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color - safe-array-concat@1.1.3: + twoslash@0.2.12(typescript@5.9.3): dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 + '@typescript/vfs': 1.6.0(typescript@5.9.3) + twoslash-protocol: 0.2.12 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color - safe-buffer@5.2.1: {} + type-fest@0.20.2: {} - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 + type-fest@0.21.3: {} - safe-regex-test@1.1.0: + type-fest@0.7.1: {} + + type-fest@4.18.2: {} + + type-fest@5.3.1: dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 + tagged-tag: 1.0.0 - safer-buffer@2.1.2: {} + type-level-regexp@0.1.17: {} - scheduler@0.27.0: {} + typescript@4.9.5: {} - semver@6.3.1: {} + typescript@5.6.1-rc: {} - semver@7.7.3: {} + typescript@5.8.3: {} - semver@7.7.4: {} + typescript@5.9.3: {} - sentence-case@2.1.1: - dependencies: - no-case: 2.3.2 - upper-case-first: 1.1.2 + ufo@1.5.3: {} - set-function-length@1.2.2: + ufo@1.6.1: {} + + uid-promise@1.0.0: {} + + uint8arrays@3.1.0: dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 + multiformats: 9.9.0 - set-function-name@2.0.2: + uint8arrays@3.1.1: dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 + multiformats: 9.9.0 + + ultrahtml@1.6.0: {} - set-proto@1.0.0: + unconfig@0.3.13: dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 + '@antfu/utils': 0.7.7 + defu: 6.1.4 + jiti: 1.21.7 - sharp@0.34.5: + uncrypto@0.1.3: {} + + unctx@2.5.0: dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.7.4 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - optional: true + acorn: 8.15.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + unplugin: 2.3.11 + + undici-types@5.26.5: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 + undici-types@6.19.8: {} - shebang-regex@3.0.0: {} + undici-types@6.21.0: {} - shell-quote@1.8.3: {} + undici-types@7.8.0: {} - side-channel-list@1.0.0: + undici@5.28.3: dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 + '@fastify/busboy': 2.1.0 - side-channel-map@1.0.1: + undici@5.28.4: dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 + '@fastify/busboy': 2.1.1 - side-channel-weakmap@1.0.2: + undici@5.29.0: dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 + '@fastify/busboy': 2.1.1 - side-channel@1.1.0: + unenv@2.0.0-rc.24: dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 + pathe: 2.0.3 - siginfo@2.0.0: {} + unhead@2.1.1: + dependencies: + hookable: 5.5.3 - signal-exit@3.0.7: {} + unicode-emoji-modifier-base@1.0.0: {} - signal-exit@4.1.0: {} + unicorn-magic@0.3.0: {} - simple-update-notifier@2.0.0: + unimport@3.7.1(rollup@4.54.0): dependencies: - semver: 7.7.4 - - slash@3.0.0: {} + '@rollup/pluginutils': 5.1.0(rollup@4.54.0) + acorn: 8.11.3 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + fast-glob: 3.3.2 + local-pkg: 0.5.0 + magic-string: 0.30.10 + mlly: 1.7.0 + pathe: 1.1.2 + pkg-types: 1.1.0 + scule: 1.3.0 + strip-literal: 1.3.0 + unplugin: 1.10.1 + transitivePeerDependencies: + - rollup - smart-buffer@4.2.0: {} + unimport@5.6.0: + dependencies: + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.15 + unplugin: 2.3.11 + unplugin-utils: 0.3.1 - snake-case@2.1.0: + unique-string@1.0.0: dependencies: - no-case: 2.3.2 + crypto-random-string: 1.0.0 - socks-proxy-agent@8.0.5: + unist-util-is@6.0.0: dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@5.5.0) - socks: 2.8.7 - transitivePeerDependencies: - - supports-color + '@types/unist': 3.0.2 - socks@2.8.7: + unist-util-position@5.0.0: dependencies: - ip-address: 10.1.0 - smart-buffer: 4.2.0 + '@types/unist': 3.0.2 - source-map-js@1.2.1: {} + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.2 - source-map@0.6.1: {} + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 - spawndamnit@3.0.1: + unist-util-visit@5.0.0: dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 - sprintf-js@1.0.3: {} + universalify@0.1.2: {} - stackback@0.0.2: {} + universalify@0.2.0: {} - std-env@3.10.0: {} + universalify@2.0.1: {} + + unocss@0.59.4(rollup@4.54.0): + dependencies: + '@unocss/astro': 0.59.4(rollup@4.54.0) + '@unocss/cli': 0.59.4(rollup@4.54.0) + '@unocss/core': 0.59.4 + '@unocss/extractor-arbitrary-variants': 0.59.4 + '@unocss/postcss': 0.59.4 + '@unocss/preset-attributify': 0.59.4 + '@unocss/preset-icons': 0.59.4 + '@unocss/preset-mini': 0.59.4 + '@unocss/preset-tagify': 0.59.4 + '@unocss/preset-typography': 0.59.4 + '@unocss/preset-uno': 0.59.4 + '@unocss/preset-web-fonts': 0.59.4 + '@unocss/preset-wind': 0.59.4 + '@unocss/reset': 0.59.4 + '@unocss/transformer-attributify-jsx': 0.59.4 + '@unocss/transformer-attributify-jsx-babel': 0.59.4 + '@unocss/transformer-compile-class': 0.59.4 + '@unocss/transformer-directives': 0.59.4 + '@unocss/transformer-variant-group': 0.59.4 + '@unocss/vite': 0.59.4(rollup@4.54.0) + transitivePeerDependencies: + - rollup + - supports-color + + unpipe@1.0.0: {} - stop-iteration-iterator@1.1.0: + unplugin-utils@0.2.5: dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 + pathe: 2.0.3 + picomatch: 4.0.3 - string-width@4.2.3: + unplugin-utils@0.3.1: dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + pathe: 2.0.3 + picomatch: 4.0.3 - string-width@7.2.0: + unplugin-vue-router@0.15.0(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)): dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + '@vue-macros/common': 3.0.0-beta.16(vue@3.5.26(typescript@5.9.3)) + '@vue/compiler-sfc': 3.5.26 + '@vue/language-core': 3.2.1 + ast-walker-scope: 0.8.3 + chokidar: 4.0.3 + json5: 2.2.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.3 + scule: 1.3.0 + tinyglobby: 0.2.15 + unplugin: 2.3.11 + unplugin-utils: 0.2.5 + yaml: 2.8.2 + optionalDependencies: + vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) + transitivePeerDependencies: + - vue - string.prototype.matchall@4.0.12: + unplugin@1.10.1: dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 + acorn: 8.11.3 + chokidar: 3.6.0 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.6.1 - string.prototype.repeat@1.0.0: + unplugin@2.3.11: dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.1 + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 - string.prototype.trim@1.2.10: + unstorage@1.10.2(idb-keyval@6.2.1)(ioredis@5.8.2): dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 + anymatch: 3.1.3 + chokidar: 3.6.0 + destr: 2.0.5 + h3: 1.15.4 + listhen: 1.7.2 + lru-cache: 10.2.2 + mri: 1.2.0 + node-fetch-native: 1.6.4 + ofetch: 1.5.1 + ufo: 1.6.1 + optionalDependencies: + idb-keyval: 6.2.1 + ioredis: 5.8.2 + transitivePeerDependencies: + - uWebSockets.js - string.prototype.trimend@1.0.9: + unstorage@1.17.3(@vercel/blob@1.0.2)(db0@0.3.4)(idb-keyval@6.2.1)(ioredis@5.8.2): dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.4 + lru-cache: 10.4.3 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.1 + optionalDependencies: + '@vercel/blob': 1.0.2 + db0: 0.3.4 + idb-keyval: 6.2.1 + ioredis: 5.8.2 - string.prototype.trimstart@1.0.8: + untun@0.1.3: dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 + citty: 0.1.6 + consola: 3.4.2 + pathe: 1.1.2 - string_decoder@1.3.0: + untyped@1.4.2: dependencies: - safe-buffer: 5.2.1 + '@babel/core': 7.24.5 + '@babel/standalone': 7.24.5 + '@babel/types': 7.24.5 + defu: 6.1.4 + jiti: 1.21.0 + mri: 1.2.0 + scule: 1.3.0 + transitivePeerDependencies: + - supports-color - strip-ansi@6.0.1: + untyped@2.0.0: dependencies: - ansi-regex: 5.0.1 + citty: 0.1.6 + defu: 6.1.4 + jiti: 2.6.1 + knitwork: 1.3.0 + scule: 1.3.0 - strip-ansi@7.1.2: + unwasm@0.3.11: dependencies: - ansi-regex: 6.2.2 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + pathe: 2.0.3 + pkg-types: 2.3.0 + unplugin: 2.3.11 - strip-bom@3.0.0: {} + update-browserslist-db@1.0.15(browserslist@4.23.0): + dependencies: + browserslist: 4.23.0 + escalade: 3.2.0 + picocolors: 1.1.1 - strip-final-newline@2.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 - strip-json-comments@2.0.1: {} + uqr@0.1.2: {} - strip-json-comments@3.1.1: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 - styled-jsx@5.1.6(react@19.2.3): + url-parse@1.5.10: dependencies: - client-only: 0.0.1 - react: 19.2.3 + querystringify: 2.2.0 + requires-port: 1.0.0 - supports-color@5.5.0: + use-callback-ref@1.3.3(@types/react@18.3.27)(react@18.3.1): dependencies: - has-flag: 3.0.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 - supports-color@7.2.0: + use-sidecar@1.1.3(@types/react@18.3.27)(react@18.3.1): dependencies: - has-flag: 4.0.0 + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 - supports-color@8.1.1: + use-sync-external-store@1.2.0(react@18.3.1): dependencies: - has-flag: 4.0.0 + react: 18.3.1 - supports-preserve-symlinks-flag@1.0.0: {} + use-sync-external-store@1.4.0(react@18.3.1): + dependencies: + react: 18.3.1 - swap-case@1.1.2: + utf-8-validate@5.0.10: dependencies: - lower-case: 1.1.4 - upper-case: 1.1.3 + node-gyp-build: 4.8.4 - syncpack-darwin-arm64@14.0.0: - optional: true + utf-8-validate@6.0.5: + dependencies: + node-gyp-build: 4.8.4 - syncpack-darwin-x64@14.0.0: - optional: true + util-deprecate@1.0.2: {} - syncpack-linux-arm64-musl@14.0.0: - optional: true + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 - syncpack-linux-arm64@14.0.0: - optional: true + uuid@10.0.0: {} - syncpack-linux-x64-musl@14.0.0: - optional: true + uuid@8.3.2: {} - syncpack-linux-x64@14.0.0: - optional: true + uuid@9.0.1: {} - syncpack-windows-arm64@14.0.0: - optional: true + v8-compile-cache-lib@3.0.1: {} - syncpack-windows-x64@14.0.0: - optional: true + validate-npm-package-name@5.0.1: {} - syncpack@14.0.0: + valtio@1.13.2(@types/react@18.3.27)(react@18.3.1): + dependencies: + derive-valtio: 0.1.0(valtio@1.13.2(@types/react@18.3.27)(react@18.3.1)) + proxy-compare: 2.6.0 + use-sync-external-store: 1.2.0(react@18.3.1) optionalDependencies: - syncpack-darwin-arm64: 14.0.0 - syncpack-darwin-x64: 14.0.0 - syncpack-linux-arm64: 14.0.0 - syncpack-linux-arm64-musl: 14.0.0 - syncpack-linux-x64: 14.0.0 - syncpack-linux-x64-musl: 14.0.0 - syncpack-windows-arm64: 14.0.0 - syncpack-windows-x64: 14.0.0 - - term-size@2.2.1: {} - - through@2.3.8: {} - - tinybench@2.9.0: {} - - tinycolor2@1.6.0: {} - - tinyexec@1.0.2: {} + '@types/react': 18.3.27 + react: 18.3.1 + + vercel@48.12.1(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3): + dependencies: + '@vercel/backends': 0.0.14(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3) + '@vercel/blob': 1.0.2 + '@vercel/build-utils': 13.2.2 + '@vercel/detect-agent': 1.0.0 + '@vercel/elysia': 0.1.12(encoding@0.1.13)(rollup@4.54.0) + '@vercel/express': 0.1.17(encoding@0.1.13)(rollup@4.54.0)(typescript@5.8.3) + '@vercel/fastify': 0.1.15(encoding@0.1.13)(rollup@4.54.0) + '@vercel/fun': 1.2.0(encoding@0.1.13) + '@vercel/go': 3.2.3 + '@vercel/h3': 0.1.21(encoding@0.1.13)(rollup@4.54.0) + '@vercel/hono': 0.2.15(encoding@0.1.13)(rollup@4.54.0) + '@vercel/hydrogen': 1.3.2 + '@vercel/nestjs': 0.2.16(encoding@0.1.13)(rollup@4.54.0) + '@vercel/next': 4.15.7(encoding@0.1.13)(rollup@4.54.0) + '@vercel/node': 5.5.14(encoding@0.1.13)(rollup@4.54.0) + '@vercel/python': 6.1.0 + '@vercel/redwood': 2.4.5(encoding@0.1.13)(rollup@4.54.0) + '@vercel/remix-builder': 5.5.5(encoding@0.1.13)(rollup@4.54.0) + '@vercel/ruby': 2.2.2 + '@vercel/rust': 1.0.3 + '@vercel/static-build': 2.8.13 + chokidar: 4.0.0 + jose: 5.9.6 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + - typescript - tinyglobby@0.2.15: + vfile-message@4.0.2: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 - tinygradient@1.1.5: + vfile@6.0.1: dependencies: - '@types/tinycolor2': 1.4.6 - tinycolor2: 1.6.0 + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 - tinyrainbow@3.0.3: {} - - title-case@2.1.1: + viem@2.10.8(bufferutil@4.0.8)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20): dependencies: - no-case: 2.3.2 - upper-case: 1.1.3 + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 1.0.0(typescript@5.9.3)(zod@3.25.20) + isows: 1.0.4(ws@8.13.0(bufferutil@4.0.8)(utf-8-validate@6.0.5)) + ws: 8.13.0(bufferutil@4.0.8)(utf-8-validate@6.0.5) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - tmp@0.0.33: + viem@2.10.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.5)(zod@3.25.20): dependencies: - os-tmpdir: 1.0.2 + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 1.0.0(typescript@5.9.3)(zod@3.25.20) + isows: 1.0.4(ws@8.13.0(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ws: 8.13.0(bufferutil@4.1.0)(utf-8-validate@6.0.5) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - to-regex-range@5.0.1: + viem@2.17.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20): dependencies: - is-number: 7.0.0 + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.4.0 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + abitype: 1.0.5(typescript@5.9.3)(zod@3.25.20) + isows: 1.0.4(ws@8.17.1(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + ws: 8.17.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - touch@3.1.1: {} + viem@2.23.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20): + dependencies: + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + '@scure/bip32': 1.6.2 + '@scure/bip39': 1.5.4 + abitype: 1.0.8(typescript@5.9.3)(zod@3.25.20) + isows: 1.0.6(ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + ox: 0.6.7(typescript@5.9.3)(zod@3.25.20) + ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - tree-kill@1.2.2: {} + viem@2.31.4(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20): + dependencies: + '@noble/curves': 1.9.2 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.0.8(typescript@5.8.3)(zod@3.25.20) + isows: 1.0.7(ws@8.18.2(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ox: 0.8.1(typescript@5.8.3)(zod@3.25.20) + ws: 8.18.2(bufferutil@4.1.0)(utf-8-validate@6.0.5) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - ts-api-utils@2.1.0(typescript@5.9.3): + viem@2.43.3(bufferutil@4.1.0)(typescript@5.9.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@5.9.3)(zod@3.22.4) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + ox: 0.11.1(typescript@5.9.3)(zod@3.22.4) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + optionalDependencies: typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - ts-node@10.9.2(@types/node@25.3.0)(typescript@5.9.3): + viem@2.43.3(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.20): dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 25.3.0 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.9.3)(zod@3.25.20) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + ox: 0.11.1(typescript@5.9.3)(zod@3.25.20) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) + optionalDependencies: typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - - tslib@1.14.1: {} - - tslib@2.8.1: {} - - turbo-darwin-64@2.8.10: - optional: true - - turbo-darwin-arm64@2.8.10: - optional: true - - turbo-linux-64@2.8.10: - optional: true - - turbo-linux-arm64@2.8.10: - optional: true - - turbo-windows-64@2.8.10: - optional: true - - turbo-windows-arm64@2.8.10: - optional: true + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - turbo@2.8.10: + viem@2.43.5(bufferutil@4.1.0)(typescript@5.8.3)(utf-8-validate@6.0.5)(zod@3.25.20): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@5.8.3)(zod@3.25.20) + isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5)) + ox: 0.11.1(typescript@5.8.3)(zod@3.25.20) + ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5) optionalDependencies: - turbo-darwin-64: 2.8.10 - turbo-darwin-arm64: 2.8.10 - turbo-linux-64: 2.8.10 - turbo-linux-arm64: 2.8.10 - turbo-windows-64: 2.8.10 - turbo-windows-arm64: 2.8.10 + typescript: 5.8.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod - turbo@2.8.21: - optionalDependencies: - '@turbo/darwin-64': 2.8.21 - '@turbo/darwin-arm64': 2.8.21 - '@turbo/linux-64': 2.8.21 - '@turbo/linux-arm64': 2.8.21 - '@turbo/windows-64': 2.8.21 - '@turbo/windows-arm64': 2.8.21 + vite-dev-rpc@1.1.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)): + dependencies: + birpc: 2.9.0 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) - type-check@0.4.0: + vite-hot-client@2.1.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)): dependencies: - prelude-ls: 1.2.1 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) - type-fest@0.21.3: {} + vite-node@2.1.9(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser - typed-array-buffer@1.0.3: + vite-node@3.2.4(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2): dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml - typed-array-byte-length@1.0.3: + vite-plugin-checker@0.10.3(@biomejs/biome@1.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)): dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 + '@babel/code-frame': 7.27.1 + chokidar: 4.0.3 + npm-run-path: 6.0.0 + picocolors: 1.1.1 + picomatch: 4.0.3 + strip-ansi: 7.1.2 + tiny-invariant: 1.3.3 + tinyglobby: 0.2.15 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vscode-uri: 3.1.0 + optionalDependencies: + '@biomejs/biome': 1.9.4 + typescript: 5.9.3 + + vite-plugin-inspect@11.3.3(@nuxt/kit@3.20.2(magicast@0.3.5))(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.0.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2)) + optionalDependencies: + '@nuxt/kit': 3.20.2(magicast@0.3.5) + transitivePeerDependencies: + - supports-color - typed-array-byte-offset@1.0.4: + vite-plugin-vue-tracer@1.2.0(vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)): dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 + estree-walker: 3.0.3 + exsolve: 1.0.8 + magic-string: 0.30.21 + pathe: 2.0.3 + source-map-js: 1.2.1 + vite: 7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2) + vue: 3.5.26(typescript@5.9.3) - typed-array-length@1.0.7: + vite@5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1): dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.54.0 + optionalDependencies: + '@types/node': 24.0.1 + fsevents: 2.3.3 + lightningcss: 1.30.2 + terser: 5.44.1 - typescript-eslint@8.50.0(eslint@9.39.2)(typescript@5.9.3): + vite@7.3.0(@types/node@24.0.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.19.2)(yaml@2.8.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2)(typescript@5.9.3) - eslint: 9.39.2 - typescript: 5.9.3 + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.0.1 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + terser: 5.44.1 + tsx: 4.19.2 + yaml: 2.8.2 + + vitepress@1.5.0(@algolia/client-search@5.12.0)(@types/node@24.0.1)(@types/react@18.3.1)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.1)(jwt-decode@4.0.0)(lightningcss@1.30.2)(postcss@8.5.6)(qrcode@1.5.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.44.1)(typescript@5.9.3): + dependencies: + '@docsearch/css': 3.6.3 + '@docsearch/js': 3.6.3(@algolia/client-search@5.12.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@iconify-json/simple-icons': 1.2.10 + '@shikijs/core': 1.22.2 + '@shikijs/transformers': 1.22.2 + '@shikijs/types': 1.22.2 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.1.4(vite@5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.12(typescript@5.9.3)) + '@vue/devtools-api': 7.6.2 + '@vue/shared': 3.5.12 + '@vueuse/core': 11.2.0(vue@3.5.12(typescript@5.9.3)) + '@vueuse/integrations': 11.2.0(change-case@5.4.4)(focus-trap@7.6.0)(fuse.js@7.1.0)(idb-keyval@6.2.1)(jwt-decode@4.0.0)(qrcode@1.5.3)(vue@3.5.12(typescript@5.9.3)) + focus-trap: 7.6.0 + mark.js: 8.11.1 + minisearch: 7.1.0 + shiki: 1.22.2 + vite: 5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + vue: 3.5.12(typescript@5.9.3) + optionalDependencies: + postcss: 8.5.6 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vitest@2.1.9(@edge-runtime/vm@3.2.0)(@types/node@24.0.1)(happy-dom@20.0.2)(lightningcss@1.30.2)(msw@2.4.9(typescript@5.8.3))(terser@5.44.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(msw@2.4.9(typescript@5.8.3))(vite@5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + vite-node: 2.1.9(@types/node@24.0.1)(lightningcss@1.30.2)(terser@5.44.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@edge-runtime/vm': 3.2.0 + '@types/node': 24.0.1 + happy-dom: 20.0.2 transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss - supports-color + - terser - typescript@5.9.3: {} - - uglify-js@3.19.3: - optional: true + vscode-uri@3.1.0: {} - unbox-primitive@1.1.0: + vue-bundle-renderer@2.2.0: dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 + ufo: 1.6.1 - undefsafe@2.0.5: {} + vue-component-type-helpers@2.0.16: {} - undici-types@7.18.2: {} + vue-demi@0.14.10(vue@3.4.27(typescript@5.9.3)): + dependencies: + vue: 3.4.27(typescript@5.9.3) - universalify@0.1.2: {} + vue-demi@0.14.10(vue@3.5.12(typescript@5.9.3)): + dependencies: + vue: 3.5.12(typescript@5.9.3) - universalify@2.0.1: {} + vue-devtools-stub@0.1.0: {} - update-browserslist-db@1.2.2(browserslist@4.28.1): + vue-resize@2.0.0-alpha.1(vue@3.5.12(typescript@5.9.3)): dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 + vue: 3.5.12(typescript@5.9.3) - update-check@1.5.4: + vue-router@4.3.2(vue@3.4.27(typescript@5.9.3)): dependencies: - registry-auth-token: 3.3.2 - registry-url: 3.1.0 + '@vue/devtools-api': 6.6.1 + vue: 3.4.27(typescript@5.9.3) - upper-case-first@1.1.2: + vue-router@4.6.4(vue@3.5.26(typescript@5.8.3)): dependencies: - upper-case: 1.1.3 - - upper-case@1.1.3: {} + '@vue/devtools-api': 6.6.4 + vue: 3.5.26(typescript@5.8.3) + optional: true - uri-js@4.4.1: + vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)): dependencies: - punycode: 2.3.1 - - util-deprecate@1.0.2: {} + '@vue/devtools-api': 6.6.4 + vue: 3.5.26(typescript@5.9.3) - uuid@13.0.0: {} + vue-template-compiler@2.7.16: + dependencies: + de-indent: 1.0.2 + he: 1.2.0 - v8-compile-cache-lib@3.0.1: {} + vue-tsc@2.0.16(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.2.1 + '@vue/language-core': 2.0.16(typescript@5.9.3) + semver: 7.5.4 + typescript: 5.9.3 - validate-npm-package-name@5.0.1: {} + vue@3.4.27(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.4.27 + '@vue/compiler-sfc': 3.4.27 + '@vue/runtime-dom': 3.4.27 + '@vue/server-renderer': 3.4.27(vue@3.4.27(typescript@5.9.3)) + '@vue/shared': 3.4.27 + optionalDependencies: + typescript: 5.9.3 - viem@2.42.1(typescript@5.9.3)(zod@4.2.0): + vue@3.5.12(typescript@5.9.3): dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.3)(zod@4.2.0) - isows: 1.0.7(ws@8.18.3) - ox: 0.9.17(typescript@5.9.3)(zod@4.2.0) - ws: 8.18.3 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-sfc': 3.5.12 + '@vue/runtime-dom': 3.5.12 + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.9.3)) + '@vue/shared': 3.5.12 optionalDependencies: typescript: 5.9.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - vite@7.3.0(@types/node@25.3.0): + vue@3.5.26(typescript@5.8.3): dependencies: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.8 - rollup: 4.53.4 - tinyglobby: 0.2.15 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.8.3)) + '@vue/shared': 3.5.26 optionalDependencies: - '@types/node': 25.3.0 - fsevents: 2.3.3 + typescript: 5.8.3 + optional: true - vitest@4.0.18(@types/node@25.3.0)(happy-dom@20.8.9): + vue@3.5.26(typescript@5.9.3): dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@25.3.0)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@25.3.0) - why-is-node-running: 2.3.0 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 optionalDependencies: - '@types/node': 25.3.0 - happy-dom: 20.8.9 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml + typescript: 5.9.3 wcwidth@1.0.1: dependencies: defaults: 1.0.4 + optional: true - whatwg-mimetype@3.0.0: {} + web-vitals@0.2.4: {} - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 + webextension-polyfill@0.10.0: {} + + webidl-conversions@3.0.1: {} - which-builtin-type@1.2.1: + webpack-bundle-analyzer@4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.5): dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.19 + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.15.0 + acorn-walk: 8.3.4 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + is-plain-object: 5.0.0 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + webpack-sources@3.2.3: {} - which-collection@1.0.2: + webpack-virtual-modules@0.6.1: {} + + webpack-virtual-modules@0.6.2: {} + + whatwg-mimetype@3.0.0: {} + + whatwg-url@5.0.0: dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-module@2.0.0: {} which-typed-array@1.1.19: dependencies: @@ -7407,18 +24203,28 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - word-wrap@1.2.5: {} + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 - wordwrap@1.0.0: {} + workerpool@6.2.1: {} wrap-ansi@6.2.0: dependencies: @@ -7432,51 +24238,200 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@9.0.2: + wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 + ansi-styles: 6.2.1 + string-width: 5.1.2 strip-ansi: 7.1.2 wrappy@1.0.2: {} - ws@8.18.3: {} + ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 6.0.5 + + ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 5.0.10 + + ws@7.5.9(bufferutil@4.1.0)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 5.0.10 + + ws@8.13.0(bufferutil@4.0.8)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 6.0.5 + + ws@8.13.0(bufferutil@4.1.0)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.5 + + ws@8.17.1(bufferutil@4.1.0)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 5.0.10 + + ws@8.17.1(bufferutil@4.1.0)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.5 + + ws@8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 5.0.10 + + ws@8.18.2(bufferutil@4.1.0)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.5 + + ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 5.0.10 + + ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@6.0.5): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.5 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + + xdg-app-paths@5.1.0: + dependencies: + xdg-portable: 7.3.0 + + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + + xmlhttprequest-ssl@2.1.2: {} - ws@8.20.0: {} + xtend@4.0.2: {} + + y18n@4.0.3: {} y18n@5.0.8: {} + yallist@2.1.2: {} + yallist@3.1.1: {} + yallist@4.0.0: {} + + yallist@5.0.0: {} + + yaml@2.8.2: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.4: {} + yargs-parser@21.1.1: {} - yargs-parser@22.0.0: {} + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 - yargs@17.7.2: + yargs@15.4.1: dependencies: - cliui: 8.0.1 + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.0 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 21.1.1 + yargs-parser: 20.2.4 - yargs@18.0.0: + yargs@17.7.2: dependencies: - cliui: 9.0.1 + cliui: 8.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - string-width: 7.2.0 + require-directory: 2.1.1 + string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 22.0.0 + yargs-parser: 21.1.1 + + yauzl-clone@1.0.4: + dependencies: + events-intercept: 2.0.0 + + yauzl-promise@2.1.3: + dependencies: + yauzl: 2.10.0 + yauzl-clone: 1.0.4 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 yn@3.1.1: {} yocto-queue@0.1.0: {} - zod-validation-error@4.0.2(zod@4.2.0): + yoctocolors@2.0.2: {} + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.13: dependencies: - zod: 4.2.0 + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.12 + cookie-es: 2.0.0 + youch-core: 0.3.3 + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 + + zod-validation-error@3.2.0(zod@3.25.20): + dependencies: + zod: 3.25.20 + + zod@3.22.4: {} + + zod@3.25.20: {} + + zustand@5.0.0(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.27 + react: 18.3.1 + use-sync-external-store: 1.4.0(react@18.3.1) - zod@4.2.0: {} + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 59a78ba9a9..b1f32102e6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,11 +1,19 @@ packages: - - extras/* - packages/* - - packages/services/* - - packages/utils/* - - packages/wallet/* - - repo/* - - test/* + - "!packages/register-tests" + - packages/cli/src/plugins/__fixtures__/hardhat + - packages/register-tests/* + - playgrounds/* + - site -publicHoistPattern: -- "eslint" +catalog: + "@tanstack/query-core": "5.49.1" + "@tanstack/react-query": "5.49.2" + "@tanstack/vue-query": "5.49.1" + "@testing-library/dom": "10.4.0" + "@testing-library/react": "16.0.1" + "@types/react": "18.3.1" + "@types/react-dom": "18.3.0" + react-dom: "18.3.1" + react: "18.3.1" + vue: "3.4.27" diff --git a/repo/README.md b/repo/README.md deleted file mode 100644 index ff6be7a7b7..0000000000 --- a/repo/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# repo - -This folder contains the boilerplate packages needed to manage -our monorepo. diff --git a/repo/eslint-config/CHANGELOG.md b/repo/eslint-config/CHANGELOG.md deleted file mode 100644 index 2df72f2e7a..0000000000 --- a/repo/eslint-config/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# @repo/eslint-config - -## 0.0.1 - -### Patch Changes - -- d5017e8: Beta release for v3 -- 7c6c811: 3.0.0-beta.3 with fixes - -## 0.0.1-beta.1 - -### Patch Changes - -- Beta release for v3 - -## 0.0.1-beta.0 - -### Patch Changes - -- 3.0.0-beta.3 with fixes diff --git a/repo/eslint-config/README.md b/repo/eslint-config/README.md deleted file mode 100644 index 8b42d901b0..0000000000 --- a/repo/eslint-config/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `@turbo/eslint-config` - -Collection of internal eslint configurations. diff --git a/repo/eslint-config/base.js b/repo/eslint-config/base.js deleted file mode 100644 index 0166a3ff21..0000000000 --- a/repo/eslint-config/base.js +++ /dev/null @@ -1,52 +0,0 @@ -import js from '@eslint/js' -import eslintConfigPrettier from 'eslint-config-prettier' -import turboPlugin from 'eslint-plugin-turbo' -import tseslint from 'typescript-eslint' -import onlyWarn from 'eslint-plugin-only-warn' - -/** - * A shared ESLint configuration for the repository. - * - * @type {import("eslint").Linter.Config} - * */ -export const config = [ - js.configs.recommended, - eslintConfigPrettier, - ...tseslint.configs.recommended, - { - plugins: { - turbo: turboPlugin, - }, - rules: { - 'turbo/no-undeclared-env-vars': 'warn', - }, - }, - { - plugins: { - onlyWarn, - }, - }, - { - rules: { - // Disallow semicolons - semi: ['error', 'never'], - - // Turn off the base ESLint version of no-unused-vars - 'no-unused-vars': 'off', - - // Use @typescript-eslint/no-unused-vars - // Allow unused vars prefixed with _ - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - }, - ], - }, - }, - { - ignores: ['dist/**'], - }, -] diff --git a/repo/eslint-config/next.js b/repo/eslint-config/next.js deleted file mode 100644 index 7acbb7b5a8..0000000000 --- a/repo/eslint-config/next.js +++ /dev/null @@ -1,49 +0,0 @@ -import js from '@eslint/js' -import eslintConfigPrettier from 'eslint-config-prettier' -import tseslint from 'typescript-eslint' -import pluginReactHooks from 'eslint-plugin-react-hooks' -import pluginReact from 'eslint-plugin-react' -import globals from 'globals' -import pluginNext from '@next/eslint-plugin-next' -import { config as baseConfig } from './base.js' - -/** - * A custom ESLint configuration for libraries that use Next.js. - * - * @type {import("eslint").Linter.Config} - * */ -export const nextJsConfig = [ - ...baseConfig, - js.configs.recommended, - eslintConfigPrettier, - ...tseslint.configs.recommended, - { - ...pluginReact.configs.flat.recommended, - languageOptions: { - ...pluginReact.configs.flat.recommended.languageOptions, - globals: { - ...globals.serviceworker, - }, - }, - }, - { - plugins: { - '@next/next': pluginNext, - }, - rules: { - ...pluginNext.configs.recommended.rules, - ...pluginNext.configs['core-web-vitals'].rules, - }, - }, - { - plugins: { - 'react-hooks': pluginReactHooks, - }, - settings: { react: { version: 'detect' } }, - rules: { - ...pluginReactHooks.configs.recommended.rules, - // React scope no longer necessary with new JSX transform. - 'react/react-in-jsx-scope': 'off', - }, - }, -] diff --git a/repo/eslint-config/package.json b/repo/eslint-config/package.json deleted file mode 100644 index 9c38b69880..0000000000 --- a/repo/eslint-config/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@repo/eslint-config", - "version": "0.0.1", - "type": "module", - "private": true, - "exports": { - "./base": "./base.js", - "./next-js": "./next.js", - "./react-internal": "./react-internal.js" - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@next/eslint-plugin-next": "^15.5.9", - "eslint": "^9.39.2", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-only-warn": "^1.1.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-turbo": "^2.6.3", - "globals": "^16.5.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.49.0" - } -} diff --git a/repo/eslint-config/react-internal.js b/repo/eslint-config/react-internal.js deleted file mode 100644 index 4762b15d76..0000000000 --- a/repo/eslint-config/react-internal.js +++ /dev/null @@ -1,39 +0,0 @@ -import js from '@eslint/js' -import eslintConfigPrettier from 'eslint-config-prettier' -import tseslint from 'typescript-eslint' -import pluginReactHooks from 'eslint-plugin-react-hooks' -import pluginReact from 'eslint-plugin-react' -import globals from 'globals' -import { config as baseConfig } from './base.js' - -/** - * A custom ESLint configuration for libraries that use React. - * - * @type {import("eslint").Linter.Config} */ -export const config = [ - ...baseConfig, - js.configs.recommended, - eslintConfigPrettier, - ...tseslint.configs.recommended, - pluginReact.configs.flat.recommended, - { - languageOptions: { - ...pluginReact.configs.flat.recommended.languageOptions, - globals: { - ...globals.serviceworker, - ...globals.browser, - }, - }, - }, - { - plugins: { - 'react-hooks': pluginReactHooks, - }, - settings: { react: { version: 'detect' } }, - rules: { - ...pluginReactHooks.configs.recommended.rules, - // React scope no longer necessary with new JSX transform. - 'react/react-in-jsx-scope': 'off', - }, - }, -] diff --git a/repo/typescript-config/CHANGELOG.md b/repo/typescript-config/CHANGELOG.md deleted file mode 100644 index 3e5ecbabf4..0000000000 --- a/repo/typescript-config/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# @repo/typescript-config - -## 0.0.1 - -### Patch Changes - -- d5017e8: Beta release for v3 -- 7c6c811: 3.0.0-beta.3 with fixes - -## 0.0.1-beta.1 - -### Patch Changes - -- Beta release for v3 - -## 0.0.1-beta.0 - -### Patch Changes - -- 3.0.0-beta.3 with fixes diff --git a/repo/typescript-config/base.json b/repo/typescript-config/base.json deleted file mode 100644 index 5117f2a3d1..0000000000 --- a/repo/typescript-config/base.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "esModuleInterop": true, - "incremental": false, - "isolatedModules": true, - "lib": ["es2022", "DOM", "DOM.Iterable"], - "module": "NodeNext", - "moduleDetection": "force", - "moduleResolution": "NodeNext", - "noUncheckedIndexedAccess": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "target": "ES2022" - } -} diff --git a/repo/typescript-config/nextjs.json b/repo/typescript-config/nextjs.json deleted file mode 100644 index e6defa48fc..0000000000 --- a/repo/typescript-config/nextjs.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./base.json", - "compilerOptions": { - "plugins": [{ "name": "next" }], - "module": "ESNext", - "moduleResolution": "Bundler", - "allowJs": true, - "jsx": "preserve", - "noEmit": true - } -} diff --git a/repo/typescript-config/package.json b/repo/typescript-config/package.json deleted file mode 100644 index ed931bce61..0000000000 --- a/repo/typescript-config/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@repo/typescript-config", - "version": "0.0.1", - "private": true, - "license": "MIT", - "publishConfig": { - "access": "public" - } -} diff --git a/repo/typescript-config/react-library.json b/repo/typescript-config/react-library.json deleted file mode 100644 index c3a1b26fbb..0000000000 --- a/repo/typescript-config/react-library.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./base.json", - "compilerOptions": { - "jsx": "react-jsx" - } -} diff --git a/repo/ui/CHANGELOG.md b/repo/ui/CHANGELOG.md deleted file mode 100644 index 232f9accb7..0000000000 --- a/repo/ui/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# @repo/ui - -## 0.0.1 - -### Patch Changes - -- d5017e8: Beta release for v3 -- 7c6c811: 3.0.0-beta.3 with fixes - -## 0.0.1-beta.1 - -### Patch Changes - -- Beta release for v3 - -## 0.0.1-beta.0 - -### Patch Changes - -- 3.0.0-beta.3 with fixes diff --git a/repo/ui/eslint.config.js b/repo/ui/eslint.config.js deleted file mode 100644 index 19170f88ed..0000000000 --- a/repo/ui/eslint.config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from "@repo/eslint-config/react-internal"; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/repo/ui/package.json b/repo/ui/package.json deleted file mode 100644 index f0045e54d0..0000000000 --- a/repo/ui/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@repo/ui", - "version": "0.0.1", - "private": true, - "type": "module", - "exports": { - "./button": "./src/button.tsx", - "./card": "./src/card.tsx", - "./code": "./src/code.tsx" - }, - "scripts": { - "lint": "eslint . --max-warnings 0", - "generate:component": "turbo gen react-component", - "typecheck": "tsc --noEmit" - }, - "devDependencies": { - "@repo/eslint-config": "workspace:^", - "@repo/typescript-config": "workspace:^", - "@turbo/gen": "^1.13.4", - "@types/node": "^25.3.0", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "typescript": "^5.9.3" - }, - "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3" - } -} diff --git a/repo/ui/src/button.tsx b/repo/ui/src/button.tsx deleted file mode 100644 index 2cb47bb9c2..0000000000 --- a/repo/ui/src/button.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client' - -import { ReactNode } from 'react' - -interface ButtonProps { - children: ReactNode - className?: string - appName: string -} - -export const Button = ({ children, className, appName }: ButtonProps) => { - return ( - - ) -} diff --git a/repo/ui/src/card.tsx b/repo/ui/src/card.tsx deleted file mode 100644 index a38d566e06..0000000000 --- a/repo/ui/src/card.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { type JSX } from 'react' - -export function Card({ - className, - title, - children, - href, -}: { - className?: string - title: string - children: React.ReactNode - href: string -}): JSX.Element { - return ( -
-

- {title} -> -

-

{children}

-
- ) -} diff --git a/repo/ui/src/code.tsx b/repo/ui/src/code.tsx deleted file mode 100644 index af16618ae8..0000000000 --- a/repo/ui/src/code.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { type JSX } from 'react' - -export function Code({ children, className }: { children: React.ReactNode; className?: string }): JSX.Element { - return {children} -} diff --git a/repo/ui/tsconfig.json b/repo/ui/tsconfig.json deleted file mode 100644 index ca86687c4b..0000000000 --- a/repo/ui/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@repo/typescript-config/react-library.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src"], - "exclude": ["node_modules", "dist"] -} diff --git a/repo/ui/turbo/generators/config.ts b/repo/ui/turbo/generators/config.ts deleted file mode 100644 index 08bff62ad5..0000000000 --- a/repo/ui/turbo/generators/config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { PlopTypes } from '@turbo/gen' - -// Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation - -export default function generator(plop: PlopTypes.NodePlopAPI): void { - // A simple generator to add a new React component to the internal UI library - plop.setGenerator('react-component', { - description: 'Adds a new react component', - prompts: [ - { - type: 'input', - name: 'name', - message: 'What is the name of the component?', - }, - ], - actions: [ - { - type: 'add', - path: 'src/{{kebabCase name}}.tsx', - templateFile: 'templates/component.hbs', - }, - { - type: 'append', - path: 'package.json', - pattern: /"exports": {(?)/g, - template: ' "./{{kebabCase name}}": "./src/{{kebabCase name}}.tsx",', - }, - ], - }) -} diff --git a/repo/ui/turbo/generators/templates/component.hbs b/repo/ui/turbo/generators/templates/component.hbs deleted file mode 100644 index d968b9e3a8..0000000000 --- a/repo/ui/turbo/generators/templates/component.hbs +++ /dev/null @@ -1,8 +0,0 @@ -export const {{ pascalCase name }} = ({ children }: { children: React.ReactNode }) => { - return ( -
-

{{ pascalCase name }} Component

- {children} -
- ); -}; diff --git a/scripts/formatPackageJson.ts b/scripts/formatPackageJson.ts new file mode 100644 index 0000000000..a4d5a13e39 --- /dev/null +++ b/scripts/formatPackageJson.ts @@ -0,0 +1,36 @@ +import path from 'node:path' + +// Generates package.json files to be published to NPM with only the necessary fields. + +console.log('Formatting package.json files.') + +// Get all package.json files +const packagePaths = await Array.fromAsync( + new Bun.Glob('packages/**/package.json').scan(), +) + +let count = 0 +for (const packagePath of packagePaths) { + type Package = Record & { + name?: string | undefined + private?: boolean | undefined + } + const file = Bun.file(packagePath) + const packageJson = (await file.json()) as Package + + // Skip private packages + if (packageJson.private) continue + + count += 1 + console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) + + await Bun.write( + `${packagePath}.tmp`, + `${JSON.stringify(packageJson, undefined, 2)}\n`, + ) + + const { devDependencies: _dD, scripts: _s, ...rest } = packageJson + await Bun.write(packagePath, `${JSON.stringify(rest, undefined, 2)}\n`) +} + +console.log(`Done. Formatted ${count} ${count === 1 ? 'file' : 'files'}.`) diff --git a/scripts/generateProxyPackages.ts b/scripts/generateProxyPackages.ts new file mode 100644 index 0000000000..a53a7167b3 --- /dev/null +++ b/scripts/generateProxyPackages.ts @@ -0,0 +1,57 @@ +import fs from 'node:fs/promises' +import path from 'node:path' + +// Generates proxy packages for package.json#exports. + +console.log('Generating proxy packages.') + +// Get all package.json files +const packagePaths = await Array.fromAsync( + new Bun.Glob('packages/**/package.json').scan(), +) + +let count = 0 +for (const packagePath of packagePaths) { + type Package = Record & { + name?: string | undefined + private?: boolean | undefined + exports?: + | Record + | undefined + } + const file = Bun.file(packagePath) + const packageJson = (await file.json()) as Package + + // Skip private packages + if (packageJson.private) continue + if (!packageJson.exports) continue + + count += 1 + console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) + + const dir = path.resolve(path.dirname(packagePath)) + + for (const [key, exports] of Object.entries(packageJson.exports)) { + // Skip `package.json` export + if (/package\.json$/.test(key)) continue + if (key === '.') continue + if (typeof exports === 'string') continue + if (!exports.default) continue + + const proxyDir = path.resolve(dir, key) + await fs.mkdir(proxyDir, { recursive: true }) + + const types = path.relative(key, exports.types) + const main = path.relative(key, exports.default) + await Bun.write( + `${proxyDir}/package.json`, + `${JSON.stringify({ type: 'module', types, main }, undefined, 2)}\n`, + ) + } +} + +console.log( + `Done. Generated proxy packages for ${count} ${ + count === 1 ? 'package' : 'packages' + }.`, +) diff --git a/scripts/preconstruct.ts b/scripts/preconstruct.ts new file mode 100644 index 0000000000..9580924f1f --- /dev/null +++ b/scripts/preconstruct.ts @@ -0,0 +1,87 @@ +import fs from 'node:fs/promises' +import path from 'node:path' + +// Symlinks package sources to dist for local development + +console.log('Setting up packages for development.') + +// Get all package.json files +const packagePaths = await Array.fromAsync( + new Bun.Glob('**/package.json').scan(), +) + +let count = 0 +for (const packagePath of packagePaths) { + type Package = { + bin?: Record | undefined + exports?: + | Record + | undefined + name?: string | undefined + private?: boolean | undefined + } + const file = Bun.file(packagePath) + const packageJson = (await file.json()) as Package + + // Skip private packages + if (packageJson.private && packageJson.name !== '@wagmi/test') continue + if (!packageJson.exports) continue + if (packageJson.bin) continue + + count += 1 + console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) + + const dir = path.resolve(path.dirname(packagePath)) + + // Empty dist directory + const dist = path.resolve(dir, 'dist') + let files: string[] = [] + try { + files = await fs.readdir(dist) + } catch { + await fs.mkdir(dist) + } + + const promises = [] + for (const file of files) { + promises.push( + fs.rm(path.join(dist, file), { recursive: true, force: true }), + ) + } + await Promise.all(promises) + + // Link exports to dist locations + for (const [key, exports] of Object.entries(packageJson.exports)) { + // Skip `package.json` exports + if (/package\.json$/.test(key)) continue + if (typeof exports === 'string') continue + + // Link exports to dist locations + for (const [type, value] of Object.entries(exports) as [ + type: 'types' | 'default', + value: string, + ][]) { + const srcDir = path.resolve( + dir, + path + .dirname(value) + .replace(`dist/${type === 'default' ? 'esm' : type}`, 'src'), + ) + let srcFileName: string + if (key === '.') srcFileName = 'index.ts' + else srcFileName = path.basename(`${key}.ts`) + const srcFilePath = path.resolve(srcDir, srcFileName) + + const distDir = path.resolve(dir, path.dirname(value)) + const distFileName = path.basename(value) + const distFilePath = path.resolve(distDir, distFileName) + + await fs.mkdir(distDir, { recursive: true }) + + // Symlink src to dist file + await fs.symlink(srcFilePath, distFilePath, 'file') + } + } +} + +console.log(`Done. Set up ${count} ${count === 1 ? 'package' : 'packages'}.`) diff --git a/scripts/restorePackageJson.ts b/scripts/restorePackageJson.ts new file mode 100644 index 0000000000..c0019a340f --- /dev/null +++ b/scripts/restorePackageJson.ts @@ -0,0 +1,29 @@ +import fs from 'node:fs/promises' +import path from 'node:path' + +// Restores package.json files from package.json.tmp files. + +console.log('Restoring package.json files.') + +// Get all package.json files +const packagePaths = await Array.fromAsync( + new Bun.Glob('packages/**/package.json.tmp').scan(), +) + +let count = 0 +for (const packagePath of packagePaths) { + type Package = { name?: string | undefined } & Record + const file = Bun.file(packagePath) + const packageJson = (await file.json()) as Package + + count += 1 + console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) + + await Bun.write( + packagePath.replace('.tmp', ''), + `${JSON.stringify(packageJson, undefined, 2)}\n`, + ) + await fs.rm(packagePath) +} + +console.log(`Done. Restored ${count} ${count === 1 ? 'file' : 'files'}.`) diff --git a/scripts/updateBlockExplorerPluginChains.ts b/scripts/updateBlockExplorerPluginChains.ts new file mode 100644 index 0000000000..d61790fd36 --- /dev/null +++ b/scripts/updateBlockExplorerPluginChains.ts @@ -0,0 +1,53 @@ +// Fetches supported chains for Etherscan and Sourcify + +console.log('Updating block explorer plugins chains.') + +let count = 0 +{ + console.log('etherscan - https://api.etherscan.io/v2/chainlist') + const res = (await fetch('https://api.etherscan.io/v2/chainlist').then( + (res) => res.json(), + )) as { + totalcount: number + result: { + chainname: string + chainid: number + blockexplorer: string + apiurl: string + status: 0 | 1 + }[] + } + + let content = 'type ChainId =\n' + for (const chain of res.result) + content += ` | ${chain.chainid} // ${chain.chainname}\n` + + await writeContent('./packages/cli/src/plugins/etherscan.ts', content) + count += 1 +} + +{ + console.log('sourcify - https://sourcify.dev/server/chains') + const res = (await fetch('https://sourcify.dev/server/chains').then((res) => + res.json(), + )) as { + name: string + chainId: number + }[] + + let content = 'type ChainId =\n' + for (const chain of res) content += ` | ${chain.chainId} // ${chain.name}\n` + + await writeContent('./packages/cli/src/plugins/sourcify.ts', content) + count += 1 +} + +console.log(`Done. Updated chains for ${count} plugins.`) + +async function writeContent(pluginPath: string, content: string) { + const file = Bun.file(pluginPath) + const text = await file + .text() + .then((text) => text.replace(/type ChainId =[\s\S]*$/, content)) + await Bun.write(pluginPath, text) +} diff --git a/scripts/updateVersion.ts b/scripts/updateVersion.ts new file mode 100644 index 0000000000..4ec949cc10 --- /dev/null +++ b/scripts/updateVersion.ts @@ -0,0 +1,43 @@ +import path from 'node:path' + +// Updates package version.ts files (so you can use the version in code without importing package.json). + +console.log('Updating version files.') + +// Get all package.json files +const packagePaths = await Array.fromAsync( + new Bun.Glob('**/package.json').scan(), +) + +let count = 0 +for (const packagePath of packagePaths) { + type Package = { + name?: string | undefined + private?: boolean | undefined + version?: string | undefined + } + const file = Bun.file(packagePath) + const packageJson = (await file.json()) as Package + + // Skip private packages + if (packageJson.private) continue + + count += 1 + console.log(`${packageJson.name} — ${packageJson.version}`) + + const versionFilePath = path.resolve( + path.dirname(packagePath), + 'src', + 'version.ts', + ) + await Bun.write( + versionFilePath, + `export const version = '${packageJson.version}'\n`, + ) +} + +console.log( + `Done. Updated version file for ${count} ${ + count === 1 ? 'package' : 'packages' + }.`, +) diff --git a/scripts/updateViemVersion.ts b/scripts/updateViemVersion.ts new file mode 100644 index 0000000000..5731394bad --- /dev/null +++ b/scripts/updateViemVersion.ts @@ -0,0 +1,44 @@ +// Updates viem version in Vitest snapshots, etc. + +console.log('Updating Viem version.') + +const file = Bun.file('package.json') +const packageJson = await file.json() +const viemVersion = packageJson.devDependencies.viem + +// Update Vitest snapshots +// Get all *.test.ts files +const testPaths = await Array.fromAsync( + new Bun.Glob('packages/**/*.test.ts').scan(), +) + +let count = 0 +for (const testPath of testPaths) { + const file = Bun.file(testPath) + const testFile = await file.text() + + // Skip files that don't contain viem version + if (!testFile.includes('Version: viem@')) continue + // Skip files that contain current version + if (testFile.includes(`Version: viem@${viemVersion}`)) continue + + console.log(testPath) + const updatedTestFile = testFile.replace( + /Version: viem@[A-Za-z0-9\-\.]+/g, + `Version: viem@${viemVersion}`, + ) + await Bun.write(testPath, updatedTestFile) + + count += 1 +} + +// // Update package.json#pnpm.overrides.viem +// if (packageJson.pnpm?.overrides?.viem !== viemVersion) { +// const path = 'package.json' +// console.log(path) +// packageJson.pnpm.overrides.viem = viemVersion +// await Bun.write(path, `${JSON.stringify(packageJson, undefined, 2)}\n`) +// count += 1 +// } + +console.log(`Done. Updated ${count} ${count === 1 ? 'file' : 'files'}.`) diff --git a/site/.vitepress/config.ts b/site/.vitepress/config.ts new file mode 100644 index 0000000000..77f242cb09 --- /dev/null +++ b/site/.vitepress/config.ts @@ -0,0 +1,151 @@ +import { + defaultHoverInfoProcessor, + transformerTwoslash, +} from '@shikijs/vitepress-twoslash' +import { presetAttributify, presetIcons, presetUno } from 'unocss' +import Unocss from 'unocss/vite' +import { defineConfig } from 'vitepress' + +import { farcasterIcon } from './constants' +import { getSidebar } from './sidebar' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + cleanUrls: true, + description: 'Reactivity for Ethereum apps', + head: [ + [ + 'meta', + { + name: 'keywords', + content: 'react, ethereum, typescript, react, react hooks, open source', + }, + ], + ['link', { rel: 'icon', href: '/favicon.svg' }], + ['meta', { name: 'theme-color', content: '#646cff' }], + // Open Graph + ['meta', { property: 'og:type', content: 'website' }], + ['meta', { property: 'og:image', content: 'https://wagmi.sh/og.png' }], + ['meta', { property: 'og:url', content: 'https://wagmi.sh' }], + // Twitter + ['meta', { name: 'twitter:card', content: 'summary_large_image' }], + ['meta', { name: 'twitter:creator', content: '@wevm_dev' }], + ['meta', { name: 'twitter:image', content: 'https://wagmi.sh/og.png' }], + ['meta', { name: 'twitter:site', content: 'wagmi.sh' }], + // Fathom + [ + 'script', + { + src: 'https://cdn.usefathom.com/script.js', + 'data-site': 'QWAXSUPT', + defer: '', + }, + ], + ], + ignoreDeadLinks: false, + lang: 'en-US', + lastUpdated: true, + markdown: { + codeTransformers: [ + transformerTwoslash({ + processHoverInfo(info) { + return ( + defaultHoverInfoProcessor(info) + // Remove shiki_core namespace + .replace(/_shikijs_core[\w_]*\./g, '') + ) + }, + }), + ], + theme: { + light: 'vitesse-light', + dark: 'vitesse-dark', + }, + }, + themeConfig: { + editLink: { + pattern: 'https://github.com/wevm/wagmi/edit/main/site/:path', + text: 'Suggest changes to this page', + }, + footer: { + message: + 'Released under the MIT License.', + copyright: 'Copyright © 2022-present Weth, LLC', + }, + logo: { + light: '/logo-light.svg', + dark: '/logo-dark.svg', + alt: 'wagmi logo', + }, + nav: [ + { text: 'React', link: '/react/getting-started' }, + { text: 'Vue', link: '/vue/getting-started' }, + { text: 'Core', link: '/core/getting-started' }, + { text: 'CLI', link: '/cli/getting-started' }, + // { text: 'Examples', link: '/examples/connect-wallet' }, + { + text: 'More', + items: [ + { + text: 'Discussions ', + link: 'https://github.com/wevm/wagmi/discussions', + }, + { + text: 'Release Notes ', + link: 'https://github.com/wevm/wagmi/releases', + }, + { + text: 'Contributing ', + link: '/dev/contributing', + }, + ], + }, + ], + outline: [2, 3], + search: { + provider: 'local', + options: { + _render(src, env, md) { + const html = md.render(src, env) + if (env.frontmatter?.search === false) return '' + if (env.relativePath.startsWith('shared')) return '' + return html + }, + }, + }, + sidebar: getSidebar(), + siteTitle: false, + socialLinks: [ + { + icon: 'github', + link: 'https://github.com/wevm/wagmi', + }, + { icon: 'bluesky', link: 'https://bsky.app/profile/wevm.dev' }, + { icon: 'x', link: 'https://twitter.com/wevm_dev' }, + { icon: { svg: farcasterIcon }, link: 'https://warpcast.com/wevm' }, + { icon: 'discord', link: 'https://discord.gg/9zHPXuBpqy' }, + ], + }, + title: 'Wagmi', + vite: { + plugins: [ + Unocss({ + shortcuts: [ + [ + 'btn', + 'px-4 py-1 rounded inline-flex justify-center gap-2 text-white leading-30px children:mya !no-underline cursor-pointer disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50', + ], + ], + presets: [ + presetUno({ + dark: 'media', + }), + presetAttributify(), + presetIcons({ + scale: 1.2, + }), + ], + }), + ], + }, +}) diff --git a/site/.vitepress/constants.ts b/site/.vitepress/constants.ts new file mode 100644 index 0000000000..981f8df3e1 --- /dev/null +++ b/site/.vitepress/constants.ts @@ -0,0 +1,2 @@ +export const farcasterIcon = + '' diff --git a/site/.vitepress/sidebar.ts b/site/.vitepress/sidebar.ts new file mode 100644 index 0000000000..29ea14bc52 --- /dev/null +++ b/site/.vitepress/sidebar.ts @@ -0,0 +1,1085 @@ +import type { DefaultTheme } from 'vitepress' + +export function getSidebar() { + return { + '/react': [ + { + text: 'Introduction', + items: [ + { text: 'Why Wagmi', link: '/react/why' }, + { text: 'Installation', link: '/react/installation' }, + { text: 'Getting Started', link: '/react/getting-started' }, + { text: 'TypeScript', link: '/react/typescript' }, + { text: 'Comparisons', link: '/react/comparisons' }, + ], + }, + { + text: 'Guides', + items: [ + { + text: 'TanStack Query', + link: '/react/guides/tanstack-query', + }, + { + text: 'Viem', + link: '/react/guides/viem', + }, + { + text: 'Error Handling', + link: '/react/guides/error-handling', + }, + { + text: 'Ethers.js Adapters', + link: '/react/guides/ethers', + }, + // { + // text: 'Testing', + // link: '/react/guides/testing', + // }, + { + text: 'Chain Properties', + link: '/react/guides/chain-properties', + }, + { + text: 'SSR', + link: '/react/guides/ssr', + }, + { + text: 'Connect Wallet', + link: '/react/guides/connect-wallet', + }, + { + text: 'Send Transaction', + link: '/react/guides/send-transaction', + }, + { + text: 'Read from Contract', + link: '/react/guides/read-from-contract', + }, + { + text: 'Write to Contract', + link: '/react/guides/write-to-contract', + }, + { + text: 'FAQ / Troubleshooting', + link: '/react/guides/faq', + }, + { + text: 'Migrate from v1 to v2', + link: '/react/guides/migrate-from-v1-to-v2', + }, + ], + }, + { + text: 'Configuration', + items: [ + { text: 'createConfig', link: '/react/api/createConfig' }, + { text: 'createStorage', link: '/react/api/createStorage' }, + { text: 'Chains', link: '/react/api/chains' }, + { + text: 'Connectors', + collapsed: true, + link: '/react/api/connectors', + items: [ + { + text: 'coinbaseWallet', + link: '/react/api/connectors/coinbaseWallet', + }, + { text: 'injected', link: '/react/api/connectors/injected' }, + { + text: 'metaMask', + link: '/react/api/connectors/metaMask', + }, + { + text: 'mock', + link: '/react/api/connectors/mock', + }, + { + text: 'safe', + link: '/react/api/connectors/safe', + }, + { + text: 'walletConnect', + link: '/react/api/connectors/walletConnect', + }, + ], + }, + { + text: 'Transports', + collapsed: true, + link: '/react/api/transports', + items: [ + { + text: 'custom (EIP-1193)', + link: '/react/api/transports/custom', + }, + { + text: 'fallback', + link: '/react/api/transports/fallback', + }, + { + text: 'http', + link: '/react/api/transports/http', + }, + { + text: 'unstable_connector', + link: '/react/api/transports/unstable_connector', + }, + { + text: 'webSocket', + link: '/react/api/transports/webSocket', + }, + ], + }, + { text: 'WagmiProvider', link: '/react/api/WagmiProvider' }, + ], + }, + { + text: 'Hooks', + link: '/react/api/hooks', + items: [ + { text: 'useAccount', link: '/react/api/hooks/useAccount' }, + { + text: 'useAccountEffect', + link: '/react/api/hooks/useAccountEffect', + }, + { text: 'useBalance', link: '/react/api/hooks/useBalance' }, + { + text: 'useBlockNumber', + link: '/react/api/hooks/useBlockNumber', + }, + { + text: 'useBlock', + link: '/react/api/hooks/useBlock', + }, + { + text: 'useBlockTransactionCount', + link: '/react/api/hooks/useBlockTransactionCount', + }, + { + text: 'useBytecode', + link: '/react/api/hooks/useBytecode', + }, + { text: 'useCall', link: '/react/api/hooks/useCall' }, + { + text: 'useCallsStatus', + link: '/react/api/hooks/useCallsStatus', + }, + { + text: 'useCapabilities', + link: '/react/api/hooks/useCapabilities', + }, + { text: 'useChainId', link: '/react/api/hooks/useChainId' }, + { text: 'useChains', link: '/react/api/hooks/useChains' }, + { text: 'useClient', link: '/react/api/hooks/useClient' }, + { text: 'useConfig', link: '/react/api/hooks/useConfig' }, + { text: 'useConnect', link: '/react/api/hooks/useConnect' }, + { + text: 'useConnections', + link: '/react/api/hooks/useConnections', + }, + { + text: 'useConnectorClient', + link: '/react/api/hooks/useConnectorClient', + }, + { + text: 'useConnectors', + link: '/react/api/hooks/useConnectors', + }, + { + text: 'useDeployContract', + link: '/react/api/hooks/useDeployContract', + }, + { text: 'useDisconnect', link: '/react/api/hooks/useDisconnect' }, + { text: 'useEnsAddress', link: '/react/api/hooks/useEnsAddress' }, + { text: 'useEnsAvatar', link: '/react/api/hooks/useEnsAvatar' }, + { text: 'useEnsName', link: '/react/api/hooks/useEnsName' }, + { + text: 'useEnsResolver', + link: '/react/api/hooks/useEnsResolver', + }, + { + text: 'useEnsText', + link: '/react/api/hooks/useEnsText', + }, + { + text: 'useFeeHistory', + link: '/react/api/hooks/useFeeHistory', + }, + { + text: 'useProof', + link: '/react/api/hooks/useProof', + }, + { + text: 'usePublicClient', + link: '/react/api/hooks/usePublicClient', + }, + { + text: 'useEstimateFeesPerGas', + link: '/react/api/hooks/useEstimateFeesPerGas', + }, + { + text: 'useEstimateGas', + link: '/react/api/hooks/useEstimateGas', + }, + { + text: 'useEstimateMaxPriorityFeePerGas', + link: '/react/api/hooks/useEstimateMaxPriorityFeePerGas', + }, + { + text: 'useGasPrice', + link: '/react/api/hooks/useGasPrice', + }, + { + text: 'useInfiniteReadContracts', + link: '/react/api/hooks/useInfiniteReadContracts', + }, + { + text: 'usePrepareTransactionRequest', + link: '/react/api/hooks/usePrepareTransactionRequest', + }, + { + text: 'useReadContract', + link: '/react/api/hooks/useReadContract', + }, + { + text: 'useReadContracts', + link: '/react/api/hooks/useReadContracts', + }, + { text: 'useReconnect', link: '/react/api/hooks/useReconnect' }, + { + text: 'useSendCalls', + link: '/react/api/hooks/useSendCalls', + }, + { + text: 'useSendTransaction', + link: '/react/api/hooks/useSendTransaction', + }, + { + text: 'useShowCallsStatus', + link: '/react/api/hooks/useShowCallsStatus', + }, + { + text: 'useSignMessage', + link: '/react/api/hooks/useSignMessage', + }, + { + text: 'useSignTypedData', + link: '/react/api/hooks/useSignTypedData', + }, + { + text: 'useSimulateContract', + link: '/react/api/hooks/useSimulateContract', + }, + { + text: 'useStorageAt', + link: '/react/api/hooks/useStorageAt', + }, + { + text: 'useSwitchAccount', + link: '/react/api/hooks/useSwitchAccount', + }, + { + text: 'useSwitchChain', + link: '/react/api/hooks/useSwitchChain', + }, + { + text: 'useTransaction', + link: '/react/api/hooks/useTransaction', + }, + { + text: 'useTransactionConfirmations', + link: '/react/api/hooks/useTransactionConfirmations', + }, + { + text: 'useTransactionCount', + link: '/react/api/hooks/useTransactionCount', + }, + { + text: 'useTransactionReceipt', + link: '/react/api/hooks/useTransactionReceipt', + }, + { + text: 'useToken', + link: '/react/api/hooks/useToken', + }, + { + text: 'useWaitForCallsStatus', + link: '/react/api/hooks/useWaitForCallsStatus', + }, + { + text: 'useWaitForTransactionReceipt', + link: '/react/api/hooks/useWaitForTransactionReceipt', + }, + { + text: 'useVerifyMessage', + link: '/react/api/hooks/useVerifyMessage', + }, + { + text: 'useVerifyTypedData', + link: '/react/api/hooks/useVerifyTypedData', + }, + { + text: 'useWalletClient', + link: '/react/api/hooks/useWalletClient', + }, + { + text: 'useWatchAsset', + link: '/react/api/hooks/useWatchAsset', + }, + { + text: 'useWatchBlocks', + link: '/react/api/hooks/useWatchBlocks', + }, + { + text: 'useWatchBlockNumber', + link: '/react/api/hooks/useWatchBlockNumber', + }, + { + text: 'useWatchContractEvent', + link: '/react/api/hooks/useWatchContractEvent', + }, + { + text: 'useWatchPendingTransactions', + link: '/react/api/hooks/useWatchPendingTransactions', + }, + { + text: 'useWriteContract', + link: '/react/api/hooks/useWriteContract', + }, + ], + }, + { + text: 'Miscellaneous', + items: [ + { text: 'Actions', link: '/react/api/actions' }, + { text: 'Errors', link: '/react/api/errors' }, + { + text: 'Utilities', + collapsed: true, + items: [ + { + text: 'cookieToInitialState', + link: '/react/api/utilities/cookieToInitialState', + }, + { text: 'deserialize', link: '/react/api/utilities/deserialize' }, + { + text: 'normalizeChainId', + link: '/react/api/utilities/normalizeChainId', + }, + { text: 'serialize', link: '/react/api/utilities/serialize' }, + ], + }, + ], + }, + ], + '/vue': [ + { + text: 'Introduction', + items: [ + { text: 'Why Wagmi', link: '/vue/why' }, + { text: 'Installation', link: '/vue/installation' }, + { text: 'Getting Started', link: '/vue/getting-started' }, + { text: 'TypeScript', link: '/vue/typescript' }, + ], + }, + { + text: 'Guides', + items: [ + { + text: 'TanStack Query', + link: '/vue/guides/tanstack-query', + }, + { + text: 'Viem', + link: '/vue/guides/viem', + }, + { + text: 'Error Handling', + link: '/vue/guides/error-handling', + }, + { + text: 'Chain Properties', + link: '/vue/guides/chain-properties', + }, + { + text: 'SSR', + link: '/vue/guides/ssr', + }, + { + text: 'Connect Wallet', + link: '/vue/guides/connect-wallet', + }, + { + text: 'Send Transaction', + link: '/vue/guides/send-transaction', + }, + { + text: 'Read from Contract', + link: '/vue/guides/read-from-contract', + }, + { + text: 'Write to Contract', + link: '/vue/guides/write-to-contract', + }, + { + text: 'FAQ / Troubleshooting', + link: '/vue/guides/faq', + }, + ], + }, + { + text: 'Configuration', + items: [ + { text: 'createConfig', link: '/vue/api/createConfig' }, + { text: 'createStorage', link: '/vue/api/createStorage' }, + { text: 'Chains', link: '/vue/api/chains' }, + { + text: 'Connectors', + collapsed: true, + link: '/vue/api/connectors', + items: [ + { + text: 'coinbaseWallet', + link: '/vue/api/connectors/coinbaseWallet', + }, + { text: 'injected', link: '/vue/api/connectors/injected' }, + { + text: 'metaMask', + link: '/vue/api/connectors/metaMask', + }, + { + text: 'mock', + link: '/vue/api/connectors/mock', + }, + { + text: 'safe', + link: '/vue/api/connectors/safe', + }, + { + text: 'walletConnect', + link: '/vue/api/connectors/walletConnect', + }, + ], + }, + { + text: 'Transports', + collapsed: true, + link: '/vue/api/transports', + items: [ + { + text: 'custom (EIP-1193)', + link: '/vue/api/transports/custom', + }, + { + text: 'fallback', + link: '/vue/api/transports/fallback', + }, + { + text: 'http', + link: '/vue/api/transports/http', + }, + { + text: 'unstable_connector', + link: '/vue/api/transports/unstable_connector', + }, + { + text: 'webSocket', + link: '/vue/api/transports/webSocket', + }, + ], + }, + { text: 'WagmiPlugin', link: '/vue/api/WagmiPlugin' }, + { text: 'Nuxt', link: '/vue/api/Nuxt' }, + ], + }, + { + text: 'Composables', + link: '/vue/api/composables', + items: [ + { text: 'useAccount', link: '/vue/api/composables/useAccount' }, + { + text: 'useAccountEffect', + link: '/vue/api/composables/useAccountEffect', + }, + { + text: 'useBalance', + link: '/vue/api/composables/useBalance', + }, + { + text: 'useBlockNumber', + link: '/vue/api/composables/useBlockNumber', + }, + { + text: 'useBytecode', + link: '/vue/api/composables/useBytecode', + }, + { text: 'useChainId', link: '/vue/api/composables/useChainId' }, + { text: 'useChains', link: '/vue/api/composables/useChains' }, + { text: 'useClient', link: '/vue/api/composables/useClient' }, + { text: 'useConfig', link: '/vue/api/composables/useConfig' }, + { text: 'useConnect', link: '/vue/api/composables/useConnect' }, + { + text: 'useConnections', + link: '/vue/api/composables/useConnections', + }, + { + text: 'useConnectorClient', + link: '/vue/api/composables/useConnectorClient', + }, + { + text: 'useConnectors', + link: '/vue/api/composables/useConnectors', + }, + { + text: 'useDisconnect', + link: '/vue/api/composables/useDisconnect', + }, + { + text: 'useEnsAddress', + link: '/vue/api/composables/useEnsAddress', + }, + { + text: 'useEnsAvatar', + link: '/vue/api/composables/useEnsAvatar', + }, + { + text: 'useEstimateGas', + link: '/vue/api/composables/useEstimateGas', + }, + { + text: 'useReadContract', + link: '/vue/api/composables/useReadContract', + }, + { + text: 'useReconnect', + link: '/vue/api/composables/useReconnect', + }, + { + text: 'useSendTransaction', + link: '/vue/api/composables/useSendTransaction', + }, + { + text: 'useSignMessage', + link: '/vue/api/composables/useSignMessage', + }, + { + text: 'useSignTypedData', + link: '/vue/api/composables/useSignTypedData', + }, + { + text: 'useSimulateContract', + link: '/vue/api/composables/useSimulateContract', + }, + { + text: 'useSwitchAccount', + link: '/vue/api/composables/useSwitchAccount', + }, + { + text: 'useSwitchChain', + link: '/vue/api/composables/useSwitchChain', + }, + { + text: 'useTransaction', + link: '/vue/api/composables/useTransaction', + }, + { + text: 'useTransactionReceipt', + link: '/vue/api/composables/useTransactionReceipt', + }, + { + text: 'useWaitForTransactionReceipt', + link: '/vue/api/composables/useWaitForTransactionReceipt', + }, + { + text: 'useWatchBlockNumber', + link: '/vue/api/composables/useWatchBlockNumber', + }, + { + text: 'useWatchContractEvent', + link: '/vue/api/composables/useWatchContractEvent', + }, + { + text: 'useWriteContract', + link: '/vue/api/composables/useWriteContract', + }, + ], + }, + { + text: 'Miscellaneous', + items: [ + { text: 'Actions', link: '/vue/api/actions' }, + { text: 'Errors', link: '/vue/api/errors' }, + { + text: 'Utilities', + collapsed: true, + items: [ + { + text: 'deserialize', + link: '/vue/api/utilities/deserialize', + }, + { text: 'serialize', link: '/vue/api/utilities/serialize' }, + ], + }, + ], + }, + ], + '/core': [ + { + text: 'Introduction', + items: [ + { text: 'Why Wagmi', link: '/core/why' }, + { text: 'Installation', link: '/core/installation' }, + { text: 'Getting Started', link: '/core/getting-started' }, + { text: 'TypeScript', link: '/core/typescript' }, + ], + }, + { + text: 'Guides', + items: [ + { + text: 'Viem', + link: '/core/guides/viem', + }, + { + text: 'Framework Adapters', + link: '/core/guides/framework-adapters', + }, + { + text: 'Error Handling', + link: '/core/guides/error-handling', + }, + { + text: 'Ethers.js Adapters', + link: '/core/guides/ethers', + }, + // { + // text: 'Testing', + // link: '/core/guides/testing', + // }, + { + text: 'Chain Properties', + link: '/core/guides/chain-properties', + }, + { + text: 'FAQ / Troubleshooting', + link: '/core/guides/faq', + }, + { + text: 'Migrate from v1 to v2', + link: '/core/guides/migrate-from-v1-to-v2', + }, + ], + }, + { + text: 'Configuration', + items: [ + { text: 'createConfig', link: '/core/api/createConfig' }, + { text: 'createConnector', link: '/core/api/createConnector' }, + { text: 'createStorage', link: '/core/api/createStorage' }, + { text: 'Chains', link: '/core/api/chains' }, + { + text: 'Connectors', + collapsed: true, + link: '/core/api/connectors', + items: [ + { + text: 'coinbaseWallet', + link: '/core/api/connectors/coinbaseWallet', + }, + { text: 'injected', link: '/core/api/connectors/injected' }, + { + text: 'metaMask', + link: '/core/api/connectors/metaMask', + }, + { + text: 'mock', + link: '/core/api/connectors/mock', + }, + { + text: 'safe', + link: '/core/api/connectors/safe', + }, + { + text: 'walletConnect', + link: '/core/api/connectors/walletConnect', + }, + ], + }, + { + text: 'Transports', + collapsed: true, + link: '/core/api/transports', + items: [ + { + text: 'custom (EIP-1193)', + link: '/core/api/transports/custom', + }, + { + text: 'fallback', + link: '/core/api/transports/fallback', + }, + { + text: 'http', + link: '/core/api/transports/http', + }, + { + text: 'unstable_connector', + link: '/core/api/transports/unstable_connector', + }, + { + text: 'webSocket', + link: '/core/api/transports/webSocket', + }, + ], + }, + ], + }, + { + text: 'Actions', + link: '/core/api/actions', + items: [ + { + text: 'call', + link: '/core/api/actions/call', + }, + { text: 'connect', link: '/core/api/actions/connect' }, + { text: 'deployContract', link: '/core/api/actions/deployContract' }, + { text: 'disconnect', link: '/core/api/actions/disconnect' }, + { + text: 'estimateFeesPerGas', + link: '/core/api/actions/estimateFeesPerGas', + }, + { text: 'estimateGas', link: '/core/api/actions/estimateGas' }, + { + text: 'estimateMaxPriorityFeePerGas', + link: '/core/api/actions/estimateMaxPriorityFeePerGas', + }, + { text: 'getAccount', link: '/core/api/actions/getAccount' }, + { text: 'getBalance', link: '/core/api/actions/getBalance' }, + { + text: 'getBlock', + link: '/core/api/actions/getBlock', + }, + { + text: 'getBlockNumber', + link: '/core/api/actions/getBlockNumber', + }, + { + text: 'getBlockTransactionCount', + link: '/core/api/actions/getBlockTransactionCount', + }, + { + text: 'getBytecode', + link: '/core/api/actions/getBytecode', + }, + { + text: 'getCallsStatus', + link: '/core/api/actions/getCallsStatus', + }, + { + text: 'getCapabilities', + link: '/core/api/actions/getCapabilities', + }, + { text: 'getChainId', link: '/core/api/actions/getChainId' }, + { text: 'getChains', link: '/core/api/actions/getChains' }, + { + text: 'getClient', + link: '/core/api/actions/getClient', + }, + { + text: 'getConnections', + link: '/core/api/actions/getConnections', + }, + { + text: 'getConnectorClient', + link: '/core/api/actions/getConnectorClient', + }, + { + text: 'getConnectors', + link: '/core/api/actions/getConnectors', + }, + { + text: 'getEnsAddress', + link: '/core/api/actions/getEnsAddress', + }, + { text: 'getEnsAvatar', link: '/core/api/actions/getEnsAvatar' }, + { text: 'getEnsName', link: '/core/api/actions/getEnsName' }, + { + text: 'getEnsResolver', + link: '/core/api/actions/getEnsResolver', + }, + { + text: 'getEnsText', + link: '/core/api/actions/getEnsText', + }, + { + text: 'getFeeHistory', + link: '/core/api/actions/getFeeHistory', + }, + { + text: 'getGasPrice', + link: '/core/api/actions/getGasPrice', + }, + { + text: 'getProof', + link: '/core/api/actions/getProof', + }, + { + text: 'getPublicClient', + link: '/core/api/actions/getPublicClient', + }, + { + text: 'getStorageAt', + link: '/core/api/actions/getStorageAt', + }, + { text: 'getToken', link: '/core/api/actions/getToken' }, + { + text: 'getTransaction', + link: '/core/api/actions/getTransaction', + }, + { + text: 'getTransactionConfirmations', + link: '/core/api/actions/getTransactionConfirmations', + }, + { + text: 'getTransactionCount', + link: '/core/api/actions/getTransactionCount', + }, + { + text: 'getTransactionReceipt', + link: '/core/api/actions/getTransactionReceipt', + }, + { + text: 'getWalletClient', + link: '/core/api/actions/getWalletClient', + }, + { + text: 'multicall', + link: '/core/api/actions/multicall', + }, + { + text: 'prepareTransactionRequest', + link: '/core/api/actions/prepareTransactionRequest', + }, + { text: 'reconnect', link: '/core/api/actions/reconnect' }, + { + text: 'readContract', + link: '/core/api/actions/readContract', + }, + { + text: 'readContracts', + link: '/core/api/actions/readContracts', + }, + { + text: 'sendCalls', + link: '/core/api/actions/sendCalls', + }, + { + text: 'sendTransaction', + link: '/core/api/actions/sendTransaction', + }, + { + text: 'showCallsStatus', + link: '/core/api/actions/showCallsStatus', + }, + { + text: 'signMessage', + link: '/core/api/actions/signMessage', + }, + { + text: 'signTypedData', + link: '/core/api/actions/signTypedData', + }, + { + text: 'simulateContract', + link: '/core/api/actions/simulateContract', + }, + { + text: 'switchAccount', + link: '/core/api/actions/switchAccount', + }, + { + text: 'switchChain', + link: '/core/api/actions/switchChain', + }, + { + text: 'verifyMessage', + link: '/core/api/actions/verifyMessage', + }, + { + text: 'verifyTypedData', + link: '/core/api/actions/verifyTypedData', + }, + { + text: 'waitForCallsStatus', + link: '/core/api/actions/waitForCallsStatus', + }, + { + text: 'waitForTransactionReceipt', + link: '/core/api/actions/waitForTransactionReceipt', + }, + { + text: 'watchAccount', + link: '/core/api/actions/watchAccount', + }, + { + text: 'watchAsset', + link: '/core/api/actions/watchAsset', + }, + { + text: 'watchBlocks', + link: '/core/api/actions/watchBlocks', + }, + { + text: 'watchBlockNumber', + link: '/core/api/actions/watchBlockNumber', + }, + { + text: 'watchChainId', + link: '/core/api/actions/watchChainId', + }, + { + text: 'watchClient', + link: '/core/api/actions/watchClient', + }, + { + text: 'watchConnections', + link: '/core/api/actions/watchConnections', + }, + { + text: 'watchConnectors', + link: '/core/api/actions/watchConnectors', + }, + { + text: 'watchContractEvent', + link: '/core/api/actions/watchContractEvent', + }, + { + text: 'watchPendingTransactions', + link: '/core/api/actions/watchPendingTransactions', + }, + { + text: 'watchPublicClient', + link: '/core/api/actions/watchPublicClient', + }, + { + text: 'writeContract', + link: '/core/api/actions/writeContract', + }, + ], + }, + { + text: 'Miscellaneous', + items: [ + { text: 'Errors', link: '/core/api/errors' }, + { + text: 'Utilities', + collapsed: true, + items: [ + { + text: 'cookieToInitialState', + link: '/core/api/utilities/cookieToInitialState', + }, + { text: 'deserialize', link: '/core/api/utilities/deserialize' }, + { + text: 'normalizeChainId', + link: '/core/api/utilities/normalizeChainId', + }, + { text: 'serialize', link: '/core/api/utilities/serialize' }, + ], + }, + ], + }, + ], + '/cli': [ + { + text: 'Introduction', + items: [ + { text: 'Why Wagmi CLI', link: '/cli/why' }, + { text: 'Installation', link: '/cli/installation' }, + { text: 'Getting Started', link: '/cli/getting-started' }, + ], + }, + { + text: 'Guides', + items: [ + { + text: 'Migrate from v1 to v2', + link: '/cli/guides/migrate-from-v1-to-v2', + }, + ], + }, + { + text: 'Config File', + items: [ + { + text: 'Configuring CLI', + link: '/cli/config/configuring-cli', + }, + { text: 'Config Options', link: '/cli/config/options' }, + ], + }, + { + text: 'Commands', + link: '/cli/api/commands', + items: [ + { + text: 'generate', + link: '/cli/api/commands/generate', + }, + { + text: 'init', + link: '/cli/api/commands/init', + }, + ], + }, + { + text: 'Plugins', + link: '/cli/api/plugins', + items: [ + { text: 'actions', link: '/cli/api/plugins/actions' }, + { text: 'blockExplorer', link: '/cli/api/plugins/blockExplorer' }, + { text: 'etherscan', link: '/cli/api/plugins/etherscan' }, + { text: 'fetch', link: '/cli/api/plugins/fetch' }, + { text: 'foundry', link: '/cli/api/plugins/foundry' }, + { text: 'hardhat', link: '/cli/api/plugins/hardhat' }, + { text: 'react', link: '/cli/api/plugins/react' }, + { text: 'sourcify', link: '/cli/api/plugins/sourcify' }, + ], + }, + { + text: 'create-wagmi', + link: '/cli/create-wagmi', + }, + ], + '/dev': [ + { + text: 'Dev', + items: [ + { text: 'Contributing', link: '/dev/contributing' }, + { text: 'Creating Connectors', link: '/dev/creating-connectors' }, + ], + }, + ], + '/examples': [ + { + text: 'React', + items: [ + { text: 'Connect Wallet', link: '/examples/connect-wallet' }, + { text: 'Send Transaction', link: '/examples/send-transaction' }, + { text: 'Write Contract', link: '/examples/contract-write' }, + { + text: 'Write Contract (Dynamic Args)', + link: '/examples/contract-write-dynamic', + }, + { text: 'Sign Message', link: '/examples/sign-message' }, + { + text: 'Sign In With Ethereum', + link: '/examples/sign-in-with-ethereum', + }, + ], + }, + ], + } satisfies DefaultTheme.Sidebar +} diff --git a/site/.vitepress/theme/components/AsideSponsors.vue b/site/.vitepress/theme/components/AsideSponsors.vue new file mode 100644 index 0000000000..09e1075abb --- /dev/null +++ b/site/.vitepress/theme/components/AsideSponsors.vue @@ -0,0 +1,27 @@ + + + + diff --git a/site/.vitepress/theme/components/Banner.vue b/site/.vitepress/theme/components/Banner.vue new file mode 100644 index 0000000000..14ccf48b91 --- /dev/null +++ b/site/.vitepress/theme/components/Banner.vue @@ -0,0 +1,16 @@ +// TODO: Dismissable, a11y, etc. +// https://github.com/faker-js/faker/pull/1487 + + + + diff --git a/site/.vitepress/theme/components/HomeBanner.vue b/site/.vitepress/theme/components/HomeBanner.vue new file mode 100644 index 0000000000..dbbf1c4f89 --- /dev/null +++ b/site/.vitepress/theme/components/HomeBanner.vue @@ -0,0 +1,13 @@ + + + + diff --git a/site/.vitepress/theme/components/HomePage.vue b/site/.vitepress/theme/components/HomePage.vue new file mode 100644 index 0000000000..31e1e10574 --- /dev/null +++ b/site/.vitepress/theme/components/HomePage.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/site/.vitepress/theme/composables/useSponsors.ts b/site/.vitepress/theme/composables/useSponsors.ts new file mode 100644 index 0000000000..571a2b7df6 --- /dev/null +++ b/site/.vitepress/theme/composables/useSponsors.ts @@ -0,0 +1,212 @@ +import { onMounted, ref } from 'vue' + +type Sponsor = { + name: string + img: string + url: string +} + +type Data = { + size: 'big' | 'medium' | 'small' + items: Sponsor[] + tier: string + type: 'platinum' | 'gold' | 'silver' +}[] + +// shared data across instances so we load only once. +const data = ref() + +// TODO: Data powered +// const dataHost = 'https://sponsors.vuejs.org' +// const dataUrl = `${dataHost}/vite.json` + +export function useSponsors() { + onMounted(async () => { + if (data.value) return + + // const result = await fetch(dataUrl) + // const json = await result.json() + // console.log(json) + const sponsors = { + platinum: [ + { + name: 'Paradigm', + url: 'https://paradigm.xyz', + img: 'paradigm-light.svg', + }, + { + name: 'Ithaca', + url: 'https://ithaca.xyz', + img: 'ithaca-light.svg', + }, + ], + gold: [ + { + name: 'Stripe', + url: 'https://www.stripe.com', + img: 'stripe-light.svg', + }, + { + name: 'zkSync', + url: 'https://zksync.io', + img: 'zksync-light.svg', + }, + { + name: 'Linea', + url: 'https://linea.build', + img: 'linea-light.svg', + }, + { + name: 'Routescan', + url: 'https://routescan.io', + img: 'routescan-light.svg', + }, + ], + silver: [ + { + name: 'Family', + url: 'https://twitter.com/family', + img: 'family-light.svg', + }, + { + name: 'Context', + url: 'https://twitter.com/context', + img: 'context-light.svg', + }, + { + name: 'WalletConnect', + url: 'https://walletconnect.com', + img: 'walletconnect-light.svg', + }, + { + name: 'PartyDAO', + url: 'https://twitter.com/prtyDAO', + img: 'partydao-light.svg', + }, + { + name: 'SushiSwap', + url: 'https://www.sushi.com', + img: 'sushi-light.svg', + }, + { + name: 'Dynamic', + url: 'https://www.dynamic.xyz', + img: 'dynamic-light.svg', + }, + { + name: 'Privy', + url: 'https://privy.io', + img: 'privy-light.svg', + }, + { + name: 'PancakeSwap', + url: 'https://pancakeswap.finance', + img: 'pancake-light.svg', + }, + { + name: 'Celo', + url: 'https://celo.org', + img: 'celo-light.svg', + }, + { + name: 'Rainbow', + url: 'https://rainbow.me', + img: 'rainbow-light.svg', + }, + { + name: 'Pimlico', + url: 'https://pimlico.io', + img: 'pimlico-light.svg', + }, + { + name: 'Zora', + url: 'https://zora.co', + img: 'zora-light.svg', + }, + { + name: 'Lattice', + url: 'https://lattice.xyz', + img: 'lattice-light.svg', + }, + { + name: 'Supa', + url: 'https://twitter.com/supafinance', + img: 'supa-light.svg', + }, + { + name: 'Syndicate', + url: 'https://syndicate.io', + img: 'syndicate-light.svg', + }, + { + name: 'Reservoir', + url: 'https://reservoir.tools', + img: 'reservoir-light.svg', + }, + { + name: 'Uniswap', + url: 'https://uniswap.org', + img: 'uniswap-light.svg', + }, + { + name: 'Biconomy', + url: 'https://biconomy.io', + img: 'biconomy-light.svg', + }, + { + name: 'Thirdweb', + url: 'https://thirdweb.com', + img: 'thirdweb-light.svg', + }, + { + name: 'Polymarket', + url: 'https://polymarket.com', + img: 'polymarket-light.svg', + }, + { + name: 'Sequence', + url: 'https://sequence.xyz', + img: 'sequence-light.svg', + }, + ], + } + + data.value = mapSponsors(sponsors) + }) + + return { data } +} + +function mapSponsors(sponsors: { + platinum: Sponsor[] + gold: Sponsor[] + silver: Sponsor[] +}) { + return [ + { + size: 'big', + items: mapImgPath(sponsors.platinum), + tier: 'Collaborators', + type: 'platinum', + }, + { + size: 'medium', + items: mapImgPath(sponsors.gold), + tier: 'Large Enterprises', + type: 'gold', + }, + { + size: 'small', + items: mapImgPath(sponsors.silver), + tier: 'Small Enterprises', + type: 'silver', + }, + ] satisfies Data +} + +function mapImgPath(sponsors: Sponsor[]) { + return sponsors.map((sponsor) => ({ + ...sponsor, + img: `https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/${sponsor.img}`, + })) +} diff --git a/site/.vitepress/theme/index.ts b/site/.vitepress/theme/index.ts new file mode 100644 index 0000000000..abcc802176 --- /dev/null +++ b/site/.vitepress/theme/index.ts @@ -0,0 +1,30 @@ +import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' + +import '@shikijs/vitepress-twoslash/style.css' +import 'uno.css' +import './style.css' + +import AsideSponsors from './components/AsideSponsors.vue' +// import Banner from './components/Banner.vue' +import HomeBanner from './components/HomeBanner.vue' +import HomePage from './components/HomePage.vue' + +export default { + extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + 'aside-ads-before': () => h(AsideSponsors), + // 'doc-before': () => h(Banner), + 'home-features-after': () => h(HomePage), + 'home-hero-before': () => h(HomeBanner), + }) + }, + enhanceApp({ app }) { + app.use(TwoslashFloatingVue) + }, +} satisfies Theme diff --git a/site/.vitepress/theme/style.css b/site/.vitepress/theme/style.css new file mode 100644 index 0000000000..4ec569cf05 --- /dev/null +++ b/site/.vitepress/theme/style.css @@ -0,0 +1,148 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attached to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + +.vp-doc [class*="language-"] .has-focused-lines .line:not(.has-focus) { + filter: unset; + opacity: 0.3; +} + +.twoslash-error-line { + max-width: min-content; + white-space: wrap; +} diff --git a/site/cli/api/commands.md b/site/cli/api/commands.md new file mode 100644 index 0000000000..643397560b --- /dev/null +++ b/site/cli/api/commands.md @@ -0,0 +1,53 @@ +# Commands + +## Available Commands + +- [`init`](/cli/api/commands/init) Creates configuration file. +- [`generate`](/cli/api/commands/generate) Generates code based on configuration, using `contracts` and `plugins`. + +## Display Info + +### `-h`, `--help` + +Show help message when `-h`, `--help` flags appear. + +::: code-group +```bash [pnpm] +pnpm wagmi --help +``` + +```bash [npm] +npx wagmi --help +``` + +```bash [yarn] +yarn wagmi --help +``` + +```bash [bun] +bun wagmi --help +``` +::: + +### `-v`, `--version` + +Show version number when `-v`, `--version` flags appear. + +::: code-group +```bash [pnpm] +pnpm wagmi --version +``` + +```bash [npm] +npx wagmi --version +``` + +```bash [yarn] +yarn wagmi --version +``` + +```bash [bun] +bun wagmi --version +``` +::: + diff --git a/site/cli/api/commands/generate.md b/site/cli/api/commands/generate.md new file mode 100644 index 0000000000..d1f88e90a0 --- /dev/null +++ b/site/cli/api/commands/generate.md @@ -0,0 +1,49 @@ +# generate + +Generates code based on configuration, using `contracts` and `plugins`. + +## Usage + +```bash +wagmi generate +``` + +## Options + +### -c, --config \ + +`string` + +Path to config file. + +```bash +wagmi generate --config wagmi.config.ts +``` + +### -r, --root \ + +`string` + +Root path to resolve config from. + +```bash +wagmi generate --root path/to/root +``` + +### -w, --watch + +`boolean` + +Watch for changes (for plugins that support watch mode). + +```bash +wagmi generate --watch +``` + +### -h, --help + +Displays help message. + +```bash +wagmi generate --help +``` \ No newline at end of file diff --git a/site/cli/api/commands/init.md b/site/cli/api/commands/init.md new file mode 100644 index 0000000000..440315e9c7 --- /dev/null +++ b/site/cli/api/commands/init.md @@ -0,0 +1,40 @@ +# init + +Creates configuration file. If TypeScript is detected, the config file will use TypeScript and be named `wagmi.config.ts`. Otherwise, the config file will use JavaScript and be named `wagmi.config.js`. + +## Usage + +```bash +wagmi init +``` + +## Options + +### -c, --config \ + +`string` + +Path to config file. + +```bash +wagmi init --config wagmi.config.ts +``` + +### -r, --root \ + +`string` + +Root path to resolve config from. + +```bash +wagmi init --root path/to/root +``` + +### -h, --help + +Displays help message. + +```bash +wagmi init --help +``` + diff --git a/site/cli/api/plugins.md b/site/cli/api/plugins.md new file mode 100644 index 0000000000..54afdb19e1 --- /dev/null +++ b/site/cli/api/plugins.md @@ -0,0 +1,42 @@ +# Plugins + +Plugins for managing ABIs, generating code, and more. + +## Import + +Import via the `'@wagmi/cli/plugins'` entrypoint. + +```ts +import { etherscan } from '@wagmi/cli/plugins' +``` + +## Available Plugins + +- [`actions`](/cli/api/plugins/actions) Generate type-safe VanillaJS actions from configuration `contracts`. +- [`blockExplorer`](/cli/api/plugins/blockExplorer) Fetch ABIs from Block Explorers that support `?module=contract&action=getabi`. +- [`etherscan`](/cli/api/plugins/etherscan) Fetch ABIs from Etherscan and add into configuration. +- [`fetch`](/cli/api/plugins/fetch) Fetch and parse ABIs from network resource with `fetch`. +- [`foundry`](/cli/api/plugins/foundry) Generate ABIs and watch for Foundry project changes. +- [`hardhat`](/cli/api/plugins/hardhat) Generate ABIs and watch for Hardhat projects changes. +- [`react`](/cli/api/plugins/react) Generate type-safe React Hooks from configuration `contracts`. +- [`sourcify`](/cli/api/plugins/sourcify) Fetch ABIs from Sourcify from configuration `contracts`. + +## Create Plugin + +Creating plugins to hook into the CLI is quite simple. Plugins most commonly inject contracts into `contracts` config, e.g. [`etherscan`](/cli/api/plugins/etherscan), and/or generate code using the `run` option, e.g. [`react`](/cli/api/plugins/react). All you need to do is write a function that returns the `Plugin` type. + +```ts{3-8} +import { type Plugin, defineConfig } from '@wagmi/cli' + +function myPlugin(): Plugin { + // `name` is the only required property. + name: 'MyPlugin', + // You likely want to at least include `contracts` or `run`. + // ... +} + +export default defineConfig({ + out: 'src/generated.ts', + plugins: [myPlugin()], +}) +``` diff --git a/site/cli/api/plugins/actions.md b/site/cli/api/plugins/actions.md new file mode 100644 index 0000000000..d62a29a6b7 --- /dev/null +++ b/site/cli/api/plugins/actions.md @@ -0,0 +1,72 @@ +# actions + +Plugin for type-safe VanillaJS actions. + +## Import + +```ts +import { actions } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6} +import { defineConfig } from '@wagmi/cli' +import { actions } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + actions(), + ], +}) +``` + +## Configuration + +```ts +import { type ActionsConfig } from '@wagmi/cli/plugins' +``` + +### getActionName + +`` 'legacy' | ((options: { contractName: string; type: 'read' | 'simulate' | 'watch' | 'write' }) => `use${string}`) `` + +- Function for setting custom hook names. +- Defaults to `` `${type}${contractName}` ``. For example, `readErc20`, `simulateErc20`, `watchErc20Event`, `writeErc20`. +- When `'legacy'` (deprecated), hook names are set to `@wagmi/cli@1` format. + +```ts +import { defineConfig } from '@wagmi/cli' +import { actions } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + actions({ + getActionName({ contractName, type }) { // [!code focus] + return `${contractName}__${type}` // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + +### overridePackageName + +`'@wagmi/core' | 'wagmi'` + +- Override detected import source. +- Defaults to `'wagmi'` or `'@wagmi/core'` depending on which package is installed. + +```ts +import { defineConfig } from '@wagmi/cli' +import { actions } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + actions({ + overridePackageName: 'wagmi', // [!code focus] + }), + ], +}) +``` + diff --git a/site/cli/api/plugins/blockExplorer.md b/site/cli/api/plugins/blockExplorer.md new file mode 100644 index 0000000000..462e37bddd --- /dev/null +++ b/site/cli/api/plugins/blockExplorer.md @@ -0,0 +1,223 @@ +# blockExplorer + +Plugin for fetching ABIs from block explorers that supports the `?module=contract&action=getabi` API format. + +## Import + +```ts +import { blockExplorer } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-14} +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +## Configuration + +```ts +import { type BlockExplorerConfig } from '@wagmi/cli/plugins' +``` + +### apiKey + +`string | undefined` + +API key for block explorer. Appended to the request URL as query param `&apikey=${apiKey}`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + apiKey: process.env.ETHERSCAN_API_KEY, // [!code focus] + baseUrl: 'https://api.etherscan.io/v2/api', + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +### baseUrl + +`string` + +Base URL for block explorer. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', // [!code focus] + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +### cacheDuration + +`number | undefined` + +Duration in milliseconds to cache ABIs. Defaults to `1_800_000` (30 minutes). + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', + cacheDuration: 300_000, // [!code focus] + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +### chainId + +`number | undefined` + +Chain ID for block explorer. Appended to the request URL as query param `&chainId=${chainId}`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + apiKey: process.env.ETHERSCAN_API_KEY, + baseUrl: 'https://api.etherscan.io/v2/api', + chainId: 1, // [!code focus] + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + + +### contracts + +`{ name: string; address?: Address | Record | undefined }[]` + +Contracts to fetch ABIs for. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', + contracts: [ // [!code focus] + { // [!code focus] + name: 'Wagmigotchi', // [!code focus] + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', // [!code focus] + }, // [!code focus] + ], // [!code focus] + }), + ], +}) +``` + +### getAddress + +`((config: { address: Address | Record }) => Address) | undefined` + +- Function to get address from contract config. +- Defaults to `({ address }) => typeof address === 'string' ? address : Object.values(address)[0]`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + getAddress({ address }) { // [!code focus] + if (typeof address === 'string') return address // [!code focus] + return Object.values(address)[0] // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + +### name + +`string` + +- Name of source. +- Defaults to `'Block Explorer'`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + blockExplorer({ + baseUrl: 'https://api.etherscan.io/v2/api', + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + name: 'Etherscan', // [!code focus] + }), + ], +}) +``` diff --git a/site/cli/api/plugins/etherscan.md b/site/cli/api/plugins/etherscan.md new file mode 100644 index 0000000000..ec52fd6796 --- /dev/null +++ b/site/cli/api/plugins/etherscan.md @@ -0,0 +1,182 @@ +# etherscan + +Plugin for fetching ABIs from [Etherscan](https://etherscan.io) and adding into `contracts` config. + +## Import + +```ts +import { etherscan } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-14} +import { defineConfig } from '@wagmi/cli' +import { etherscan } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + chainId: 1, + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +## Configuration + +```ts +import { type EtherscanConfig } from '@wagmi/cli/plugins' +``` + +### apiKey + +`string` + +Etherscan API key. Etherscan API keys are specific per network and include testnets (e.g. Ethereum Mainnet and Sepolia share same API key). + +```ts +import { defineConfig } from '@wagmi/cli' +import { etherscan } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, // [!code focus] + chainId: 1, + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +### cacheDuration + +`number | undefined` + +- Duration in milliseconds to cache ABIs. +- Defaults to `1_800_000` (30 minutes). + +```ts +import { defineConfig } from '@wagmi/cli' +import { etherscan } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + cacheDuration: 300_000, // [!code focus] + chainId: 1, + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + }), + ], +}) +``` + +### chainId + +`number` + +Chain ID to use for fetching ABI. If [`address`](/cli/config/options#address) is an object, `chainId` is used to select the address. + +View supported chains on the [Etherscan docs](https://docs.etherscan.io/etherscan-v2/getting-started/supported-chains). + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + chainId: 1, // [!code focus] + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + { + name: 'EnsRegistry', + address: { + 1: '0x314159265dd8dbb310642f98f50c066173c1259b', + 5: '0x112234455c3a32fd11230c42e7bccd4a84e02010', + }, + }, + ], + }), + ], +}) +``` + +### contracts + +`{ name: string; address?: Address | Record | undefined }[]` + +Contracts to fetch ABIs for. + +```ts +import { defineConfig } from '@wagmi/cli' +import { etherscan } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + chainId: 1, + contracts: [ // [!code focus] + { // [!code focus] + name: 'Wagmigotchi', // [!code focus] + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', // [!code focus] + }, // [!code focus] + ], // [!code focus] + }), + ], +}) +``` + +### tryFetchProxyImplementation + +`boolean | undefined` + +- Whether to try fetching proxy implementation address of the contract. +- Defaults to `false`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { blockExplorer } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + chainId: 1, + contracts: [ + { + name: 'FiatToken', + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + }, + ], + tryFetchProxyImplementation: true, // [!code focus] + }), + ], +}) +``` + + diff --git a/site/cli/api/plugins/fetch.md b/site/cli/api/plugins/fetch.md new file mode 100644 index 0000000000..8d463a7ab6 --- /dev/null +++ b/site/cli/api/plugins/fetch.md @@ -0,0 +1,269 @@ +# fetch + +Plugin for fetching and parsing ABIs from network resource with `fetch`. + +## Import + +```ts +import { fetch } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-23} +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) +``` + + +## Configuration + +```ts +import { type FetchConfig } from '@wagmi/cli/plugins' +``` + +### cacheDuration + +`number | undefined` + +- Duration in milliseconds to cache ABIs. +- Defaults to `1_800_000` (30 minutes). + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + cacheDuration: 300_000, // [!code focus] + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) +``` + +### contracts + +`{ name: string; address?: Address | Record | undefined }[]` + +Contracts to fetch ABIs for. + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ // [!code focus] + { // [!code focus] + name: 'Wagmigotchi', // [!code focus] + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', // [!code focus] + }, // [!code focus] + ], // [!code focus] + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) + +``` + +### getCacheKey + +`((config: { contract: { address: Address | Record | undefined; name: string } }) => string) | undefined` + +- Function for creating a cache key for contract. Contract data is cached at `~/.wagmi-cli/plugins/fetch/cache/`. +- Defaults to `({ contract }) => JSON.stringify(contract)`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ + { + name: 'wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + getCacheKey({ contract }) { // [!code focus] + if (typeof contract.address === 'string') // [!code focus] + return `${name}:${contract.address}` // [!code focus] + return `${name}:${JSON.stringify(contract.address)}` // [!code focus] + }, // [!code focus] + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) + +``` + +### name + +`string` + +- Name of source. +- Defaults to `'Fetch'`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + name: 'Etherscan', // [!code focus] + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) +``` + +### parse + +`((config: { response: Response }) => Abi | Promise) | undefined` + +- Function for parsing ABI from fetch response. +- Defaults to `({ response }) => response.json()` + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + async parse({ response }) { // [!code focus] + const json = await response.json() // [!code focus] + if (json.status === '0') throw new Error(json.message) // [!code focus] + return json.result // [!code focus] + }, // [!code focus] + request(contract) { + if (!contract.address) throw new Error('address is required') + const address = + typeof contract.address === 'string' + ? contract.address + : Object.values(contract.address)[0] + return { + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, + } + }, + }), + ], +}) +``` + +### request + +`(config: { address?: Address | Record | undefined }) => { url: RequestInfo; init?: RequestInit | undefined } | Promise<{ url: RequestInfo; init?: RequestInit | undefined }>` + +Function for returning a request to fetch ABI from. + +```ts +import { defineConfig } from '@wagmi/cli' +import { fetch } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + fetch({ + contracts: [ + { + name: 'Wagmigotchi', + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + }, + ], + request(contract) { // [!code focus] + if (!contract.address) throw new Error('address is required') // [!code focus] + const address = // [!code focus] + typeof contract.address === 'string' // [!code focus] + ? contract.address // [!code focus] + : Object.values(contract.address)[0] // [!code focus] + return { // [!code focus] + url: `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}`, // [!code focus] + } // [!code focus] + }, // [!code focus] + }), + ], +}) + +``` diff --git a/site/cli/api/plugins/foundry.md b/site/cli/api/plugins/foundry.md new file mode 100644 index 0000000000..d24677b5c0 --- /dev/null +++ b/site/cli/api/plugins/foundry.md @@ -0,0 +1,217 @@ +# foundry + +Plugin for resolving ABIs from [Foundry](https://github.com/foundry-rs/foundry) projects. Supports [`watch`](/cli/api/commands/generate#w-watch) mode. + +## Import + +```ts +import { foundry } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-8} +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + project: '../hello_foundry', + }), + ], +}) +``` + +## Configuration + +```ts +import { type FoundryConfig } from '@wagmi/cli/plugins' +``` + +### artifacts + +`string | undefined` + +- Project's artifacts directory. Same as your `foundry.toml`/`forge`s `--out` (`-o`) option. +- Defaults to `'out/'`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + artifacts: 'out/', // [!code focus] + }), + ], +}) +``` + +### deployments + +`{ [key: string]: address?: Address | Record | undefined } | undefined` + +Mapping of addresses to attach to artifacts. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + deployments: { // [!code focus] + Counter: { // [!code focus] + 1: '0x314159265dd8dbb310642f98f50c066173c1259b', // [!code focus] + 5: '0x112234455c3a32fd11230c42e7bccd4a84e02010', // [!code focus] + }, // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + +### exclude + +`string[] | undefined` + +Artifact files to exclude relative to `artifacts`. Supports glob patterns. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + exclude: [ // [!code focus] + // the following patterns are excluded by default // [!code focus] + 'Common.sol/**', // [!code focus] + 'Components.sol/**', // [!code focus] + 'Script.sol/**', // [!code focus] + 'StdAssertions.sol/**', // [!code focus] + 'StdInvariant.sol/**', // [!code focus] + 'StdError.sol/**', // [!code focus] + 'StdCheats.sol/**', // [!code focus] + 'StdMath.sol/**', // [!code focus] + 'StdJson.sol/**', // [!code focus] + 'StdStorage.sol/**', // [!code focus] + 'StdUtils.sol/**', // [!code focus] + 'Vm.sol/**', // [!code focus] + 'console.sol/**', // [!code focus] + 'console2.sol/**', // [!code focus] + 'test.sol/**', // [!code focus] + '**.s.sol/*.json', // [!code focus] + '**.t.sol/*.json', // [!code focus] + ], // [!code focus] + }), + ], +}) +``` + +### forge + +`{ clean?: boolean | undefined; build?: boolean | undefined; path?: string | undefined; rebuild?: boolean | undefined } | undefined` + +Options for `forge`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + forge: { // [!code focus] + clean: true, // [!code focus] + build: true, // [!code focus] + path: 'path/to/forge', // [!code focus] + rebuild: true, // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + +#### clean + +- Remove build artifacts and cache directories on start up. +- Defaults to `false`. + +#### build + +- Build Foundry project before fetching artifacts. +- Defaults to `true`. + +#### path + +- Path to `forge` executable command. +- Defaults to `forge`. + +#### rebuild + +- Rebuild every time a watched file or directory is changed. Used for setting up [`watch`](/cli/api/commands/generate#w-watch) mode. +- Defaults to `true`. + +### include + +`string[] | undefined` + +Artifact files to include relative to `artifacts`. Supports glob patterns. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ + include: [ // [!code focus] + // the following patterns are included by default // [!code focus] + '*.json', // [!code focus] + ], // [!code focus] + }), + ], +}) +``` + +### namePrefix + +`string | undefined` + +Prefix to prepend to artifact names. Useful for preventing name collisions between contracts from other sources. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ // [!code focus] + namePrefix: 'HelloFoundry', // [!code focus] + }), // [!code focus] + ], +}) +``` + +### project + +`string | undefined` + +- Path to Foundry project. +- Defaults to Foundry configuration using `forge config --json` command. + +```ts +import { defineConfig } from '@wagmi/cli' +import { foundry } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + foundry({ // [!code focus] + project: '../hello_foundry', // [!code focus] + }), // [!code focus] + ], +}) +``` \ No newline at end of file diff --git a/site/cli/api/plugins/hardhat.md b/site/cli/api/plugins/hardhat.md new file mode 100644 index 0000000000..38cca9aebe --- /dev/null +++ b/site/cli/api/plugins/hardhat.md @@ -0,0 +1,199 @@ +# hardhat + +Plugin for resolving ABIs from [Hardhat](https://hardhat.org) projects. Supports [`watch`](/cli/api/commands/generate#w-watch) mode. + +```ts +import { hardhat } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-8} +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + project: '../hello_hardhat', + }), + ], +}) +``` + +## Configuration + +```ts +import { type HardhatConfig } from '@wagmi/cli/plugins' +``` + +### artifacts + +`string | undefined` + +- Project's artifacts directory. Same as your project's `artifacts` [path configuration](https://hardhat.org/hardhat-runner/docs/config#path-configuration) option. +- Defaults to `'artifacts/'`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + artifacts: 'out/', // [!code focus] + project: '../hello_hardhat', + }), + ], +}) +``` + +### deployments + +`{ [key: string]: address?: Address | Record | undefined } | undefined` + +Mapping of addresses to attach to artifacts. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + project: '../hello_hardhat', + deployments: { // [!code focus] + Counter: { // [!code focus] + 1: '0x314159265dd8dbb310642f98f50c066173c1259b', // [!code focus] + 5: '0x112234455c3a32fd11230c42e7bccd4a84e02010', // [!code focus] + }, // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + +### exclude + +`string[] | undefined` + +Artifact files to exclude relative to `artifacts`. Supports glob patterns. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + exclude: [ // [!code focus] + // the following patterns are excluded by default // [!code focus] + 'build-info/**', // [!code focus] + '*.dbg.json', // [!code focus] + ], // [!code focus] + project: '../hello_hardhat', + }), + ], +}) +``` + +### commands + +`{ clean?: string | boolean | undefined; build?: string | boolean | undefined; rebuild?: string | boolean | undefined } | undefined` + +Hardhat command options. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + commands: { // [!code focus] + clean: 'pnpm hardhat clean', // [!code focus] + build: 'pnpm hardhat compile', // [!code focus] + rebuild: 'pnpm hardhat compile', // [!code focus] + }, // [!code focus] + project: '../hello_hardhat', + }), + ], +}) +``` + +#### clean + +- Remove build artifacts and cache directories on start up. +- Defaults to `'${packageManger} hardhat clean'`. + +#### build + +- Build Foundry project before fetching artifacts. +- Defaults to `'${packageManger} hardhat compile'`. + +#### rebuild + +- Command to run when watched file or directory is changed. Used for setting up [`watch`](/cli/api/commands/generate#w-watch) mode. +- Defaults to `'${packageManger} hardhat compile'`. + +### include + +`string[] | undefined` + +Artifact files to include relative to `artifacts`. Supports glob patterns. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + include: [ // [!code focus] + // the following patterns are included by default // [!code focus] + '*.json', // [!code focus] + ], // [!code focus] + project: '../hello_hardhat', + }), + ], +}) +``` + +### namePrefix + +`string | undefined` + +Prefix to prepend to artifact names. Useful for preventing name collisions between contracts from other sources. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + namePrefix: 'HelloHardhat', // [!code focus] + project: '../hello_hardhat', + }), + ], +}) +``` + +### project + +`string` + +Path to Hardhat project. + +```ts +import { defineConfig } from '@wagmi/cli' +import { hardhat } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + hardhat({ + project: '../hello_hardhat', // [!code focus] + }), + ], +}) +``` \ No newline at end of file diff --git a/site/cli/api/plugins/react.md b/site/cli/api/plugins/react.md new file mode 100644 index 0000000000..e78f782c6d --- /dev/null +++ b/site/cli/api/plugins/react.md @@ -0,0 +1,52 @@ +# react + +Plugin for generating type-safe [Wagmi Hooks](/react/api/hooks). + +## Import + +```ts +import { react } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6} +import { defineConfig } from '@wagmi/cli' +import { react } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + react(), + ], +}) +``` + +## Configuration + +```ts +import { type ReactConfig } from '@wagmi/cli/plugins' +``` + +### getHookName + +`` 'legacy' | ((options: { contractName: string; type: 'read' | 'simulate' | 'watch' | 'write' }) => `use${string}`) `` + +- Function for setting custom hook names. +- Defaults to `` `use${type}${contractName}` ``. For example, `useReadErc20`, `useSimulateErc20`, `useWatchErc20Event`, `useWriteErc20`. +- When `'legacy'` (deprecated), hook names are set to `@wagmi/cli@1` format. + +```ts +import { defineConfig } from '@wagmi/cli' +import { react } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + react({ + getHookName({ contractName, type }) { // [!code focus] + return `use${contractName}__${type}` // [!code focus] + }, // [!code focus] + }), + ], +}) +``` + diff --git a/site/cli/api/plugins/sourcify.md b/site/cli/api/plugins/sourcify.md new file mode 100644 index 0000000000..d965fdde02 --- /dev/null +++ b/site/cli/api/plugins/sourcify.md @@ -0,0 +1,115 @@ +# sourcify + +Plugin for fetching ABIs from [Sourcify](https://sourcify.dev/). Sourcify is a decentralized, open-source, smart contract verification and metadata repository. + +## Import + +```ts +import { sourcify } from '@wagmi/cli/plugins' +``` + +## Usage + +```ts{2,6-13} +import { defineConfig } from '@wagmi/cli' +import { sourcify } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + sourcify({ + contracts: [ + { + name: 'deposit', + address: '0x00000000219ab540356cbb839cbe05303d7705fa', + }, + ], + }), + ], +}) +``` + +## Configuration + +```ts +import { type SourcifyConfig } from '@wagmi/cli/plugins' +``` + +### cacheDuration + +`number | undefined` + +- Duration in milliseconds to cache ABIs. +- Defaults to `1_800_000` (30 minutes). + +```ts +import { defineConfig } from '@wagmi/cli' +import { sourcify } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + sourcify({ + cacheDuration: 300_000, // [!code focus] + chainId: 100, + contracts: [ + { + name: 'Deposit', + address: '0x00000000219ab540356cbb839cbe05303d7705fa', + }, + ], + }), + ], +}) +``` + +### chainId + +`number` + +Chain ID to use for fetching ABI. If `address` is an object, `chainId` is used to select the address. See [Sourcify docs](https://docs.sourcify.dev/docs/chains) for supported chains. + +```ts +import { defineConfig } from '@wagmi/cli' +import { sourcify } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + sourcify({ + chainId: 100, // [!code focus] + contracts: [ + { + name: 'Community', + address: { + 100: '0xC4c622862a8F548997699bE24EA4bc504e5cA865', + 137: '0xC4c622862a8F548997699bE24EA4bc504e5cA865', + }, + }, + ], + }), + ], +}) +``` + +### contracts + +`{ name: string; address?: Address | Record | undefined }[]` + +Contracts to fetch ABIs for. + +```ts +import { defineConfig } from '@wagmi/cli' +import { sourcify } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + sourcify({ + chainId: 100, + contracts: [ // [!code focus] + { // [!code focus] + name: 'Deposit', // [!code focus] + address: '0x00000000219ab540356cbb839cbe05303d7705fa', // [!code focus] + }, // [!code focus] + ], // [!code focus] + }), + ], +}) +``` diff --git a/site/cli/config/configuring-cli.md b/site/cli/config/configuring-cli.md new file mode 100644 index 0000000000..9e8e11fae3 --- /dev/null +++ b/site/cli/config/configuring-cli.md @@ -0,0 +1,124 @@ +# Configuring CLI + +When running `wagmi` from the command line, `@wagmi/cli` will automatically try to resolve a config file named `wagmi.config.js` or `wagmi.config.ts` inside the project root. The most basic config file looks like this: + +::: code-group +```js [wagmi.config.js] +export default { + // config options +} +``` +::: + +Note `@wagmi/cli` supports using ES modules syntax in the config file even if the project is not using native Node ESM, e.g. `"type": "module"` in package.json. In this case, the config file is auto pre-processed before load. + +You can also explicitly specify a config file to use with the `--config`/`-c` CLI option (resolved relative to the current directory): + +```bash +wagmi --config my-config.js +``` + +To scaffold a config file quickly, check out the [`init`](/cli/api/commands/init) command. + +## Config Intellisense + +Since Wagmi CLI ships with TypeScript typings, you can use your editor's intellisense with [JSDoc](https://jsdoc.app) type hints: + +::: code-group +```js [wagmi.config.js] +/** @type {import('@wagmi/cli').Config} */ +export default { + // ... +} +``` +::: + +Alternatively, you can use the `defineConfig` utility which should provide intellisense without the need for JSDoc annotations: + +::: code-group +```js [wagmi.config.js] +import { defineConfig } from '@wagmi/cli' + +export default defineConfig({ + // ... +}) +``` +::: + +Wagmi CLI also directly supports TypeScript config files. You can use `wagmi.config.ts` with the `defineConfig` helper as well. + +## Conditional Config + +If the config needs to conditionally determine options based on the environment, it can export a function instead: + +::: code-group +```js [wagmi.config.js] +export default defineConfig(() => { + if (process.env.NODE_ENV === 'dev') { + return { + // dev specific config + } + } else { + return { + // production specific config + } + } +}) +``` +::: + +## Async Config + +If the config needs to call async function, it can export a async function instead: + +::: code-group +```js [wagmi.config.js] +export default defineConfig(async () => { + const data = await asyncFunction() + return { + // ... + } +}) +``` +::: + +This can be useful for resolving external resources from the network or filesystem that are required for configuration ahead of running a command. + +## Array Config + +The config can also be represented either as a pre-defined array or returned as an array from a function: + +::: code-group +```js [wagmi.config.js] +export default defineConfig([ + { + // config 1 + }, + { + // config 2 + }, +]) +``` +::: + +## Environment Variables + +Environmental Variables can be obtained from `process.env` as usual. + +Note that Wagmi CLI doesn't load `.env` files by default as the files to load can only be determined after evaluating the config. However, you can use the exported `loadEnv` utility to load the specific `.env` files if needed. + +::: code-group +```js [wagmi.config.js] +import { defineConfig, loadEnv } from '@wagmi/cli' + +export default defineConfig(() => { + const env = loadEnv({ + mode: process.env.NODE_ENV, + envDir: process.cwd(), + }) + return { + // ... + } +}) +``` +::: \ No newline at end of file diff --git a/site/cli/config/options.md b/site/cli/config/options.md new file mode 100644 index 0000000000..04a2cd2d56 --- /dev/null +++ b/site/cli/config/options.md @@ -0,0 +1,132 @@ +# Config Options + +Configuration options for Wagmi CLI. + +## contracts + +`ContractConfig[] | undefined` + +Array of contracts to use when running [commands](/cli/api/commands). `abi` and `name` are required, all other properties are optional. + +### address + +`Address | Record | undefined` + +Contract address or addresses. Accepts an object `{ [chainId]: address }` for targeting specific chains. + +::: code-group +```ts {6,11-14} [wagmi.config.ts] +export default { + out: 'src/generated.ts', + contracts: [ + { + abi: […], + address: '0x…', + name: 'MyCoolContract', + }, + { + abi: […], + address: { + 1: '0xfoo…', + 5: '0xbar…', + }, + name: 'MyCoolMultichainContract' + } + ], +} +``` +::: + +### abi + +`Abi` + +ABI for contract. Used by [plugins](/cli/api/plugins) to generate code base on properties. + +::: code-group +```ts {5} [wagmi.config.ts] +export default { + out: 'src/generated.ts', + contracts: [ + { + abi: […], + name: 'MyCoolContract' + }, + ], +} +``` +::: + +### name + +`string` + +Name of contract. Must be unique. Used by [plugins](/cli/api/plugins) to name generated code. + +::: code-group +```ts {6} [wagmi.config.ts] +export default { + out: 'src/generated.ts', + contracts: [ + { + abi: […], + name: 'MyCoolContract' + }, + ], +} +``` +::: + +## out + +`string` + +Path to output generated code. Must be unique per config. Use an [Array Config](/cli/config/configuring-cli#array-config) for multiple outputs. + +::: code-group +```ts {2} [wagmi.config.ts] +export default { + out: 'src/generated.ts', + contracts: [ + { + abi: […], + name: 'MyCoolContract' + }, + ], +} +``` +::: + +## plugins + +`Plugin[] | undefined` + +Plugins to use and their configuration. + +Wagmi CLI has multiple [built-in plugins](/cli/api/plugins) that are used to manage ABIs, generate code, etc. + +::: code-group +```ts {1,5-20} [wagmi.config.ts] +import { etherscan, react } from '@wagmi/cli/plugins' + +export default { + out: 'src/generated.js', + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY, + chainId: 5, + contracts: [ + { + name: 'EnsRegistry', + address: { + 1: '0x314159265dd8dbb310642f98f50c066173c1259b', + 5: '0x112234455c3a32fd11230c42e7bccd4a84e02010', + }, + }, + ], + }), + react(), + ], +} +``` +::: diff --git a/site/cli/create-wagmi.md b/site/cli/create-wagmi.md new file mode 100644 index 0000000000..bdf8dda308 --- /dev/null +++ b/site/cli/create-wagmi.md @@ -0,0 +1,75 @@ +# create-wagmi + +## Overview + +create-wagmi is a command line interface (CLI) for scaffolding new Wagmi projects. + +## Usage + +::: code-group +```bash [pnpm] +pnpm create wagmi +``` +```bash [npm] +npm create wagmi@latest +``` +```bash [yarn] +yarn create wagmi +``` +```bash [bun] +bun create wagmi +``` +::: + +## Options + +### `-t`, `--template` + +You can specify a custom [template](#templates) by passing the `--template`/`-t` flag: + +::: code-group +```bash [pnpm] +pnpm create wagmi --template next +``` +```bash [npm] +npm create wagmi@latest --template next +``` +```bash [yarn] +yarn create wagmi --template next +``` +```bash [bun] +bun create wagmi --template next +``` +::: + +### `--bun`/`--npm`/`--pnpm`/`--yarn` + +Use a specific package manager to install dependencies. By default, `create-wagmi` will use the package manager you used to run the command. + +### `-h`, `--help` + +Prints the help message. + +### `-v`, `--version` + +Prints the CLI version. + +## Templates + +`create-wagmi` currently comes with the following templates: + +- `next`: A Next.js Wagmi project. +- `nuxt`: A Nuxt Wagmi project. +- `vite-react`: A Vite (React) Wagmi project. +- `vite-vanilla`: A Vite Wagmi Core project. +- `vite-vue`: A Vite (Vue) Wagmi project. + +If you do not specify the template on the command line, you will be prompted to select a framework and variant. + +- **React** : A React project. + - **Vite** : A React + Vite Wagmi project (`vite-react`). + - **Next** : A React + Next Wagmi project (`next`). +- **Vue**: A Vue project. + - **Vite**: A Vue + Vite Wagmi project (`vite-vue`). + - **Nuxt**: A Vue + Nuxt Wagmi project (`nuxt`). +- **Vanilla**: A Vite Wagmi project without React (`vite-vanilla`). diff --git a/site/cli/getting-started.md b/site/cli/getting-started.md new file mode 100644 index 0000000000..ccf2d2b606 --- /dev/null +++ b/site/cli/getting-started.md @@ -0,0 +1,167 @@ +# Getting Started + +## Overview + +Wagmi CLI is a command line interface for managing ABIs (from Etherscan/block explorers, Foundry/Hardhat projects, etc.), generating code (e.g. React Hooks), and much more. It makes working with Ethereum easier by automating manual work so you can build faster. You can learn more about the rationale behind the project in the [Why Wagmi CLI](/cli/why) section. + +## Manual Installation + +To manually add Wagmi CLI to your project, install the required packages. + +::: code-group +```bash [pnpm] +pnpm add -D @wagmi/cli +``` + +```bash [npm] +npm install --save-dev @wagmi/cli +``` + +```bash [yarn] +yarn add -D @wagmi/cli +``` + +```bash [bun] +bun add -D @wagmi/cli +``` +::: + +## Create Config File + +Run the `init` command to generate a configuration file: either `wagmi.config.ts` if TypeScript is detected, otherwise `wagmi.config.js`. You can also create the configuration file manually. See [Configuring CLI](/cli/config/configuring-cli) for more info. + +::: code-group +```bash [pnpm] +pnpm wagmi init +``` + +```bash [npm] +npx wagmi init +``` + +```bash [yarn] +yarn wagmi init +``` + +```bash [bun] +bun wagmi init +``` +::: + +The generated configuration file will look something like this: + +::: code-group +```ts [wagmi.config.ts] +import { defineConfig } from '@wagmi/cli' + +export default defineConfig({ + out: 'src/generated.ts', + contracts: [], + plugins: [], +}) +``` +::: + +## Add Contracts And Plugins + +Once the configuration file is set up, you can add contracts and plugins to it. These contracts and plugins are used to manage ABIs (fetch from block explorers, resolve from the file system, etc.), generate code (React hooks, etc.), and much more! + +For example, we can add the ERC-20 contract from Viem, and the [`etherscan`](/cli/api/plugins/etherscan) and [`react`](/cli/api/plugins/react) plugins. + +::: code-group +```ts{2,3,9-12,15-27,28} [wagmi.config.ts] +import { defineConfig } from '@wagmi/cli' +import { etherscan, react } from '@wagmi/cli/plugins' +import { erc20Abi } from 'viem' +import { mainnet, sepolia } from 'wagmi/chains' + +export default defineConfig({ + out: 'src/generated.ts', + contracts: [ + { + name: 'erc20', + abi: erc20Abi, + }, + ], + plugins: [ + etherscan({ + apiKey: process.env.ETHERSCAN_API_KEY!, + chainId: mainnet.id, + contracts: [ + { + name: 'EnsRegistry', + address: { + [mainnet.id]: '0x314159265dd8dbb310642f98f50c066173c1259b', + [sepolia.id]: '0x112234455c3a32fd11230c42e7bccd4a84e02010', + }, + }, + ], + }), + react(), + ], +}) +``` +::: + +## Run Code Generation + +Now that we added a few contracts and plugins to the configuration file, we can run the [`generate`](/cli/api/commands/generate) command to resolve ABIs and generate code to the `out` file. + +::: code-group +```bash [pnpm] +pnpm wagmi generate +``` + +```bash [npm] +npx wagmi generate +``` + +```bash [yarn] +yarn wagmi generate +``` + +```bash [bun] +bun wagmi generate +``` +::: + +In this example, the `generate` command will do the following: + +- Validate the `etherscan` and `react` plugins +- Fetch and cache the ENS Registry ABI from the Mainnet Etherscan API +- Pull in the `erc20Abi` using the name `'ERC20'` +- Generate React Hooks for both ABIs +- Save ABIs, ENS Registry deployment addresses, and React Hooks to the `out` file + +## Use Generated Code + +Once `out` is created, you can start using the generated code in your project. + +```ts +import { useReadErc20, useReadErc20BalanceOf } from './generated' + +// Use the generated ERC-20 read hook +const { data } = useReadErc20({ + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + functionName: 'balanceOf', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], +}) + +// Use the generated ERC-20 "balanceOf" hook +const { data } = useReadErc20BalanceOf({ + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], +}) +``` + +::: tip +Instead of committing the `out` file, you likely want to add `out` to your `.gitignore` and run `generate` during the build process or before you start your dev server in a `"predev"` script. +::: + +## Next Steps + +For more information on what to do next, check out the following topics. + +- [**Configuring CLI**](/cli/config/configuring-cli) Learn how to configure the CLI to work best for your project. +- [**Commands**](/cli/api/commands) Learn more about the CLI commands and how to use them. +- [**Plugins**](/cli/api/plugins) Browse the collection of plugins and set them up with your config. diff --git a/site/cli/guides/migrate-from-v1-to-v2.md b/site/cli/guides/migrate-from-v1-to-v2.md new file mode 100644 index 0000000000..2db0fef568 --- /dev/null +++ b/site/cli/guides/migrate-from-v1-to-v2.md @@ -0,0 +1,51 @@ +--- +title: Migrate from v1 to v2 +titleTemplate: Wagmi CLI +description: Guide for migrating from Wagmi CLI v1 to v2. +--- + +# Migrate from v1 to v2 + +To get started, install the latest version of the Wagmi CLI. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/cli +``` + +```bash-vue [npm] +npm install @wagmi/cli +``` + +```bash-vue [yarn] +yarn add @wagmi/cli +``` + +```bash-vue [bun] +bun add @wagmi/cli +``` +::: + +::: info Not ready to migrate yet? +The Wagmi CLI v1 docs are still available at [1.x.wagmi.sh/cli](https://1.x.wagmi.sh/cli). +::: + +## Changed generated action and hook names + +Generated action and hook names now align with [Wagmi v2 naming conventions](/react/guides/migrate-from-v1-to-v2#renamed-hooks). If you want hooks to still follow Wagmi v1 naming conventions, set [`getActionName`](/cli/api/plugins/actions#getactionname) and [`getHookName`](/cli/api/plugins/react#gethookname) to `'legacy'`. + +```ts +import { defineConfig } from '@wagmi/cli' +import { actions, react } from '@wagmi/cli/plugins' + +export default defineConfig({ + plugins: [ + actions({ + getActionName: 'legacy', // [!code focus] + }), + react({ + getHookName: 'legacy', // [!code focus] + }), + ], +}) +``` diff --git a/site/cli/installation.md b/site/cli/installation.md new file mode 100644 index 0000000000..649565ca34 --- /dev/null +++ b/site/cli/installation.md @@ -0,0 +1,60 @@ +# Installation + +Install Wagmi CLI via your package manager. + +## Package Manager + +Install the required package. + +::: code-group +```bash [pnpm] +pnpm add @wagmi/cli +``` + +```bash [npm] +npm install @wagmi/cli +``` + +```bash [yarn] +yarn add @wagmi/cli +``` + +```bash [bun] +bun add @wagmi/cli +``` +::: + +## Using Unreleased Commits + +If you can't wait for a new release to test the latest features, you can either install from the `canary` tag (tracks the [`main`](https://github.com/wevm/wagmi/tree/main) branch). + +::: code-group +```bash [pnpm] +pnpm add @wagmi/cli@canary +``` + +```bash [npm] +npm install @wagmi/cli@canary +``` + +```bash [yarn] +yarn add @wagmi/cli@canary +``` + +```bash [bun] +bun add @wagmi/cli@canary +``` +::: + +Or clone the [Wagmi repo](https://github.com/wevm/wagmi) to your local machine, build, and link it yourself. + +```bash +git clone https://github.com/wevm/wagmi.git +cd wagmi +pnpm install +pnpm build +cd packages/cli +pnpm link --global +``` + +Then go to the project where you are using the Wagmi CLI and run `pnpm link --global @wagmi/cli` (or the package manager that you used to link Wagmi CLI globally). \ No newline at end of file diff --git a/site/cli/why.md b/site/cli/why.md new file mode 100644 index 0000000000..a626c46006 --- /dev/null +++ b/site/cli/why.md @@ -0,0 +1,92 @@ +# Why Wagmi CLI + +## The Problem + +The most common way to interact with smart contracts is through [Application Binary Interfaces](https://docs.soliditylang.org/en/latest/abi-spec.html). ABIs describe smart contracts' public functionality (e.g. functions, events, errors) as well as how to encode and decode related data (e.g. arguments and results). + +While ABIs are extremely powerful, there isn't a uniform way developers manage them in their apps. Developers do a bunch of different things, like: + +- Publish packages on npm containing ABIs +- Write custom scripts to fetch ABIs from external sources +- Compile contracts into application project +- Copy and paste ABIs from local projects or block explorers + +All these approaches take time that you could spend doing more important things, like interacting with your smart contracts! + +## The Solution + +The Wagmi CLI is an attempt to automate manual work so you can build faster. In short, the CLI manages ABIs and generates code. It takes ABIs as inputs and outputs ABIs and generated code. For example, the [Etherscan plugin](/cli/api/plugins/etherscan) allows you to fetch ABIs across multiple chains and deployments and immediately start importing them into your project. + +Code generation is another big advantage of the CLI. Using the [React plugin](/cli/api/plugins/react), you can generate [Wagmi Hooks](/react/api/hooks) for ABIs. When you combine this with the CLI's different ABI sources, like Etherscan, Foundry/Hardhat, and more, you reduce a lot of boilerplate code. + +::: code-group +```ts [Diff] +import { useReadContract, useWriteContract } from 'wagmi' // [!code --] +import { froggyFriendsAbi, froggyFriendsAddress } from './generated' // [!code --] +import { useReadFroggyFriends, useWriteFroggyFriends } from './generated' // [!code ++] + +function App() { + const { data } = useReadContract({ // [!code --] + const { data } = useReadFroggyFriends({ // [!code ++] + abi: froggyFriendsAbi, // [!code --] + address: froggyFriendsAddress, // [!code --] + functionName: 'tokenURI', + args: [123n], + }) + + const { write } = useWriteContract() // [!code --] + const { write } = useWriteFroggyFriends() // [!code ++] + const onClick = React.useCallback(() => { + write({ + abi: froggyFriendsAbi, // [!code --] + address: froggyFriendsAddress, // [!code --] + functionName: 'mint', + args: ['foo', 123n], + }) + }, [write]) +} +``` +```ts [Before] +import { useReadContract, useWriteContract } from 'wagmi' +import { froggyFriendsAbi, froggyFriendsAddress } from './generated' + +function App() { + const { data } = useReadContract({ + abi: froggyFriendsAbi, + address: froggyFriendsAddress, + functionName: 'tokenURI', + args: [123n], + }) + + const { write } = useWriteContract() + const onClick = React.useCallback(() => { + write({ + abi: froggyFriendsAbi, + address: froggyFriendsAddress, + functionName: 'mint', + args: ['foo', 123n], + }) + }, [write]) +} +``` +```ts [After] +import { useReadFroggyFriends, useWriteFroggyFriends } from './generated' + +function App() { + const { data } = useReadFroggyFriends({ + functionName: 'tokenURI', + args: [123n], + }) + + const { write } = useWriteFroggyFriends() + const onClick = React.useCallback(() => { + write({ + functionName: 'mint', + args: ['foo', 123n], + }) + }, [write]) +} +``` +::: + +Finally, the Wagmi CLI supports popular smart contract development tools, [Foundry](/cli/api/plugins/foundry) and [Hardhat](/cli/api/plugins/hardhat). You can run the CLI in [watch mode](/cli/api/commands/generate#w-watch), make changes to your contracts, and the CLI will automatically pick up ABI changes and run plugins over those changes. A major boon to working a monorepo and shortening the feedback loop across your stack. diff --git a/site/components/Browsers.vue b/site/components/Browsers.vue new file mode 100644 index 0000000000..4367018bc9 --- /dev/null +++ b/site/components/Browsers.vue @@ -0,0 +1,9 @@ + + + diff --git a/site/components/SearchChains.vue b/site/components/SearchChains.vue new file mode 100644 index 0000000000..b649a6202e --- /dev/null +++ b/site/components/SearchChains.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/site/core/api/actions.md b/site/core/api/actions.md new file mode 100644 index 0000000000..664cd9a86a --- /dev/null +++ b/site/core/api/actions.md @@ -0,0 +1,25 @@ + + +# Actions + +Actions for accounts, wallets, contracts, transactions, signing, ENS, and more. + +## Import + +```ts +import { getAccount } from '@wagmi/core' +``` + +## Available Actions + + diff --git a/site/core/api/actions/call.md b/site/core/api/actions/call.md new file mode 100644 index 0000000000..52f202a570 --- /dev/null +++ b/site/core/api/actions/call.md @@ -0,0 +1,340 @@ + + +# call + +Action for executing a new message call immediately without submitting a transaction to the network. + +## Import + +```ts +import { call } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts twoslash +import { type CallParameters } from '@wagmi/core' +``` + +### account + +`Account | Address | undefined` + +The Account to call from. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded args. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### to + +`Address | undefined` + +The contract address or recipient. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + accessList: [ // [!code focus:6] + { + address: '0x1', + storageKeys: ['0x1'], + }, + ], + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +The gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + gas: 1_000_000n, // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gasPrice + +`bigint | undefined` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { parseGwei } from 'viem' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas (in wei), inclusive of `maxPriorityFeePerGas`. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { parseGwei } from 'viem' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas (in wei). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { parseGwei } from 'viem' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### nonce + +`number | undefined` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + nonce: 420, // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value (in wei) sent with this transaction. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await call(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`number | undefined` + +The block number to perform the call against. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + blockNumber: 15121123n, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to perform the call against. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' + +await call(config, { + blockTag: 'safe', // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The block tag to perform the call against. + +::: code-group +```ts [index.ts] +import { call } from '@wagmi/core' +import { config } from './config' +import { mainnet } from '@wagmi/core/chains' + +await call(config, { + chainId: mainnet.id, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts twoslash +import { type CallReturnType } from '@wagmi/core' +``` + +`{ data: 0x${string} }` + +The call data. + +## Error + +```ts twoslash +import { type CallErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`call`](https://viem.sh/docs/actions/public/call.html) diff --git a/site/core/api/actions/connect.md b/site/core/api/actions/connect.md new file mode 100644 index 0000000000..30d04a85e3 --- /dev/null +++ b/site/core/api/actions/connect.md @@ -0,0 +1,102 @@ + + +# connect + +Action for connecting accounts with [connectors](/core/api/connectors). + +## Import + +```ts +import { connect } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { connect } from '@wagmi/core' +import { injected } from '@wagmi/connectors' +import { config } from './config' + +const result = await connect(config, { connector: injected() }) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ConnectParameters } from '@wagmi/core' +``` + +### chainId + +`number | undefined` + +Chain ID to connect to. + +Not all connectors support connecting directly to a `chainId` (e.g. they don't support programmatic chain switching). In those cases, the connector will connect to whatever chain the connector's provider is connected to. + +::: code-group +```ts [index.ts] +import { connect } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { injected } from '@wagmi/connectors' +import { config } from './config' + +const result = await connect(config, { + chainId: mainnet.id, // [!code focus] + connector: injected(), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`CreateConnectorFn | Connector` + +[Connector](/core/api/connectors) to connect with. + +::: code-group +```ts [index.ts] +import { connect } from '@wagmi/core' +import { injected } from '@wagmi/connectors' // [!code focus] +import { config } from './config' + +const result = await connect(config, { + connector: injected(), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ConnectReturnType } from '@wagmi/core' +``` + +### accounts + +`readonly [Address, ...Address[]]` + +Connected accounts from connector. + +### chainId + +`number` + +Connected chain ID from connector. + +## Error + +```ts +import { type ConnectErrorType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/deployContract.md b/site/core/api/actions/deployContract.md new file mode 100644 index 0000000000..3cdbfd130f --- /dev/null +++ b/site/core/api/actions/deployContract.md @@ -0,0 +1,264 @@ + + +# deployContract + +Action for deploying a contract to the network, given bytecode, and constructor arguments. + +## Import + +```ts +import { deployContract } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Deploying with Constructor Args + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, + args: [69420], + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + + +## Parameters + +```ts +import { type DeployContractParameters } from '@wagmi/core' +``` + +### abi + +`Abi` + +The contract's ABI. + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, // [!code focus] + args: [69420], + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when deploying a contract. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + args: [69420], + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when deploying the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, + args: [69420], // [!code focus] + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### bytecode + +`Hex` + +The contract's bytecode. + +::: code-group +```ts [index.ts] +import { deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const result = await deployContract(config, { + abi: wagmiAbi, + args: [69420], + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', // [!code focus] +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to use when deploying a contract. +- Defaults to current connector. + +::: code-group +```ts [index.ts] +import { getAccount, deployContract } from '@wagmi/core' +import { wagmiAbi } from './abi' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await deployContract(config, { + abi: wagmiAbi, + args: [69420], + bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', + connector, // [!code focus] +}) +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type DeployContractReturnType } from '@wagmi/core' +``` + +[`Hash`](https://viem.sh/docs/glossary/types.html#hash) + +Transaction hash. + +## Error + +```ts +import { type DeployContractErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`deployContract`](https://viem.sh/docs/contract/deployContract) diff --git a/site/core/api/actions/disconnect.md b/site/core/api/actions/disconnect.md new file mode 100644 index 0000000000..6c77fb5c74 --- /dev/null +++ b/site/core/api/actions/disconnect.md @@ -0,0 +1,60 @@ + + +# disconnect + +Action for disconnecting connections. + +## Import + +```ts +import { disconnect } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { disconnect } from '@wagmi/core' +import { config } from './config' + +await disconnect(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type DisconnectParameters } from '@wagmi/core' +``` + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to disconnect with. + +::: code-group +```ts [index.ts] +import { disconnect, getAccount } from '@wagmi/core' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await disconnect(config, { + connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Error + +```ts +import { type DisconnectErrorType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/estimateFeesPerGas.md b/site/core/api/actions/estimateFeesPerGas.md new file mode 100644 index 0000000000..ce804efeb9 --- /dev/null +++ b/site/core/api/actions/estimateFeesPerGas.md @@ -0,0 +1,139 @@ + + +# estimateFeesPerGas + +Returns an estimate for the fees per gas (in wei) for a transaction to be likely included in the next block. + +## Import + +```ts +import { estimateFeesPerGas } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { estimateFeesPerGas } from '@wagmi/core' +import { config } from './config' + +const result = await estimateFeesPerGas(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type EstimateFeesPerGasParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { estimateFeesPerGas } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const result = await estimateFeesPerGas(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### formatUnits + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { estimateFeesPerGas } from '@wagmi/core' +import { config } from './config' + +const feesPerGas = estimateFeesPerGas(config, { + formatUnits: 'ether', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559'` + +- Fee value type. +- Defaults to `'eip1559'` + +::: code-group +```ts [index.ts] +import { estimateFeesPerGas } from '@wagmi/core' +import { config } from './config' + +const result = estimateFeesPerGas(config, { + type: 'legacy', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type EstimateFeesPerGasReturnType } from '@wagmi/core' +``` + +[`FeeValues`](https://viem.sh/docs/glossary/types.html#feevalues) + +An estimate (in wei) for the fees per gas. + +### formatted + +`{ gasPrice: string | undefined; maxFeePerGas: string | undefined; maxPriorityFeePerGas: string | undefined; }` + +Object of formatted values using [`formatUnits`](#formatunits). + +### gasPrice + +`bigint | undefined` + +- Gas price. +- When [`type`](#type) is `'eip1559'`, value is `undefined`. + +### maxFeePerGas + +`bigint | undefined` + +- Max fee per gas. +- When [`type`](#type) is `'legacy'`, value is `undefined`. + +### maxPriorityFeePerGas + +`bigint | undefined` + +- Max priority fee per gas. +- When [`type`](#type) is `'legacy'`, value is `undefined`. + +## Error + +```ts +import { type EstimateFeesPerGasErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`estimateFeesPerGas`](https://viem.sh/docs/actions/public/estimateFeesPerGas.html) diff --git a/site/core/api/actions/estimateGas.md b/site/core/api/actions/estimateGas.md new file mode 100644 index 0000000000..cbeb1ba40d --- /dev/null +++ b/site/core/api/actions/estimateGas.md @@ -0,0 +1,340 @@ + + +# estimateGas + +Action for estimating the gas necessary to complete a transaction without submitting it to the network. + +## Import + +```ts +import { estimateGas } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type EstimateGasParameters } from '@wagmi/core' +``` + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when estimating gas. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to target when estimating gas. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + chainId: mainnet.id, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to estimate with. If no [`account`](#account) is provided, will use default account from connector. + +::: code-group +```ts [index.ts] +import { getConnections, estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const connections = getConnections(config) +const result = await estimateGas(config, { + connector: connections[0]?.connector, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded args. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + gas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + gasPrice: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + nonce: 123, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### to + +`Address | undefined` + +The transaction recipient or contract address. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + type: 'eip1559', // [!code focus] + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```ts [index.ts] +import { estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await estimateGas(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type EstimateGasReturnType } from '@wagmi/core' +``` + +`bigint` + +The gas estimate in wei. + +## Error + +```ts +import { type EstimateGasErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`estimateGas`](https://viem.sh/docs/actions/public/estimateGas.html) diff --git a/site/core/api/actions/estimateMaxPriorityFeePerGas.md b/site/core/api/actions/estimateMaxPriorityFeePerGas.md new file mode 100644 index 0000000000..906e98b931 --- /dev/null +++ b/site/core/api/actions/estimateMaxPriorityFeePerGas.md @@ -0,0 +1,74 @@ + + +# estimateMaxPriorityFeePerGas + +Returns an estimate for the max priority fee per gas (in wei) for a transaction to be likely included in the next block. + +## Import + +```ts +import { estimateMaxPriorityFeePerGas } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { estimateMaxPriorityFeePerGas } from '@wagmi/core' +import { config } from './config' + +const result = await estimateMaxPriorityFeePerGas(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type EstimateFeesPerGasParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { estimateMaxPriorityFeePerGas } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const result = await estimateMaxPriorityFeePerGas(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type EstimateFeesPerGasReturnType } from '@wagmi/core' +``` + +`bigint` + +An estimate (in wei) for the max priority fee per gas. + +## Error + +```ts +import { type EstimateFeesPerGasErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`estimateMaxPriorityFeePerGas`](https://viem.sh/docs/actions/public/estimateMaxPriorityFeePerGas.html) diff --git a/site/core/api/actions/getAccount.md b/site/core/api/actions/getAccount.md new file mode 100644 index 0000000000..a8dde7602e --- /dev/null +++ b/site/core/api/actions/getAccount.md @@ -0,0 +1,29 @@ +# getAccount + +Action for getting current account. + +## Import + +```ts +import { getAccount } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getAccount } from '@wagmi/core' +import { config } from './config' + +const account = getAccount(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetAccountReturnType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/getBalance.md b/site/core/api/actions/getBalance.md new file mode 100644 index 0000000000..192d5a69ce --- /dev/null +++ b/site/core/api/actions/getBalance.md @@ -0,0 +1,197 @@ + + +# getBalance + +Action for fetching native currency or token balance. + +## Import + +```ts +import { getBalance } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetBalanceParameters } from '@wagmi/core' +``` + +### address + +`Address` + +Address to get balance for. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get balance at. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockNumber: 17829139n, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get balance at. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockTag: 'latest', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const balance = await getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### token + +`Address | undefined` + +ERC-20 token address to get balance for. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### unit + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { getBalance } from '@wagmi/core' +import { config } from './config' + +const balance = getBalance(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + unit: 'ether', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetBalanceReturnType } from '@wagmi/core' +``` + +### decimals + +`number` + +Number of decimals for balance [`value`](#value). + +### formatted + +`string` + +Formatted value of balance using [`unit`](#unit). + +### symbol + +`string` + +Symbol of native currency or token. + +### value + +`bigint` + +Raw value of balance. + +## Error + +```ts +import { type GetBalanceErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getBalance`](https://viem.sh/docs/actions/public/getBalance.html) for native currency balances +- [`multicall`](https://viem.sh/docs/actions/public/multicall.html) for token balances diff --git a/site/core/api/actions/getBlock.md b/site/core/api/actions/getBlock.md new file mode 100644 index 0000000000..980b026247 --- /dev/null +++ b/site/core/api/actions/getBlock.md @@ -0,0 +1,146 @@ + + +# getBlock + +Action for fetching information about a block at a block number, hash or tag. + +## Import + +```ts +import { getBlock } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlock(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetBlockParameters } from '@wagmi/core' +``` + +### blockHash + +`` `0x${string}` `` + +Information at a given block hash. + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlock(config, { + blockHash: '0x89644bbd5c8d682a2e9611170e6c1f02573d866d286f006cbf517eec7254ec2d' // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`` bigint `` + +Information at a given block number. + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlock(config, { + blockNumber: 42069n // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`` 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' `` + +Information at a given block tag. Defaults to `'latest'`. + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlock(config, { + blockTag: 'pending' // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const blockNumber = await getBlock(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### includeTransactions + +`boolean` + +Whether or not to include transactions as objects. + +::: code-group +```ts [index.ts] +import { getBlock } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlock(config, { + includeTransactions: true // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetBlockReturnType } from '@wagmi/core' +``` + +[`Block`](https://viem.sh/docs/glossary/types.html#block) + +Information about the block. + +## Error + +```ts +import { type GetBlockErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getBlock`](https://viem.sh/docs/actions/public/getBlock.html) diff --git a/site/core/api/actions/getBlockNumber.md b/site/core/api/actions/getBlockNumber.md new file mode 100644 index 0000000000..ce13cbb14e --- /dev/null +++ b/site/core/api/actions/getBlockNumber.md @@ -0,0 +1,93 @@ + + +# getBlockNumber + +Action for fetching the number of the most recent block seen. + +## Import + +```ts +import { getBlockNumber } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getBlockNumber } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlockNumber(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetBlockNumberParameters } from '@wagmi/core' +``` + +### cacheTime + +`number | undefined` + +Time in milliseconds that cached block number will remain in memory. + +::: code-group +```ts [index.ts] +import { getBlockNumber } from '@wagmi/core' +import { config } from './config' + +const blockNumber = await getBlockNumber(config, { + cacheTime: 4_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getBlockNumber } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const blockNumber = await getBlockNumber(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetBlockNumberReturnType } from '@wagmi/core' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type GetBlockNumberErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getBlockNumber`](https://viem.sh/docs/actions/public/getBlockNumber.html) +- [`watchBlockNumber`](https://viem.sh/docs/actions/public/watchBlockNumber.html) diff --git a/site/core/api/actions/getBlockTransactionCount.md b/site/core/api/actions/getBlockTransactionCount.md new file mode 100644 index 0000000000..c351272209 --- /dev/null +++ b/site/core/api/actions/getBlockTransactionCount.md @@ -0,0 +1,92 @@ + + +# getBlockTransactionCount + +Action for fetching the number of Transactions at a block number, hash or tag. + +## Import + +```ts +import { getBlockTransactionCount } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getBlockTransactionCount } from '@wagmi/core' +import { config } from './config' + +const blockTransactionCount = await getBlockTransactionCount(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetBlockTransactionCountParameters } from '@wagmi/core' +``` + +### cacheTime + +`number | undefined` + +Time in milliseconds that cached block transaction count will remain in memory. + +::: code-group +```ts [index.ts] +import { getBlockTransactionCount } from '@wagmi/core' +import { config } from './config' + +const blockTransactionCount = await getBlockTransactionCount(config, { + cacheTime: 4_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getBlockTransactionCount } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const blockTransactionCount = await getBlockTransactionCount(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetBlockTransactionCountReturnType } from '@wagmi/core' +``` + +`number` + +The number of Transactions at a block number + +## Error + +```ts +import { type GetBlockTransactionCountErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getBlockTransactionCount`](https://viem.sh/docs/actions/public/getBlockTransactionCount.html) diff --git a/site/core/api/actions/getBytecode.md b/site/core/api/actions/getBytecode.md new file mode 100644 index 0000000000..3a926d6eea --- /dev/null +++ b/site/core/api/actions/getBytecode.md @@ -0,0 +1,133 @@ + + +# getBytecode + +Action for retrieving the bytecode at an address. + +## Import + +```ts +import { getBytecode } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getBytecode } from '@wagmi/core' +import { config } from './config' + +await getBytecode(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetBytecodeParameters } from '@wagmi/core' +``` + +### address + +`Address` + +The contract address. + +::: code-group +```ts [index.ts] +import { getBytecode } from '@wagmi/core' +import { config } from './config' + +await getBytecode(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +The block number to check the bytecode at. + +::: code-group +```ts [index.ts] +import { getBytecode } from '@wagmi/core' +import { config } from './config' + +await getBytecode(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockNumber: 16280770n, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to check the bytecode at. + +::: code-group +```ts [index.ts] +import { getBytecode } from '@wagmi/core' +import { config } from './config' + +await getBytecode(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockTag: 'safe', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The chain ID to check the bytecode at. + +::: code-group +```ts [index.ts] +import { getBytecode } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +await getBytecode(config, { + chainId: mainnet.id, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetBytecodeReturnType } from '@wagmi/core' +``` + +`Hex` + +The contract's bytecode. + +## Error + +```ts +import { type GetBytecodeErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getCode`](https://viem.sh/docs/contract/getCode) diff --git a/site/core/api/actions/getCallsStatus.md b/site/core/api/actions/getCallsStatus.md new file mode 100644 index 0000000000..dc03862b19 --- /dev/null +++ b/site/core/api/actions/getCallsStatus.md @@ -0,0 +1,97 @@ + + +# getCallsStatus + +Action to fetch the status and receipts of a call batch that was sent via [`sendCalls`](/core/api/actions/sendCalls). + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_getcallsstatus) + +## Import + +```ts +import { getCallsStatus } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await getCallsStatus(config, { + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetCallsStatusParameters } from '@wagmi/core' +``` + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```ts [index.ts] +import { getConnections, getCallsStatus } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const status = await getCallsStatus(config, { + connector: connections[0]?.connector, // [!code focus] + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { getCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await getCallsStatus(config, { + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetCallsStatusReturnType } from '@wagmi/core' +``` + +`{ status: 'PENDING' | 'CONFIRMED', receipts: TransactionReceipt[] }` + +The status and receipts of the call batch. + +## Error + +```ts +import { type GetCallsStatusErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getCallsStatus`](https://viem.sh/experimental/eip5792/getCallsStatus) diff --git a/site/core/api/actions/getCapabilities.md b/site/core/api/actions/getCapabilities.md new file mode 100644 index 0000000000..36bad2bdda --- /dev/null +++ b/site/core/api/actions/getCapabilities.md @@ -0,0 +1,96 @@ + + +# getCapabilities + +Action to extract capabilities (grouped by chain ID) that a connected wallet supports (e.g. paymasters, session keys, etc). + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_getcapabilities) + + + +## Import + +```ts +import { getCapabilities } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getCapabilities } from '@wagmi/core' +import { config } from './config' + +const capabilities = await getCapabilities(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetCapabilitiesParameters } from '@wagmi/core' +``` + +### account + +`Account | Address | undefined` + +Fetch capabilities for the provided account. + +::: code-group +```ts [index.ts] +import { getCapabilities } from '@wagmi/core' +import { config } from './config' + +const capabilities = await getCapabilities(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get capabilities from. + +::: code-group +```ts [index.ts] +import { getConnections, getCapabilities } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const capabilities = await getCapabilities(config, { + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetCapabilitiesReturnType } from '@wagmi/core' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type GetCapabilitiesErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getCapabilities`](https://viem.sh/experimental/eip5792/getCapabilities) diff --git a/site/core/api/actions/getChainId.md b/site/core/api/actions/getChainId.md new file mode 100644 index 0000000000..9d03f8468e --- /dev/null +++ b/site/core/api/actions/getChainId.md @@ -0,0 +1,38 @@ +# getChainId + +Action for getting current chain ID. + + +## Import + +```ts +import { getChainId } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getChainId } from '@wagmi/core' +import { config } from './config' + +const chainId = getChainId(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetChainIdReturnType } from '@wagmi/core' +``` + +`number` + +Current chain ID from [`config.state.chainId`](/core/api/createConfig#chainid). + +::: info +Only returns chain IDs for chains configured via `createConfig`'s [`chains`](/core/api/createConfig#chains) parameter. + +If the active [connection](/core/api/createConfig#connection) [`chainId`](/core/api/createConfig#chainid-1) is not from a chain included in your Wagmi `Config`, `getChainId` will return the last configured chain ID. +::: diff --git a/site/core/api/actions/getChains.md b/site/core/api/actions/getChains.md new file mode 100644 index 0000000000..c70334e027 --- /dev/null +++ b/site/core/api/actions/getChains.md @@ -0,0 +1,31 @@ +# getChains + +Action for getting configured chains. + +## Import + +```ts +import { getChains } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getChains } from '@wagmi/core' +import { config } from './config' + +const chains = getChains(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetChainsReturnType } from '@wagmi/core' +``` + +`readonly [Chain, ...Chain[]]` + +Chains from [`config.chains`](/core/api/createConfig#chains). diff --git a/site/core/api/actions/getClient.md b/site/core/api/actions/getClient.md new file mode 100644 index 0000000000..9f29f73236 --- /dev/null +++ b/site/core/api/actions/getClient.md @@ -0,0 +1,56 @@ +# getClient + +Action for getting Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. + +## Import + +```ts +import { getClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getClient } from '@wagmi/core' +import { config } from './config' + +const client = getClient(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetClientParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when getting Viem Client. + +::: code-group +```ts [index.ts] +import { getClient } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const client = await getClient(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetClientReturnType } from '@wagmi/core' +``` + +`Client` + +Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. diff --git a/site/core/api/actions/getConnections.md b/site/core/api/actions/getConnections.md new file mode 100644 index 0000000000..513d0d017d --- /dev/null +++ b/site/core/api/actions/getConnections.md @@ -0,0 +1,31 @@ +# getConnections + +Action for getting active connections. + +## Import + +```ts +import { getConnections } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getConnections } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetConnectionsReturnType } from '@wagmi/core' +``` + +[`Connection[]`](/core/api/createConfig#connection) + +Active connections. diff --git a/site/core/api/actions/getConnectorClient.md b/site/core/api/actions/getConnectorClient.md new file mode 100644 index 0000000000..46d2795c7a --- /dev/null +++ b/site/core/api/actions/getConnectorClient.md @@ -0,0 +1,108 @@ + + +# getConnectorClient + +Action for getting a Viem [`Client`](https://viem.sh/docs/clients/custom.html) object for the current or provided connector. + +## Import + +```ts +import { getConnectorClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getConnectorClient } from '@wagmi/core' +import { config } from './config' + +const client = await getConnectorClient(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetConnectorClientParameters } from '@wagmi/core' +``` + +### account + +`Address | Account | undefined` + +Account to use with client. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { getConnectorClient } from '@wagmi/core' +import { config } from './config' + +const client = await getConnectorClient(config, { + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use with client. + +::: code-group +```ts [index.ts] +import { getConnectorClient } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const client = await getConnectorClient(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to get client for. +- Defaults to current connector. + +::: code-group +```ts [index.ts] +import { getConnections, getConnectorClient } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const client = await getConnectorClient(config, { + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetChainIdReturnType } from '@wagmi/core' +``` + +`Client` + +Viem [`Client`](https://viem.sh/docs/clients/custom.html) object for the current or provided connector. + +## Error + +```ts +import { type GetConnectorClientErrorType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/getConnectors.md b/site/core/api/actions/getConnectors.md new file mode 100644 index 0000000000..ddbb049be9 --- /dev/null +++ b/site/core/api/actions/getConnectors.md @@ -0,0 +1,31 @@ +# getConnectors + +Action for getting configured connectors. + +## Import + +```ts +import { getConnectors } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getConnectors } from '@wagmi/core' +import { config } from './config' + +const connectors = getConnectors(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetConnectorsReturnType } from '@wagmi/core' +``` + +`readonly Connector[]` + +Connectors from [`config.connectors`](/core/api/createConfig#connectors-1). diff --git a/site/core/api/actions/getEnsAddress.md b/site/core/api/actions/getEnsAddress.md new file mode 100644 index 0000000000..dc44dbb732 --- /dev/null +++ b/site/core/api/actions/getEnsAddress.md @@ -0,0 +1,187 @@ + + +# getEnsAddress + +Action for fetching ENS address for name. + +## Import + +```ts +import { getEnsAddress } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = getEnsAddress(config, { + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `getEnsAddress`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type GetEnsAddressParameters } from '@wagmi/core' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS address at. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = getEnsAddress(config, { + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS address at. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = getEnsAddress(config, { + blockTag: 'latest', // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = await getEnsAddress(config, { + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### coinType + +`number | undefined` + +The [ENSIP-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution) coin type to fetch the address for. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = await getEnsAddress(config, { + coinType: 60, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### name + +`string` + +Name to get the address for. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = await getEnsAddress(config, { + name: normalize('wevm.eth'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAddress = await getEnsAddress(config, { + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetEnsAddressReturnType } from '@wagmi/core' +``` + +`string` + +ENS address. + +## Error + +```ts +import { type GetEnsAddressErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getEnsAddress`](https://viem.sh/docs/ens/actions/getEnsAddress.html) diff --git a/site/core/api/actions/getEnsAvatar.md b/site/core/api/actions/getEnsAvatar.md new file mode 100644 index 0000000000..b7da4aa0f0 --- /dev/null +++ b/site/core/api/actions/getEnsAvatar.md @@ -0,0 +1,210 @@ + + +# getEnsAvatar + +Action for fetching ENS address for avatar. + +## Import + +```ts +import { getEnsAvatar } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `getEnsAvatar`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type GetEnsAvatarParameters } from '@wagmi/core' +``` + +--- + +### assetGatewayUrls + +`{ ipfs?: string | undefined; arweave?: string | undefined } | undefined` + +Gateway urls to resolve IPFS and/or Arweave assets. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + assetGatewayUrls: { // [!code focus] + ipfs: 'https://cloudflare-ipfs.com', // [!code focus] + }, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + + +### blockNumber + +`bigint | undefined` + +Block number to get avatar at. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get avatar at. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + blockTag: 'latest', // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gatewayUrls + +`string[] | undefined` + +A set of Universal Resolver gateways, used for resolving CCIP-Read requests made through the ENS Universal Resolver Contract. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + gatewayUrls: ['https://cloudflare-ipfs.com'] { // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### name + +`string` + +Name to get the avatar for. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + name: normalize('wevm.eth'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensAvatar = await getEnsAvatar(config, { + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetEnsAvatarReturnType } from '@wagmi/core' +``` + +`string | null` + +The avatar URI for ENS name. + +## Error + +```ts +import { type getEnsAvatarError } from '@wagmi/core' +``` + + + +## Viem + +- [`getEnsAvatar`](https://viem.sh/docs/ens/actions/getEnsAvatar.html) diff --git a/site/core/api/actions/getEnsName.md b/site/core/api/actions/getEnsName.md new file mode 100644 index 0000000000..f663923f94 --- /dev/null +++ b/site/core/api/actions/getEnsName.md @@ -0,0 +1,157 @@ + + +# getEnsName + +Action for fetching primary ENS name for address. + +## Import + +```ts +import { getEnsName } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { config } from './config' + +const ensName = getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetEnsNameParameters } from '@wagmi/core' +``` + +### address + +`Address` + +Address to get the name for. + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { config } from './config' + +const ensName = await getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get name at. + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { config } from './config' + +const ensName = getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + blockNumber: 17829139n, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get name at. + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { config } from './config' + +const ensName = getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + blockTag: 'latest', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const ensName = await getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { getEnsName } from '@wagmi/core' +import { config } from './config' + +const ensName = await getEnsName(config, { + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + universalResolverName: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetEnsNameReturnType } from '@wagmi/core' +``` + +`string | null` + +The primary ENS name for the address. Returns `null` if address does not have primary name assigned. + +## Error + +```ts +import { type GetEnsNameErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getEnsName`](https://viem.sh/docs/ens/actions/getEnsName.html) diff --git a/site/core/api/actions/getEnsResolver.md b/site/core/api/actions/getEnsResolver.md new file mode 100644 index 0000000000..c73301e071 --- /dev/null +++ b/site/core/api/actions/getEnsResolver.md @@ -0,0 +1,167 @@ + + +# getEnsResolver + +Action for fetching ENS resolver for name. + +## Import + +```ts +import { getEnsResolver } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = getEnsResolver(config, { + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `getEnsResolver`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type GetEnsResolverParameters } from '@wagmi/core' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get resolver at. + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = getEnsResolver(config, { + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get resolver at. + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = getEnsResolver(config, { + blockTag: 'latest', // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = await getEnsResolver(config, { + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### name + +`string` + +Name to get the resolver for. + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = await getEnsResolver(config, { + name: normalize('wevm.eth'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { getEnsResolver } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensResolver = await getEnsResolver(config, { + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetEnsResolverReturnType } from '@wagmi/core' +``` + +`Address` + +The address of the resolver. + +## Error + +```ts +import { type getEnsResolverError } from '@wagmi/core' +``` + + + +## Viem + +- [`getEnsResolver`](https://viem.sh/docs/ens/actions/getEnsResolver.html) diff --git a/site/core/api/actions/getEnsText.md b/site/core/api/actions/getEnsText.md new file mode 100644 index 0000000000..a5ffb8ed5a --- /dev/null +++ b/site/core/api/actions/getEnsText.md @@ -0,0 +1,195 @@ + + +# getEnsText + +Action for fetching a text record for a specified ENS name and key. + +## Import + +```ts +import { getEnsText } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = getEnsText(config, { + name: normalize('wevm.eth'), + key: 'com.twitter', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `getEnsText`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type GetEnsTextParameters } from '@wagmi/core' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get the text at. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = getEnsText(config, { + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get the text at. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = getEnsText(config, { + blockTag: 'latest', // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = await getEnsText(config, { + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### key + +`string` + +ENS key to get Text for. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = await getEnsText(config, { + name: normalize('wevm.eth'), + key: 'com.twitter', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### name + +`string` + +Name to get the text for. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = await getEnsText(config, { + name: normalize('wevm.eth'), // [!code focus] + key: 'com.twitter', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { getEnsText } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +const ensText = await getEnsText(config, { + name: normalize('wevm.eth'), + key: 'com.twitter', + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetEnsTextReturnType } from '@wagmi/core' +``` + +`string | null` + +The text record for ENS name. + +Returns `null` if name does not have text assigned. + +## Error + +```ts +import { type getEnsTextError } from '@wagmi/core' +``` + + + +## Viem + +- [`getEnsText`](https://viem.sh/docs/ens/actions/getEnsText.html) diff --git a/site/core/api/actions/getFeeHistory.md b/site/core/api/actions/getFeeHistory.md new file mode 100644 index 0000000000..3787d8f66b --- /dev/null +++ b/site/core/api/actions/getFeeHistory.md @@ -0,0 +1,157 @@ + + +# getFeeHistory + +Action for fetching a collection of historical gas information. + +## Import + +```ts +import { getFeeHistory } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetFeeHistoryParameters } from '@wagmi/core' +``` + +### blockCount + +`number` + +Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, // [!code focus] + rewardPercentiles: [25, 75] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### rewardPercentiles + +`number[]` + +A monotonically increasing list of percentile values to sample from each block's effective priority fees per gas in ascending order, weighted by gas used. + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, + rewardPercentiles: [25, 75] // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Highest number block of the requested range. + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, + blockNumber: 1551231n, // [!code focus] + rewardPercentiles: [25, 75], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag of the highest number block of the requested range. + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, + blockTag: 'safe', // [!code focus] + rewardPercentiles: [25, 75], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getFeeHistory } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const feeHistory = await getFeeHistory(config, { + blockCount: 4, + chainId: mainnet.id, // [!code focus] + rewardPercentiles: [25, 75], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetFeeHistoryReturnType } from '@wagmi/core' +``` + +[`FeeHistory`](https://viem.sh/docs/glossary/types.html#feehistory) + +The fee history. + +## Error + +```ts +import { type GetFeeHistoryErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getFeeHistory`](https://viem.sh/docs/actions/public/getFeeHistory.html) diff --git a/site/core/api/actions/getGasPrice.md b/site/core/api/actions/getGasPrice.md new file mode 100644 index 0000000000..106829fcfd --- /dev/null +++ b/site/core/api/actions/getGasPrice.md @@ -0,0 +1,74 @@ + + +# getGasPrice + +Action for fetching the current price of gas (in wei). + +## Import + +```ts +import { getGasPrice } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getGasPrice } from '@wagmi/core' +import { config } from './config' + +const gasPrice = await getGasPrice(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetGasPriceParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getGasPrice } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const gasPrice = await getGasPrice(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetGasPriceReturnType } from '@wagmi/core' +``` + +`bigint` + +Current price of gas (in wei). + +## Error + +```ts +import { type GetGasPriceErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getGasPrice`](https://viem.sh/docs/actions/public/getGasPrice.html) diff --git a/site/core/api/actions/getProof.md b/site/core/api/actions/getProof.md new file mode 100644 index 0000000000..60cd493260 --- /dev/null +++ b/site/core/api/actions/getProof.md @@ -0,0 +1,169 @@ + + +# getProof + +Action for return the account and storage values of the specified account including the Merkle-proof. + +## Import + +```ts +import { getProof } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' + +await getProof(config, { + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetProofParameters } from '@wagmi/core' +``` + +### address + +`Address` + +The account address to get the proof for. + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' + +await getProof(config, { + address: '0x4200000000000000000000000000000000000016', // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### storageKeys + +`` `0x${string}`[] `` + +Array of storage-keys that should be proofed and included. + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' + +await getProof(config, { + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ // [!code focus:3] + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Proof at a given block number. + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' + +await getProof(config, { + address: '0x4200000000000000000000000000000000000016', + blockNumber: 42069n, // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Proof at a given block tag. + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' + +await getProof(config, { + address: '0x4200000000000000000000000000000000000016', + blockTag: 'latest', // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The ID of chain to get the proof for. + +::: code-group +```ts [index.ts] +import { getProof } from '@wagmi/core' +import { config } from './config' +import { optimism } from '@wagmi/core/chains' + +await getProof(config, { + chainId: optimism.id, // [!code focus] + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetProofReturnType } from '@wagmi/core' +``` + +`Proof` + +Proof data. + +## Error + +```ts +import { type GetProofErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getProof`](https://viem.sh/docs/actions/public/getProof.html) diff --git a/site/core/api/actions/getPublicClient.md b/site/core/api/actions/getPublicClient.md new file mode 100644 index 0000000000..437104aa78 --- /dev/null +++ b/site/core/api/actions/getPublicClient.md @@ -0,0 +1,60 @@ +# getPublicClient + +Action for getting Viem [`PublicClient`](https://viem.sh/docs/clients/public.html) instance. + +## Import + +```ts +import { getPublicClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getPublicClient } from '@wagmi/core' +import { config } from './config' + +const client = getPublicClient(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +If you want to optimize bundle size, you should use [`getClient`](/core/api/actions/getClient) along with Viem's [tree-shakable actions](https://viem.sh/docs/clients/custom.html#tree-shaking) instead. Since Public Client has all public actions attached directly to it. +::: + +## Parameters + +```ts +import { type GetClientParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when getting Viem Public Client. + +::: code-group +```ts [index.ts] +import { getPublicClient } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const client = getPublicClient(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetPublicClientReturnType } from '@wagmi/core' +``` + +`PublicClient | undefined` + +Viem [`PublicClient`](https://viem.sh/docs/clients/public.html) instance. diff --git a/site/core/api/actions/getStorageAt.md b/site/core/api/actions/getStorageAt.md new file mode 100644 index 0000000000..23a5dc1d2f --- /dev/null +++ b/site/core/api/actions/getStorageAt.md @@ -0,0 +1,157 @@ + + +# getStorageAt + +Action for returning the value from a storage slot at a given address. + +## Import + +```ts +import { getStorageAt } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { config } from './config' + +await getStorageAt(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetStorageAtParameters } from '@wagmi/core' +``` + +### address + +`Address` + +The contract address. + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { config } from './config' + +await getStorageAt(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + slot: '0x0', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### slot + +`Hex` + +The storage position (as a hex encoded value). + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { config } from './config' + +await getStorageAt(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +The block number to check the storage at. + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { config } from './config' + +await getStorageAt(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockNumber: 16280770n, // [!code focus] + slot: '0x0', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to check the storage at. + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { config } from './config' + +await getStorageAt(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockTag: 'safe', // [!code focus] + slot: '0x0', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The chain ID to check the storage at. + +::: code-group +```ts [index.ts] +import { getStorageAt } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +await getStorageAt(config, { + chainId: mainnet.id, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetStorageAtReturnType } from '@wagmi/core' +``` + +`Hex` + +The value of the storage slot. + +## Error + +```ts +import { type GetStorageAtErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getStorageAt`](https://viem.sh/docs/contract/getStorageAt) diff --git a/site/core/api/actions/getToken.md b/site/core/api/actions/getToken.md new file mode 100644 index 0000000000..b37805cff0 --- /dev/null +++ b/site/core/api/actions/getToken.md @@ -0,0 +1,141 @@ + + +# getToken + +Action for fetching token info. + +## Import + +```ts +import { getToken } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getToken } from '@wagmi/core' +import { config } from './config' + +const token = getToken(config, { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetTokenParameters } from '@wagmi/core' +``` + +### address + +`Address` + +Address to get token for. + +::: code-group +```ts [index.ts] +import { getToken } from '@wagmi/core' +import { config } from './config' + +const token = getToken(config, { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getToken } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const token = await getToken(config, { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### formatUnits + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { getToken } from '@wagmi/core' +import { config } from './config' + +const token = getToken(config, { + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + formatUnits: 'ether', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetTokenReturnType } from '@wagmi/core' +``` + +### address + +`Address` + +Address of token. + +### decimals + +`number` + +Number of decimals for token. + +### name + +`string | undefined` + +Name of token. + +### symbol + +`string | undefined` + +Symbol of token. + +### totalSupply + +`{ formatted: string; value: bigint; }` + +Total supply of token. `formatted` is formatted using [`formatUnits`](#formatunits). + +## Error + +```ts +import { type GetTokenErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`multicall`](https://viem.sh/docs/contract/multicall) diff --git a/site/core/api/actions/getTransaction.md b/site/core/api/actions/getTransaction.md new file mode 100644 index 0000000000..8ebe770990 --- /dev/null +++ b/site/core/api/actions/getTransaction.md @@ -0,0 +1,173 @@ + + +# getTransaction + +Action for fetching transactions given hashes or block identifiers. + +## Import + +```ts +import { getTransaction } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetTransactionParameters } from '@wagmi/core' +``` + +--- + +### blockHash + +`bigint | undefined` + +Block hash to get transaction at (with [`index`](#index)). + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + blockHash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] + index: 0, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Block number to get transaction at (with [`index`](#index)). + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + blockNumber: 17829139n, // [!code focus] + index: 0, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get transaction at (with [`index`](#index)). + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + blockTag: 'safe', // [!code focus] + index: 0, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const transaction = await getTransaction(config, { + chainId: mainnet.id, // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### hash + +`` `0x${string}` | undefined `` + +Hash to get transaction. + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### index + +`number | undefined` + +An index to be used with a block identifier ([hash](#blockhash), [number](#blocknumber), or [tag](#blocktag)). + +::: code-group +```ts [index.ts] +import { getTransaction } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransaction(config, { + blockTag: 'safe', + index: 0 // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetTransactionReturnType } from '@wagmi/core' +``` + +[`Transaction`](https://viem.sh/docs/glossary/types.html#transaction) + +## Error + +```ts +import { type GetTransactionErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getTransaction`](https://viem.sh/docs/actions/public/getTransaction.html) diff --git a/site/core/api/actions/getTransactionConfirmations.md b/site/core/api/actions/getTransactionConfirmations.md new file mode 100644 index 0000000000..eb42df19ac --- /dev/null +++ b/site/core/api/actions/getTransactionConfirmations.md @@ -0,0 +1,117 @@ + + +# getTransactionConfirmations + +Action for fetching the number of blocks passed (confirmations) since the transaction was processed on a block. If confirmations is 0, then the Transaction has not been confirmed & processed yet. + +## Import + +```ts +import { getTransactionConfirmations } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getTransactionConfirmations } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransactionConfirmations(config, { + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetTransactionConfirmationsParameters } from '@wagmi/core' +``` + +--- + +### hash + +`` `0x${string}` | undefined `` + +The hash of the transaction. + +::: code-group +```ts [index.ts] +import { getTransactionConfirmations } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransactionConfirmations(config, { + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### transactionReceipt + +`TransactionReceipt | undefined` + +The transaction receipt. + +::: code-group +```ts [index.ts] +import { getTransactionConfirmations } from '@wagmi/core' +import { config } from './config' + +const transaction = getTransactionConfirmations(config, { + transactionReceipt: { ... }, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getTransactionConfirmations } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const transaction = await getTransactionConfirmations(config, { + chainId: mainnet.id, // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetTransactionConfirmationsReturnType } from '@wagmi/core' +``` + +`bigint` + +The number of blocks passed since the transaction was processed. If confirmations is 0, then the Transaction has not been confirmed & processed yet. + +## Error + +```ts +import { type GetTransactionConfirmationsErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getTransactionConfirmations`](https://viem.sh/docs/actions/public/getTransactionConfirmations.html) diff --git a/site/core/api/actions/getTransactionCount.md b/site/core/api/actions/getTransactionCount.md new file mode 100644 index 0000000000..d08987c84d --- /dev/null +++ b/site/core/api/actions/getTransactionCount.md @@ -0,0 +1,139 @@ + + +# getTransactionCount + +Action for fetching the number of transactions an Account has sent. + +## Import + +```ts +import { getTransactionCount } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getTransactionCount } from '@wagmi/core' +import { config } from './config' + +const transactionCount = getTransactionCount(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetTransactionCountParameters } from '@wagmi/core' +``` + +--- + +### address + +`Address` + +The address of the account. + +::: code-group +```ts [index.ts] +import { getTransactionCount } from '@wagmi/core' +import { config } from './config' + +const transactionCount = getTransactionCount(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Get the count at a block number. + +::: code-group +```ts [index.ts] +import { getTransactionCount } from '@wagmi/core' +import { config } from './config' + +const transactionCount = getTransactionCount(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockNumber: 17829139n, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Get the count at a block tag. + +::: code-group +```ts [index.ts] +import { getTransactionCount } from '@wagmi/core' +import { config } from './config' + +const transactionCount = getTransactionCount(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockTag: 'latest', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { getTransactionCount } from '@wagmi/core' +import { config } from './config' + +const transactionCount = getTransactionCount(config, { + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + + +## Return Type + +```ts +import { type GetTransactionCountReturnType } from '@wagmi/core' +``` + +`number` + +The number of transactions an account has sent. + +## Error + +```ts +import { type GetTransactionCountErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getTransactionCount`](https://viem.sh/docs/actions/public/getTransactionCount.html) diff --git a/site/core/api/actions/getTransactionReceipt.md b/site/core/api/actions/getTransactionReceipt.md new file mode 100644 index 0000000000..d0687ac63e --- /dev/null +++ b/site/core/api/actions/getTransactionReceipt.md @@ -0,0 +1,95 @@ + + +# getTransactionReceipt + +Action for return the [Transaction Receipt](https://viem.sh/docs/glossary/terms.html#transaction-receipt) given a [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + +## Import + +```ts +import { getTransactionReceipt } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +await getTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type GetTransactionReceiptParameters } from '@wagmi/core' +``` + +### hash + +`` `0x${string}` `` + +A transaction hash. + +::: code-group +```ts [index.ts] +import { getTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +await getTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The ID of chain to return the transaction receipt from. + +::: code-group +```ts [index.ts] +import { getTransactionReceipt } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +await getTransactionReceipt(config, { + chainId: mainnet.id, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetTransactionReceiptReturnType } from '@wagmi/core' +``` + +[`TransactionReceipt`](https://viem.sh/docs/glossary/types.html#transactionreceipt) + +The transaction receipt. + +## Error + +```ts +import { type GetTransactionReceiptErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`getTransactionReceipt`](https://viem.sh/docs/actions/public/getTransactionReceipt.html) diff --git a/site/core/api/actions/getWalletClient.md b/site/core/api/actions/getWalletClient.md new file mode 100644 index 0000000000..a321b3637c --- /dev/null +++ b/site/core/api/actions/getWalletClient.md @@ -0,0 +1,112 @@ + + +# getWalletClient + +Action for getting a Viem [`WalletClient`](https://viem.sh/docs/clients/wallet.html) object for the current or provided connector. + +## Import + +```ts +import { getWalletClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getWalletClient } from '@wagmi/core' +import { config } from './config' + +const client = getWalletClient(config) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: warning +If you want to optimize bundle size, you should use [`getConnectorClient`](/core/api/actions/getConnectorClient) along with Viem's [tree-shakable actions](https://viem.sh/docs/clients/custom.html#tree-shaking) instead. Since Wallet Client has all wallet actions attached directly to it. +::: + +## Parameters + +```ts +import { type GetWalletClientParameters } from '@wagmi/core' +``` + +### account + +`Address | Account | undefined` + +Account to use with client. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { getWalletClient } from '@wagmi/core' +import { config } from './config' + +const client = getWalletClient(config, { + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use with client. + +::: code-group +```ts [index.ts] +import { getWalletClient } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const client = getWalletClient(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to get client for. +- Defaults to current connector. + +::: code-group +```ts [index.ts] +import { getConnections, getWalletClient } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const client = getWalletClient(config, { + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type GetChainIdReturnType } from '@wagmi/core' +``` + +`WalletClient` + +Viem [`WalletClient`](https://viem.sh/docs/clients/wallet.html) object for the current or provided connector. + +## Error + +```ts +import { type GetWalletClientErrorType } from '@wagmi/core' +``` + + \ No newline at end of file diff --git a/site/core/api/actions/multicall.md b/site/core/api/actions/multicall.md new file mode 100644 index 0000000000..ec46368d17 --- /dev/null +++ b/site/core/api/actions/multicall.md @@ -0,0 +1,355 @@ +# multicall + +Action for batching up multiple functions on a contract in a single RPC call via the [Multicall3 contract](https://github.com/mds1/multicall). + +## Import + +```ts +import { multicall } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const wagmigotchiContract = { + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + abi: wagmigotchiABI, +} as const +const mlootContract = { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, +} as const + +const result = await multicall(config, { + contracts: [ + { + ...wagmigotchiContract, + functionName: 'getAlive', + }, + { + ...wagmigotchiContract, + functionName: 'getBoredom', + }, + { + ...mlootContract, + functionName: 'getChest', + args: [69], + }, + { + ...mlootContract, + functionName: 'getWaist', + args: [69], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type MulticallParameters } from '@wagmi/core' +``` + +### contracts + +`readonly Contract[]` + +Set of contracts to call. + +#### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, // [!code hl] + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', // [!code hl] + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], // [!code hl] + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + chainId: 1, // [!code hl] + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +#### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', // [!code hl] + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### allowFailure + +`boolean` + +Whether or not the Hook should throw if a call reverts. If set to `true` (default), and a call reverts, then `multicall` will fail silently and its error will be logged in the results array. Defaults to `true`. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + allowFailure: false, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### batchSize + +`number` + +The maximum size (in bytes) for each calldata chunk. Set to `0` to disable the size limit. Defaults to `1024`. + +> Note: Some RPC Providers limit the amount of calldata (`data`) that can be sent in a single `eth_call` request. It is best to check with your RPC Provider to see if there are any calldata size limits to `eth_call` requests. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + batchSize: 1_024, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`number` + +The block number to perform the read against. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + blockNumber: 69420n, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to read against. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + blockTag: 'safe', // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### multicallAddress + +`Address` + +Address of multicall contract. + +::: code-group +```tsx [index.tsx] +import { multicall } from '@wagmi/core' +import { config } from './config' + +const result = await multicall(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], + multicallAddress: '0xca11bde05977b3631167028862be2a173976ca11', // [!code hl] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +## Return Type + +```ts +import { type MulticallReturnType } from '@wagmi/core' +``` + +## Type Inference + +With [`contracts[number]['abi']`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and the return type. See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type MulticallErrorType } from '@wagmi/core' +``` + +## Viem + +- [`multicall`](https://viem.sh/docs/actions/public/multicall.html) diff --git a/site/core/api/actions/prepareTransactionRequest.md b/site/core/api/actions/prepareTransactionRequest.md new file mode 100644 index 0000000000..1a1f1fc207 --- /dev/null +++ b/site/core/api/actions/prepareTransactionRequest.md @@ -0,0 +1,307 @@ + + +# prepareTransactionRequest + +Action for preparing a transaction request for signing by populating a nonce, gas limit, fee values, and a transaction type. + +## Import + +```ts +import { prepareTransactionRequest } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type PrepareTransactionRequestParameters } from '@wagmi/core' +``` + +### account + +`Account | Address | undefined` + +The Account to send the transaction from. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### to + +`` `0x${string}` | undefined `` + +The transaction recipient or contract address. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + accessList: [ // [!code focus:6] + { + address: '0x1', + storageKeys: ['0x1'], + }, + ], + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to prepare the transaction request for. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + chainId: mainnet.id, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded args. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gasPrice + +`bigint | undefined` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas (in wei), inclusive of `maxPriorityFeePerGas`. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas (in wei). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### nonce + +`number | undefined` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + nonce: 5, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### parameters + +`("fees" | "gas" | "nonce" | "type")[] | undefined` + +Parameters to prepare. + +For instance, if `["gas", "nonce"]` is provided, then only the `gas` and `nonce` parameters will be prepared. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + parameters: ['gas', 'nonce'], // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +The transaction recipient or contract address. + +::: code-group +```ts [index.ts] +import { prepareTransactionRequest } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +await prepareTransactionRequest(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type PrepareTransactionRequestReturnType } from '@wagmi/core' +``` + +[`TransactionRequest`](https://viem.sh/docs/glossary/types.html#transactionrequest) + +The transaction request. + +## Error + +```ts +import { type PrepareTransactionRequestErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`prepareTransactionRequest`](https://viem.sh/docs/actions/wallet/prepareTransactionRequest.html) diff --git a/site/core/api/actions/readContract.md b/site/core/api/actions/readContract.md new file mode 100644 index 0000000000..80dfbb0b2e --- /dev/null +++ b/site/core/api/actions/readContract.md @@ -0,0 +1,258 @@ + + +# readContract + +Action for calling a **read-only** function on a contract, and returning the response. + +A **read-only** function (constant function) on a Solidity contract is denoted by a pure or view keyword. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas. + +## Import + +```ts +import { readContract } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ReadContractParameters } from '@wagmi/core' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' // [!code focus] +import { config } from './config' + +const result = await readContract(config, { + abi, // [!code focus] + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Account | undefined` + +Account to use when calling the contract (`msg.sender`). + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### address + +`Address` + +The contract's address. + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + functionName: 'totalSupply', +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], // [!code focus] +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to call contract at. + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + blockNumber: 17829139n, // [!code focus] +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to call contract at. + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + blockTag: 'safe', // [!code focus] +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### functionName + +`string` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```ts [index.ts] +import { readContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await readContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', // [!code focus] + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], +}) +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ReadContractReturnType } from '@wagmi/core' +``` + +`unknown` + +- Result of contract read-only function. +- Inferred from [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and the return type. See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type ReadContractErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`readContract`](https://viem.sh/docs/contract/readContract.html) diff --git a/site/core/api/actions/readContracts.md b/site/core/api/actions/readContracts.md new file mode 100644 index 0000000000..67cbdf65af --- /dev/null +++ b/site/core/api/actions/readContracts.md @@ -0,0 +1,363 @@ + + +# readContracts + +Action for calling multiple read methods on a contract. + +## Import + +```ts +import { readContracts } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const wagmigotchiContract = { + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + abi: wagmigotchiABI, +} as const +const mlootContract = { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, +} as const + +const result = await readContracts(config, { + contracts: [ + { + ...wagmigotchiContract, + functionName: 'getAlive', + }, + { + ...wagmigotchiContract, + functionName: 'getBoredom', + }, + { + ...mlootContract, + functionName: 'getChest', + args: [69], + }, + { + ...mlootContract, + functionName: 'getWaist', + args: [69], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ReadContractsParameters } from '@wagmi/core' +``` + +### contracts + +`readonly Contract[]` + +Set of contracts to call. + +#### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, // [!code hl] + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', // [!code hl] + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], // [!code hl] + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + chainId: 1, // [!code hl] + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +#### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', // [!code hl] + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### allowFailure + +`boolean` + +Whether or not the Hook should throw if a call reverts. If set to `true` (default), and a call reverts, then `readContracts` will fail silently and its error will be logged in the results array. Defaults to `true`. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + allowFailure: false, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### batchSize + +`number` + +The maximum size (in bytes) for each calldata chunk. Set to `0` to disable the size limit. Defaults to `1024`. + +> Note: Some RPC Providers limit the amount of calldata (`data`) that can be sent in a single `eth_call` request. It is best to check with your RPC Provider to see if there are any calldata size limits to `eth_call` requests. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + batchSize: 1_024, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`number` + +The block number to perform the read against. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + blockNumber: 69420n, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to read against. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + blockTag: 'safe', // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### multicallAddress + +`Address` + +Address of multicall contract. + +::: code-group +```tsx [index.tsx] +import { readContracts } from '@wagmi/core' +import { config } from './config' + +const result = await readContracts(config, { + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], + multicallAddress: '0xca11bde05977b3631167028862be2a173976ca11', // [!code hl] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ReadContractsReturnType } from '@wagmi/core' +``` + +## Type Inference + +With [`contracts[number]['abi']`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and the return type. See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type ReadContractsErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`multicall`](https://viem.sh/docs/actions/public/multicall.html) when supported by current chain. +- [`readContract`](https://viem.sh/docs/contract/readContract.html) when multicall is not supported. diff --git a/site/core/api/actions/reconnect.md b/site/core/api/actions/reconnect.md new file mode 100644 index 0000000000..983be9aa76 --- /dev/null +++ b/site/core/api/actions/reconnect.md @@ -0,0 +1,72 @@ + + +# reconnect + +Action for reconnecting [connectors](/core/api/connectors). + +## Import + +```ts +import { reconnect } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { reconnect } from '@wagmi/core' +import { injected } from '@wagmi/connectors' +import { config } from './config' + +const result = await reconnect(config, { connectors: [injected()] }) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ReconnectParameters } from '@wagmi/core' +``` + +### connectors + +`(CreateConnectorFn | Connector)[] | undefined` + +- [Connectors](/core/api/connectors) to reconnect to. +- Defaults to [`Config['connectors']`](/core/api/createConfig#connectors). + +::: code-group +```ts [index.ts] +import { reconnect } from '@wagmi/core' +import { injected } from '@wagmi/connectors' +import { config } from './config' + +const result = await reconnect(config, { + connectors: [injected()], // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ReconnectReturnType } from '@wagmi/core' +``` + +`Connection[]` + +[Connections](/core/api/createConfig#connection) that were successfully reconnected. + +## Error + +```ts +import { type ReconnectErrorType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/sendCalls.md b/site/core/api/actions/sendCalls.md new file mode 100644 index 0000000000..8e65890e5e --- /dev/null +++ b/site/core/api/actions/sendCalls.md @@ -0,0 +1,223 @@ + + +# sendCalls + +Action that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + + + +## Import + +```ts +import { sendCalls } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SendCallsParameters } from '@wagmi/core' +``` + +### account + +`Account | Address | null | undefined` + +Account to execute the calls. + +If set to `null`, it is assumed that the wallet will handle filling the sender of the calls. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### calls + +`{ to: Hex, data?: Hex, value?: bigint }[]` + +Calls to execute. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ // [!code focus] + { // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: parseEther('1') // [!code focus] + }, // [!code focus] + { // [!code focus] + data: '0xdeadbeef', // [!code focus] + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + }, // [!code focus] + ], // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### capabilities + +`WalletCapabilities | undefined` + +Capability metadata for the calls (e.g. specifying a paymaster). + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + capabilities: { // [!code focus] + paymasterService: { // [!code focus] + url: 'https://...' // [!code focus] + } // [!code focus] + } // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`number | undefined` + +The target chain ID to broadcast the calls. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + chainId: 10, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get send the calls with. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { getConnections } from '@wagmi/core' +import { sendCalls } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const id = await sendCalls(config, { + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + data: '0xdeadbeef', + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + }, + ], + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SendCallsReturnType } from '@wagmi/core' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type SendCallsErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`sendCalls`](https://viem.sh/experimental/eip5792/sendCalls) diff --git a/site/core/api/actions/sendTransaction.md b/site/core/api/actions/sendTransaction.md new file mode 100644 index 0000000000..e02e1908dc --- /dev/null +++ b/site/core/api/actions/sendTransaction.md @@ -0,0 +1,341 @@ + + +# sendTransaction + +Action for creating, signing, and sending transactions to networks. + +## Import + +```ts +import { sendTransaction } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SendTransactionParameters } from '@wagmi/core' +``` + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when sending transaction. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to validate against before sending transaction. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + chainId: mainnet.id, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to send transaction with. +- Defaults to current connector. + +::: code-group +```ts [index.ts] +import { getConnections, sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const connections = getConnections(config) +const result = await sendTransaction(config, { + connector: connections[0]?.connector, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded args. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### gas + +`bigint | undefined | null` + +Gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + gas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + gasPrice: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + nonce: 123, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### to + +`Address` + +The transaction recipient or contract address. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + type: 'eip1559', // [!code focus] + value: parseEther('0.01'), +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```ts [index.ts] +import { sendTransaction } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +const result = await sendTransaction(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SendTransactionReturnType } from '@wagmi/core' +``` + +[`Hash`](https://viem.sh/docs/glossary/types.html#hash) + +Transaction hash. + +## Error + +```ts +import { type SendTransactionErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`sendTransaction`](https://viem.sh/docs/actions/wallet/sendTransaction.html) diff --git a/site/core/api/actions/showCallsStatus.md b/site/core/api/actions/showCallsStatus.md new file mode 100644 index 0000000000..c47d1fb88d --- /dev/null +++ b/site/core/api/actions/showCallsStatus.md @@ -0,0 +1,99 @@ + + +# showCallsStatus + +Action to request for the wallet to show information about a call batch that was sent via `showCalls`. + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_showcallsstatus) + + + +## Import + +```ts +import { showCallsStatus } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { showCallsStatus } from '@wagmi/core' +import { config } from './config' + +await showCallsStatus(config, { + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type ShowCallsStatusParameters } from '@wagmi/core' +``` + +### connector + +`Connector | undefined` + +Connector to show call statuses with. + +::: code-group +```ts [index.ts] +import { getConnections, showCallsStatus } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +await showCallsStatus(config, { + connector: connections[0]?.connector, // [!code focus] + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { showCallsStatus } from '@wagmi/core' +import { config } from './config' + +await showCallsStatus(config, { + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type ShowCallsStatusReturnType } from '@wagmi/core' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type ShowCallsStatusErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`showCallsStatus`](https://viem.sh/experimental/eip5792/showCallsStatus) diff --git a/site/core/api/actions/signMessage.md b/site/core/api/actions/signMessage.md new file mode 100644 index 0000000000..23569146ac --- /dev/null +++ b/site/core/api/actions/signMessage.md @@ -0,0 +1,125 @@ + + +# signMessage + +Action for signing messages. + +## Import + +```ts +import { signMessage } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { signMessage } from '@wagmi/core' +import { config } from './config' + +await signMessage(config, { message: 'hello world' }) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SignMessageParameters } from '@wagmi/core' +``` + +### account + +`Address | Account | undefined` + +Account to use when signing message. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { signMessage } from '@wagmi/core' +import { config } from './config' + +const result = await signMessage(config, { + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + message: 'hello world', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to sign message with. + +::: code-group +```ts [index.ts] +import { getAccount, signMessage } from '@wagmi/core' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await signMessage(config, { + connector, // [!code focus] + message: 'hello world', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### message + +`string | { raw: Hex | ByteArray }` + +Message to sign. + +::: code-group +```ts [index.ts] +import { signMessage } from '@wagmi/core' +import { config } from './config' + +const result = await signMessage(config, { + message: 'hello world', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: tip +By default, viem signs the UTF-8 representation of the message. To sign the data representation of the message, you can use the `raw` attribute. + +```ts +import { signMessage } from '@wagmi/core' +import { config } from './config' + +const result = await signMessage(config, { + message: { raw: '0x68656c6c6f20776f726c64' }, // [!code focus] +}) +``` +::: + +## Return Type + +```ts +import { type SignMessageReturnType } from '@wagmi/core' +``` + +[`Hex`](https://viem.sh/docs/glossary/types.html#hex) + +The signed message. + +## Error + +```ts +import { type SignMessageErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`signMessage`](https://viem.sh/docs/actions/wallet/signMessage.html) diff --git a/site/core/api/actions/signTypedData.md b/site/core/api/actions/signTypedData.md new file mode 100644 index 0000000000..757c87160f --- /dev/null +++ b/site/core/api/actions/signTypedData.md @@ -0,0 +1,409 @@ + + +# signTypedData + +Action for signing typed data and calculating an Ethereum-specific [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature. + +## Import + +```ts +import { signTypedData } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' + +const result = await signTypedData(config, { + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SignTypedDataParameters } from '@wagmi/core' +``` + +### account + +`Address | Account | undefined` + +Account to use when signing data. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' +import { types } from './typedData' + +const result = await signTypedData(config, { + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + types, + primaryType: 'Mail', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/typedData.ts[typedData.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to sign data with. + +::: code-group +```ts [index.ts] +import { getAccount, signTypedData } from '@wagmi/core' +import { config } from './config' +import { types } from './typedData' + +const { connector } = getAccount(config) +const result = await signTypedData(config, { + connector, // [!code focus] + types, + primaryType: 'Mail', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/typedData.ts[typedData.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### domain + +`TypedDataDomain | undefined` + +- The typed data domain. +- If `EIP712Domain` key exists in [`types`](#types), `domain` schema is inferred from it. + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' +import { types } from './typedData' + +const result = await signTypedData(config, { + domain: { // [!code focus] + name: 'Ether Mail', // [!code focus] + chainId: 1, // [!code focus] + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', // [!code focus] + version: '1', // [!code focus] + }, // [!code focus] + types, + primaryType: 'Mail', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/typedData.ts[typedData.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### message + +`Record` + +- Data to sign. +- Type inferred from [`types`](#types) and [`primaryType`](#primarytype). + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' +import { types } from './typedData' + +const result = await signTypedData(config, { + types, + primaryType: 'Mail', + message: { // [!code focus] + from: { // [!code focus] + name: 'Cow', // [!code focus] + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', // [!code focus] + }, // [!code focus] + to: { // [!code focus] + name: 'Bob', // [!code focus] + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', // [!code focus] + }, // [!code focus] + contents: 'Hello, Bob!', // [!code focus] + }, // [!code focus] +}) +``` +<<< @/snippets/typedData.ts[typedData.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### primaryType + +`string` + +- The primary type to extract from [`types`](#types) and use in [`message`](#message). +- Type inferred from [`types`](#types). + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' +import { types } from './typedData' + +const result = await signTypedData(config, { + types, + primaryType: 'Mail', // [!code focus] + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/typedData.ts[typedData.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### types + +`TypedData` + +- The type definitions for the typed data. +- By defining inline or adding a [const assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) to `types`, TypeScript will infer the correct types for [`message`](#message) and [`primaryType`](#primarytype). See the Wagmi [TypeScript docs](/core/typescript) for more information. + +::: code-group +```ts [index.ts] +import { signTypedData } from '@wagmi/core' +import { config } from './config' + +const result = await signTypedData(config, { + types: { // [!code focus] + Person: [ // [!code focus] + { name: 'name', type: 'string' }, // [!code focus] + { name: 'wallet', type: 'address' }, // [!code focus] + ], // [!code focus] + Mail: [ // [!code focus] + { name: 'from', type: 'Person' }, // [!code focus] + { name: 'to', type: 'Person' }, // [!code focus] + { name: 'contents', type: 'string' }, // [!code focus] + ], // [!code focus] + }, // [!code focus] + primaryType: 'Mail', + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SignTypedDataReturnType } from '@wagmi/core' +``` + +[`Hex`](https://viem.sh/docs/glossary/types.html#hex) + +The signed data. + +## Type Inference + +With [`types`](#types) setup correctly, TypeScript will infer the correct types for [`domain`](#domain), [`message`](#message), and [`primaryType`](#primarytype). See the Wagmi [TypeScript docs](/core/typescript) for more information. + +::: code-group +```ts twoslash [Inline] +import { createConfig, http, signTypedData } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const result = await signTypedData(config, { + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +```ts twoslash [Const-Asserted] +import { createConfig, http, signTypedData } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const + +const result = await signTypedData(config, { + types, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +::: + +## Error + +```ts +import { type SignTypedDataErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`signTypedData`](https://viem.sh/docs/actions/wallet/signTypedData.html) diff --git a/site/core/api/actions/simulateContract.md b/site/core/api/actions/simulateContract.md new file mode 100644 index 0000000000..b7884ef713 --- /dev/null +++ b/site/core/api/actions/simulateContract.md @@ -0,0 +1,598 @@ + + +# simulateContract + +Action for simulating/validating a contract interaction. + +## Import + +```ts +import { simulateContract } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SimulateContractParameters } from '@wagmi/core' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' // [!code focus] +import { config } from './config' + +const result = await simulateContract(config, { + abi, // [!code focus] + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when signing data. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### address + +`Address` + +The contract's address. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ // [!code focus] + '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + 123n, // [!code focus] + ], // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to simulate against. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + blockNumber: 17829139n, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to simulate against. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + blockTag: 'safe', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to validate against before sending transaction. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to simulate transaction with. + +::: code-group +```ts [index.ts] +import { getAccount, simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + connector, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### dataSuffix + +`` `0x${string}` | undefined `` + +Data to append to the end of the calldata. Useful for adding a ["domain" tag](https://opensea.notion.site/opensea/Seaport-Order-Attributions-ec2d69bf455041a5baa490941aad307f). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + dataSuffix: '0xdeadbeef', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### functionName + +`string` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'approve', // [!code focus] + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 123n] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gas: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gasPrice: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + nonce: 123, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + type: 'eip1559', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```ts [index.ts] +import { simulateContract } from '@wagmi/core' +import { parseEther } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + value: parseEther('0.01'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SimulateContractReturnType } from '@wagmi/core' +``` + +The simulation result and write request. + +### request + +Write request that includes [parameters](#parameters). + +### response + +`unknown` + +- Result of contract simulation. +- Inferred from [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and [`value`](#value). See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type SimulateContractErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`simulateContract`](https://viem.sh/docs/contract/simulateContract.html) diff --git a/site/core/api/actions/switchAccount.md b/site/core/api/actions/switchAccount.md new file mode 100644 index 0000000000..8d0f629c13 --- /dev/null +++ b/site/core/api/actions/switchAccount.md @@ -0,0 +1,81 @@ + + +# switchAccount + +Action for switching the current account. + +## Import + +```ts +import { switchAccount } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { getConnections, switchAccount } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const result = await switchAccount(config, { + connector: connections[0]?.connector, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type SwitchAccountParameters } from '@wagmi/core' +``` + +### connector + +`Connector` + +[Connector](/core/api/connectors) to switch to. + +::: code-group +```ts [index.ts] +import { getConnections, switchAccount } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const result = await switchAccount(config, { + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SwitchAccountReturnType } from '@wagmi/core' +``` + +### accounts + +`readonly [Address, ...Address[]]` + +Connected accounts from connector. + +### chainId + +`number` + +Connected chain ID from connector. + +## Error + +```ts +import { type SwitchAccountErrorType } from '@wagmi/core' +``` + + diff --git a/site/core/api/actions/switchChain.md b/site/core/api/actions/switchChain.md new file mode 100644 index 0000000000..c741e801c8 --- /dev/null +++ b/site/core/api/actions/switchChain.md @@ -0,0 +1,122 @@ + + +# switchChain + +Action for switching the target chain for a connector or the Wagmi [`Config`](/core/api/createConfig#config). + +## Import + +```ts +import { switchChain } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { switchChain } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +await switchChain(config, { chainId: mainnet.id }) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: tip +When connected, `switchChain` will switch the target chain for the connector. When not connected, `switchChain` will switch the target chain for the Wagmi [`Config`](/core/api/createConfig#config). +::: + +## Parameters + +```ts +import { type SwitchChainParameters } from '@wagmi/core' +``` + +### addEthereumChainParameter + +`{ chainName: string; nativeCurrency?: { name: string; symbol: string; decimals: number } | undefined; rpcUrls: readonly string[]; blockExplorerUrls?: string[] | undefined; iconUrls?: string[] | undefined } | undefined` + +[EIP-3085 parameters](https://eips.ethereum.org/EIPS/eip-3085) to use when adding chain to connector (when supported). + +::: code-group +```ts [index.ts] +import { switchChain } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const result = await switchChain(config, { + addEthereumChainParameter: { // [!code focus] + iconUrls: ['https://example.com/icon.png'], // [!code focus] + }, // [!code focus] + chainId: mainnet.id, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to switch to. + +::: code-group +```ts [index.ts] +import { switchChain } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const result = await switchChain(config, { + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector` + +[Connector](/core/api/connectors) to switch chain with. + +::: code-group +```ts [index.ts] +import { getConnections, switchAccount } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const connections = getConnections(config) +const result = await switchChain(config, { + chainId: mainnet.id, + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type SwitchChainReturnType } from '@wagmi/core' +``` + +`Chain` + +Chain that was switched to. + +## Error + +```ts +import { type SwitchChainErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`switchChain`](https://viem.sh/docs/actions/wallet/switchChain.html) when connected. diff --git a/site/core/api/actions/verifyMessage.md b/site/core/api/actions/verifyMessage.md new file mode 100644 index 0000000000..93d46992b9 --- /dev/null +++ b/site/core/api/actions/verifyMessage.md @@ -0,0 +1,200 @@ + + +# verifyMessage + +Action for verify that a message was signed by the provided address. It supports verifying messages that were signed by either a Smart Contract Account or Externally Owned Account. + +## Import + +```ts +import { verifyMessage } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type VerifyMessageParameters } from '@wagmi/core' +``` + +### address + +`Address` + +The Ethereum address that signed the original message. + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### message + +`string | { raw: Hex | ByteArray }` + +The message to be verified. + +By default, wagmi verifies the UTF-8 representation of the message. + +::: code-group +```ts [index.ts] +import { getAccount, verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: tip +By default, viem signs the UTF-8 representation of the message. To sign the data representation of the message, you can use the `raw` attribute. + +```ts +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: { raw: '0x68656c6c6f20776f726c64' } // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +::: + +### signature + +`Hex | ByteArray ` + +The signature that was generated by signing the message with the address's signer. + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The ID of chain to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +await verifyMessage(config, { + chainId: mainnet.id, // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + blockNumber: 12345678n, // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The block tag to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyMessage } from '@wagmi/core' +import { config } from './config' + +await verifyMessage(config, { + blockTag: 'latest', // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type VerifyMessageReturnType } from '@wagmi/core' +``` + +`boolean` + +Whether the signed message is valid for the given address. + +## Error + +```ts +import { type VerifyMessageErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`verifyMessage`](https://viem.sh/docs/actions/public/verifyMessage.html) diff --git a/site/core/api/actions/verifyTypedData.md b/site/core/api/actions/verifyTypedData.md new file mode 100644 index 0000000000..e9afa17ad4 --- /dev/null +++ b/site/core/api/actions/verifyTypedData.md @@ -0,0 +1,595 @@ + + +# verifyTypedData + +Action for verify that a typed data was signed by the provided address. It supports verifying typed data that were signed by either a Smart Contract Account or Externally Owned Account. + +## Import + +```ts +import { verifyTypedData } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type VerifyTypedDataParameters } from '@wagmi/core' +``` + +### address + +`Address` + +The Ethereum address that signed the original typed data. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### domain + +`TypedDataDomain` + +The typed data domain. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain: { // [!code focus:6] + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### types + +The type definitions for the typed data. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types: { // [!code focus:11] + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### primaryType + +`string` + +The primary `type` to extract from types and use in `value`. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ // [!code focus:5] + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### message + +Type inferred from `types` & `primaryType`. + +The message to be verified. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types, + message: { // [!code focus:11] + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### signature + +`Hex | ByteArray` + +The signature that was generated by signing the typed data with the address's signer. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', // [!code focus] +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The ID of chain to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + chainId: mainnet.id, // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + blockNumber: 12345678n, // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +::: code-group +```ts [index.ts] +import { verifyTypedData } from '@wagmi/core' +import { domain, types } from './data' +import { config } from './config' + +const valid = await verifyTypedData(config, { + blockTag: 'latest', // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', +}) +// true +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type VerifyTypedDataReturnType } from '@wagmi/core' +``` + +`boolean` + +Whether the signed message is valid for the given address. + +## Type Inference + +With [`types`](#types) setup correctly, TypeScript will infer the correct types for [`domain`](#domain), [`message`](#message), and [`primaryType`](#primarytype). See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type VerifyTypedDataErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`verifyTypedData`](https://viem.sh/docs/actions/public/verifyTypedData.html) diff --git a/site/core/api/actions/waitForCallsStatus.md b/site/core/api/actions/waitForCallsStatus.md new file mode 100644 index 0000000000..8261801046 --- /dev/null +++ b/site/core/api/actions/waitForCallsStatus.md @@ -0,0 +1,143 @@ + + +# waitForCallsStatus + +Waits for a call bundle to be confirmed & included on a block before returning the status & receipts. + + + +## Import + +```ts +import { waitForCallsStatus } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { sendCalls, waitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const id = await sendCalls(config, { + calls: [{ + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }] +}) + +const { status, receipts } = await waitForCallsStatus(config, { // [!code focus] + id, // [!code focus] +}) // [!code focus] +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WaitForCallsStatusParameters } from '@wagmi/core' +``` + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```ts [index.ts] +import { getConnections, waitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const connections = getConnections(config) +const status = await waitForCallsStatus(config, { + connector: connections[0]?.connector, // [!code focus] + id: '0x1234567890abcdef', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { waitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await waitForCallsStatus(config, { + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number` + +Polling interval in milliseconds. + +::: code-group +```ts [index.ts] +import { waitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await waitForCallsStatus(config, { + id: '0x1234567890abcdef', + pollingInterval: 1_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### timeout + +`number` + +Timeout in milliseconds before `waitForCallsStatus` stops polling. + +::: code-group +```ts [index.ts] +import { waitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await waitForCallsStatus(config, { + id: '0x1234567890abcdef', + timeout: 10_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WaitForCallsStatusReturnType } from '@wagmi/core' +``` + +`{ status: 'PENDING' | 'CONFIRMED', receipts: TransactionReceipt[] }` + +The status and receipts of the call batch. + +## Error + +```ts +import { type WaitForCallsStatusErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`waitForCallsStatus`](https://viem.sh/experimental/eip5792/waitForCallsStatus) diff --git a/site/core/api/actions/waitForTransactionReceipt.md b/site/core/api/actions/waitForTransactionReceipt.md new file mode 100644 index 0000000000..853189055d --- /dev/null +++ b/site/core/api/actions/waitForTransactionReceipt.md @@ -0,0 +1,155 @@ + + +# waitForTransactionReceipt + +Action that waits for the transaction to be included on a block, and then returns the transaction receipt. If the transaction reverts, then the action will throw an error. Replacement detection (e.g. sped up transactions) is also supported. + +## Import + +```ts +import { waitForTransactionReceipt } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +const transactionReceipt = waitForTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WaitForTransactionReceiptParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const transactionReceipt = await waitForTransactionReceipt(config, { + chainId: mainnet.id, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### confirmations + +`number | undefined` + +The number of confirmations (blocks that have passed) to wait before resolving. + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +const transactionReceipt = await waitForTransactionReceipt(config, { + confirmations: 2, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onReplaced + +` +(({ reason: 'replaced' | 'repriced' | 'cancelled'; replacedTransaction: Transaction; transaction: Transaction; transactionReceipt: TransactionReceipt }) => void) | undefined +` + +Optional callback to emit if the transaction has been replaced. + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +const transactionReceipt = await waitForTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + onReplaced: replacement => console.log(replacement), // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### hash + +`` `0x${string}` `` + +The transaction hash to wait for. + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +const transactionReceipt = await waitForTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { waitForTransactionReceipt } from '@wagmi/core' +import { config } from './config' + +const transactionReceipt = await waitForTransactionReceipt(config, { + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + pollingInterval: 1_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WaitForTransactionReceiptReturnType } from '@wagmi/core' +``` + +[`TransactionReceipt`](https://viem.sh/docs/glossary/types.html#transactionreceipt) + +The transaction receipt. + +## Error + +```ts +import { type WaitForTransactionReceiptErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`waitForTransactionReceipt`](https://viem.sh/docs/actions/public/waitForTransactionReceipt.html) diff --git a/site/core/api/actions/watchAccount.md b/site/core/api/actions/watchAccount.md new file mode 100644 index 0000000000..a71ca4d161 --- /dev/null +++ b/site/core/api/actions/watchAccount.md @@ -0,0 +1,61 @@ +# watchAccount + +Subscribe to account changes. + +## Import + +```ts +import { watchAccount } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchAccount } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchAccount(config, { + onChange(data) { + console.log('Account changed!', data) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchAccountParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(account: GetAccountReturnType, prevAccount: GetAccountReturnType): void` + +Callback function called when account changes. + +::: code-group +```ts [index.ts] +import { watchAccount } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchAccount(config, { + onChange(account) { // [!code focus:3] + console.log('Account changed!', account) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchAccountReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/watchAsset.md b/site/core/api/actions/watchAsset.md new file mode 100644 index 0000000000..394dadd7ae --- /dev/null +++ b/site/core/api/actions/watchAsset.md @@ -0,0 +1,134 @@ + + +# watchAsset + +Action for requesting user tracks the token in their wallet. Returns a boolean indicating if the token was successfully added. + +## Import + +```ts +import { watchAsset } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchAsset } from '@wagmi/core' +import { config } from './config' + +await watchAsset(config, { + type: 'ERC20', + options: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'WAGMI', + decimals: 18, + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchAssetParameters } from '@wagmi/core' +``` + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to sign message with. + +::: code-group +```ts [index.ts] +import { getAccount, watchAsset } from '@wagmi/core' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await watchAsset(config, { + connector, // [!code focus] + options: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'WAGMI', + decimals: 18, + }, + type: 'ERC20', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### options + +`{ address: string; symbol: string; decimals: number; image?: string | undefined; }` + +Asset options. + +::: code-group +```ts [index.ts] +import { watchAsset } from '@wagmi/core' +import { config } from './config' + +const result = await watchAsset(config, { + options: { // [!code focus] + address: '0x0000000000000000000000000000000000000000', // [!code focus] + symbol: 'WAGMI', // [!code focus] + decimals: 18, // [!code focus] + }, // [!code focus] + type: 'ERC20', +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'ERC20'` + +Type of asset. + +::: code-group +```ts [index.ts] +import { watchAsset } from '@wagmi/core' +import { config } from './config' + +const result = await watchAsset(config, { + options: { + address: '0x0000000000000000000000000000000000000000', + symbol: 'WAGMI', + decimals: 18, + }, + type: 'ERC20', // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchAssetReturnType } from '@wagmi/core' +``` + +`boolean` + +Returns a boolean indicating if the token was successfully added. + +## Error + +```ts +import { type WatchAssetErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`watchAsset`](https://viem.sh/docs/actions/wallet/watchAsset.html) + diff --git a/site/core/api/actions/watchBlockNumber.md b/site/core/api/actions/watchBlockNumber.md new file mode 100644 index 0000000000..098fd6613e --- /dev/null +++ b/site/core/api/actions/watchBlockNumber.md @@ -0,0 +1,226 @@ +# watchBlockNumber + +Action that watches for block number changes. + +## Import + +```ts +import { watchBlockNumber } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchBlockNumberParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + chainId: mainnet.id, // [!code focus] + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitOnBegin + +`boolean | undefined` + +Whether or not to emit the latest block number to the callback when the subscription opens. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + emitOnBegin: true, // [!code focus] + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitMissed + +`boolean | undefined` + +Whether or not to emit the missed block numbers to the callback. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + emitMissed: true, // [!code focus] + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + + +### onBlockNumber + +`(blockNumber: bigint, prevBlockNumber: bigint | undefined) => void` + +Callback for when block number changes. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { // [!code focus] + console.log('Block number changed!', blockNumber) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block number. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + onError(error) { // [!code focus] + console.error('Block number error', error) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + poll: true, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + pollingInterval: 1_000, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { watchBlockNumber } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + syncConnectedChain: false, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchBlockNumberReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. + +## Viem + +- [`watchBlockNumber`](https://viem.sh/docs/actions/public/watchBlockNumber.html) diff --git a/site/core/api/actions/watchBlocks.md b/site/core/api/actions/watchBlocks.md new file mode 100644 index 0000000000..892ef0005f --- /dev/null +++ b/site/core/api/actions/watchBlocks.md @@ -0,0 +1,249 @@ +# watchBlocks + +Action that watches for block changes. + +## Import + +```ts +import { watchBlocks } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchBlocksParameters } from '@wagmi/core' +``` + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized'` + +Watch for new blocks on a given tag. Defaults to `'latest'`. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + blockTag: 'pending', // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const unwatch = watchBlocks(config, { + chainId: mainnet.id, // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitMissed + +`boolean` + +Whether or not to emit missed blocks to the callback. Defaults to `false`. + +Missed blocks may occur in instances where internet connection is lost, or the block time is lesser than the polling interval of the client. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + emitMissed: true, // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitOnBegin + +`boolean` + +Whether or not to emit the block to the callback when the subscription opens. Defaults to `false`. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + emitOnBegin: true, // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onBlock + +`(block: Block, prevblock: Block | undefined) => void` + +Callback for when block changes. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + onBlock(block) { // [!code focus] + console.log('Block changed!', block) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + onBlock(block) { + console.log('Block changed!', block) + }, + onError(error) { // [!code focus] + console.error('Block error', error) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + poll: true, // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + pollingInterval: 1_000, // [!code focus] + onBlock(block) { + console.log('Block changed!', block) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { watchBlocks } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchBlocks(config, { + onBlock(block) { + console.log('Block changed!', block) + }, + syncConnectedChain: false, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchBlocksReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. + +## Viem + +- [`watchBlocks`](https://viem.sh/docs/actions/public/watchBlocks.html) diff --git a/site/core/api/actions/watchChainId.md b/site/core/api/actions/watchChainId.md new file mode 100644 index 0000000000..a003fd16dc --- /dev/null +++ b/site/core/api/actions/watchChainId.md @@ -0,0 +1,61 @@ +# watchChainId + +Subscribe to chain ID changes. + +## Import + +```ts +import { watchChainId } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchChainId } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchChainId(config, { + onChange(chainId) { + console.log('Chain ID changed!', chainId) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchChainIdParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(chainId: GetChainIdReturnType, prevChainId: GetChainIdReturnType): void` + +Callback function called when chain ID changes. + +::: code-group +```ts [index.ts] +import { watchChainId } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchChainId(config, { + onChange(chainId) { // [!code focus:3] + console.log('Chain ID changed!', chainId) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchChainIdReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/watchClient.md b/site/core/api/actions/watchClient.md new file mode 100644 index 0000000000..aa3087c0d0 --- /dev/null +++ b/site/core/api/actions/watchClient.md @@ -0,0 +1,61 @@ +# watchClient + +Subscribe to Client changes. + +## Import + +```ts +import { watchClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchClient } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchClient(config, { + onChange(client) { + console.log('Client changed!', client) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchClientParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(client: GetClientReturnType, prevClient: GetClientReturnType): void` + +Callback function called when Client changes. + +::: code-group +```ts [index.ts] +import { watchClient } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchClient(config, { + onChange(client) { // [!code focus:3] + console.log('Client changed!', client) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchClientReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/watchConnections.md b/site/core/api/actions/watchConnections.md new file mode 100644 index 0000000000..11cb0a617b --- /dev/null +++ b/site/core/api/actions/watchConnections.md @@ -0,0 +1,61 @@ +# watchConnections + +Subscribe to connections changes. + +## Import + +```ts +import { watchConnections } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchConnections } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchConnections(config, { + onChange(data) { + console.log('Connections changed!', data) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchConnectionsParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(connections: GetConnectionsReturnType, prevConnections: GetConnectionsReturnType): void` + +Callback function called when connections changes. + +::: code-group +```ts [index.ts] +import { watchConnections } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchConnections(config, { + onChange(data) { // [!code focus:3] + console.log('Connections changed!', data) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchConnectionsReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/watchConnectors.md b/site/core/api/actions/watchConnectors.md new file mode 100644 index 0000000000..0b951a4584 --- /dev/null +++ b/site/core/api/actions/watchConnectors.md @@ -0,0 +1,61 @@ +# watchConnectors + +Subscribe to connectors changes. + +## Import + +```ts +import { watchConnectors } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchConnectors } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchConnectors(config, { + onChange(connectors) { + console.log('Connectors changed!', connectors) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchConnectorsParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(connectors: GetConnectorsReturnType, prevConnectors: GetConnectorsReturnType): void` + +Callback function called when connectors changes. + +::: code-group +```ts [index.ts] +import { watchConnectors } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchConnectors(config, { + onChange(connectors) { // [!code focus:3] + console.log('Connectors changed!', connectors) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchConnectorsReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/watchContractEvent.md b/site/core/api/actions/watchContractEvent.md new file mode 100644 index 0000000000..91498f1784 --- /dev/null +++ b/site/core/api/actions/watchContractEvent.md @@ -0,0 +1,376 @@ + + +# watchContractEvent + +Action that watches and returns emitted contract event logs. + +## Import + +```ts +import { watchContractEvent } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchContractEventParameters } from '@wagmi/core' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' // [!code focus] +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, // [!code focus] + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### args + +`object | readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`eventName`](#eventname). + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + args: { // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }, // [!code focus] + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### batch + +`boolean | undefined` + +- Whether or not the events should be batched on each invocation. +- Defaults to `true`. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + batch: false, // [!code focus] + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + chainId: mainnet.id, // [!code focus] + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### eventName + +`string` + +- Event to listen for the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + eventName: 'Approval', // [!code focus] + onLogs(logs) { + console.log('Logs changed!', logs) + }, +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block number. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, + onError(error) { // [!code focus] + console.error('Logs error', error) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### onLogs + +`(logs: Log[], prevLogs: Log[] | undefined) => void` + +Callback for when logs changes. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { // [!code focus] + console.log('Logs changed!', logs) // [!code focus] + }, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, + poll: true, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, + pollingInterval: 1_000, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### strict + +`boolean | undefined` + +- Defaults to `false`. + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, + strict: true, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { watchContractEvent } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const unwatch = watchContractEvent(config, { + abi, + onLogs(logs) { + console.log('Logs changed!', logs) + }, + syncConnectedChain: false, // [!code focus] +}) +unwatch() +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchContractEventReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`eventName`](#eventname), [`args`](#args), and [`onLogs`](#onlogs) parameters. See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type WatchContractEventError } from '@wagmi/core' +``` + + + +## Viem + +- [`watchContractEvent`](https://viem.sh/docs/contract/watchContractEvent.html) diff --git a/site/core/api/actions/watchPendingTransactions.md b/site/core/api/actions/watchPendingTransactions.md new file mode 100644 index 0000000000..fe0e5c3885 --- /dev/null +++ b/site/core/api/actions/watchPendingTransactions.md @@ -0,0 +1,207 @@ + + +# watchPendingTransactions + +Action that watches and returns pending transaction hashes. + +## Import + +```ts +import { watchPendingTransactions } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchPendingTransactionsParameters } from '@wagmi/core' +``` + +### batch + +`boolean | undefined` + +- Whether or not the transactions should be batched on each invocation. +- Defaults to `true`. + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' + +const unwatch = watchPendingTransactions(config, { + batch: false, // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { mainnet } from '@wagmi/core/chains' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + chainId: mainnet.id, // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from watching pending transactions. + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onError(error) { // [!code focus] + console.log('Error', error) // [!code focus] + }, // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onTransactions + +`(transactions: Hash[], prevTransactions: Hash[] | undefined) => void` + +Callback when new incoming pending transactions are detected. + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onTransactions(transactions) { // [!code focus] + console.log('New transactions!', transactions) // [!code focus] + }, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new pending transactions instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + poll: false, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + pollingInterval: 1_000, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { watchPendingTransactions } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPendingTransactions(config, { + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + syncConnectedChain: false, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchPendingTransactionsReturnType } from '@wagmi/core' +``` + +Function to unsubscribe from pending transaction listener. + +## Error + +```ts +import { type WatchPendingTransactionsError } from '@wagmi/core' +``` + +## Viem + +- [`watchPendingTransactions`](https://viem.sh/docs/actions/public/watchPendingTransactions.html) diff --git a/site/core/api/actions/watchPublicClient.md b/site/core/api/actions/watchPublicClient.md new file mode 100644 index 0000000000..7363fc3deb --- /dev/null +++ b/site/core/api/actions/watchPublicClient.md @@ -0,0 +1,61 @@ +# watchPublicClient + +Subscribe to Public Client changes. + +## Import + +```ts +import { watchPublicClient } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { watchPublicClient } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPublicClient(config, { + onChange(client) { + console.log('Public Client changed!', client) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WatchPublicClientParameters } from '@wagmi/core' +``` + +### onChange + +`onChange(client: GetPublicClientReturnType, prevClient: GetPublicClientReturnType): void` + +Callback function called when Public Client changes. + +::: code-group +```ts [index.ts] +import { watchPublicClient } from '@wagmi/core' +import { config } from './config' + +const unwatch = watchPublicClient(config, { + onChange(client) { // [!code focus:3] + console.log('Public Client changed!', client) + }, +}) +unwatch() +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WatchPublicClientReturnType } from '@wagmi/core' +``` + +Function for cleaning up watcher. \ No newline at end of file diff --git a/site/core/api/actions/writeContract.md b/site/core/api/actions/writeContract.md new file mode 100644 index 0000000000..34000d9d32 --- /dev/null +++ b/site/core/api/actions/writeContract.md @@ -0,0 +1,560 @@ + + +# writeContract + +Action for executing a write function on a contract. + +A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state. + +## Import + +```ts +import { writeContract } from '@wagmi/core' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +::::tip Pairing with `simulateContract` + +Pairing [`simulateContract`](/core/api/actions/simulateContract) with `writeContract` allows you to validate if the transaction will succeed ahead of time. If the simulate succeeds, `writeContract` can execute the transaction. + +::: code-group +```ts [index.ts] +import { simulateContract, writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const { request } = await simulateContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +const hash = await writeContract(config, request) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: +:::: + + +## Parameters + +```ts +import { type WriteContractParameters } from '@wagmi/core' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' // [!code focus] +import { config } from './config' + +const result = await writeContract(config, { + abi, // [!code focus] + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when signing data. Throws if account is not found on [`connector`](#connector). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### address + +`Address` + +The contract's address. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ // [!code focus] + '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + 123n, // [!code focus] + ] // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to validate against before sending transaction. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + chainId: mainnet.id, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/core/api/connectors) to sign data with. + +::: code-group +```ts [index.ts] +import { getAccount, writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const { connector } = getAccount(config) +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + connector, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### dataSuffix + +`` `0x${string}` | undefined `` + +Data to append to the end of the calldata. Useful for adding a ["domain" tag](https://opensea.notion.site/opensea/Seaport-Order-Attributions-ec2d69bf455041a5baa490941aad307f). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + dataSuffix: '0xdeadbeef', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### functionName + +`string` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'approve', // [!code focus] + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', 123n] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gas: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gasPrice: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + nonce: 123, // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + type: 'eip1559', // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```ts [index.ts] +import { writeContract } from '@wagmi/core' +import { parseEther } from 'viem' +import { abi } from './abi' +import { config } from './config' + +const result = await writeContract(config, { + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + value: parseEther('0.01'), // [!code focus] +}) +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WriteContractReturnType } from '@wagmi/core' +``` + +[`Hash`](https://viem.sh/docs/glossary/types.html#hash) + +The transaction hash. + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and [`value`](#value). See the Wagmi [TypeScript docs](/core/typescript) for more information. + +## Error + +```ts +import { type WriteContractErrorType } from '@wagmi/core' +``` + + + +## Viem + +- [`writeContract`](https://viem.sh/docs/contract/writeContract.html) diff --git a/site/core/api/actions/writeContracts.md b/site/core/api/actions/writeContracts.md new file mode 100644 index 0000000000..a6afe42ff6 --- /dev/null +++ b/site/core/api/actions/writeContracts.md @@ -0,0 +1,317 @@ + + +# writeContracts + +Action that requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + + + +## Import + +```ts +import { writeContracts } from '@wagmi/core/experimental' +``` + +## Usage + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WriteContractsParameters } from '@wagmi/core/experimental' +``` + +### account + +`Account | Address | undefined` + +Account to execute the calls. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### contracts + +`{ to: Hex, data?: Hex, value?: bigint }[]` + +Calls to execute. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ // [!code focus] + { // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + abi, // [!code focus] + functionName: 'approve', // [!code focus] + args: [ // [!code focus] + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + 100n // [!code focus] + ], // [!code focus] + }, // [!code focus] + { // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + abi, // [!code focus] + functionName: 'transferFrom', // [!code focus] + args: [ // [!code focus] + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus] + '0x0000000000000000000000000000000000000000', // [!code focus] + 100n // [!code focus] + ], // [!code focus] + }, // [!code focus] + ], // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### capabilities + +`WalletCapabilities | undefined` + +Capability metadata for the calls (e.g. specifying a paymaster). + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + capabilities: { // [!code focus] + paymasterService: { // [!code focus] + url: 'https://...' // [!code focus] + } // [!code focus] + } // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`number | undefined` + +The target chain ID to broadcast the calls. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + chainId: 10, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get send the calls with. + +::: code-group +```ts [index.ts] +import { parseAbi } from 'viem' +import { getConnections } from '@wagmi/core' +import { writeContracts } from '@wagmi/core/experimental' +import { config } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const connections = getConnections(config) +const id = await writeContracts(config, { + contracts: [ + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], + connector: connections[0]?.connector, // [!code focus] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type WriteContractsReturnType } from '@wagmi/core/experimental' +``` + +`bigint` + +Most recent block number seen. + +## Error + +```ts +import { type WriteContractsErrorType } from '@wagmi/core/experimental' +``` + + + +## Viem + +- [`writeContracts`](https://viem.sh/experimental/eip5792/writeContracts) diff --git a/site/core/api/chains.md b/site/core/api/chains.md new file mode 100644 index 0000000000..6bdd81de6a --- /dev/null +++ b/site/core/api/chains.md @@ -0,0 +1,24 @@ + + +# Chains + +## Import + +Import via the `'@wagmi/core/chains'` entrypoint (proxies all chains from `'viem/chains'`). + +```ts +import { mainnet } from '@wagmi/core/chains' +``` + +## Available Chains + +Chain definitions as of `viem@{{viemVersion}}`. For `viem@latest`, visit the [Viem repo](https://github.com/wevm/viem/blob/main/src/chains/index.ts). + + + + diff --git a/site/core/api/connectors.md b/site/core/api/connectors.md new file mode 100644 index 0000000000..d68718b25b --- /dev/null +++ b/site/core/api/connectors.md @@ -0,0 +1,28 @@ + + +# Connectors + +Connectors for popular wallet providers and protocols. + +## Import + +```ts +import { injected } from 'wagmi/connectors' +``` + +## Built-In Connectors + +Available via the `'wagmi/connectors'` entrypoint. + + diff --git a/site/core/api/connectors/coinbaseWallet.md b/site/core/api/connectors/coinbaseWallet.md new file mode 100644 index 0000000000..d8968eac7d --- /dev/null +++ b/site/core/api/connectors/coinbaseWallet.md @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/site/core/api/connectors/injected.md b/site/core/api/connectors/injected.md new file mode 100644 index 0000000000..6e874baef3 --- /dev/null +++ b/site/core/api/connectors/injected.md @@ -0,0 +1,7 @@ + + + diff --git a/site/core/api/connectors/metaMask.md b/site/core/api/connectors/metaMask.md new file mode 100644 index 0000000000..5553c2b34a --- /dev/null +++ b/site/core/api/connectors/metaMask.md @@ -0,0 +1,7 @@ + + + diff --git a/site/core/api/connectors/mock.md b/site/core/api/connectors/mock.md new file mode 100644 index 0000000000..d085d93cb4 --- /dev/null +++ b/site/core/api/connectors/mock.md @@ -0,0 +1,6 @@ + + + diff --git a/site/core/api/connectors/safe.md b/site/core/api/connectors/safe.md new file mode 100644 index 0000000000..cc800dcc90 --- /dev/null +++ b/site/core/api/connectors/safe.md @@ -0,0 +1,6 @@ + + + diff --git a/site/core/api/connectors/walletConnect.md b/site/core/api/connectors/walletConnect.md new file mode 100644 index 0000000000..ba65cbda96 --- /dev/null +++ b/site/core/api/connectors/walletConnect.md @@ -0,0 +1,6 @@ + + + diff --git a/site/core/api/createConfig.md b/site/core/api/createConfig.md new file mode 100644 index 0000000000..26df850162 --- /dev/null +++ b/site/core/api/createConfig.md @@ -0,0 +1,7 @@ + + + diff --git a/site/core/api/createConnector.md b/site/core/api/createConnector.md new file mode 100644 index 0000000000..7cdbd00bdd --- /dev/null +++ b/site/core/api/createConnector.md @@ -0,0 +1,31 @@ +# createConnector + +Creates new [`CreateConnectorFn`](#parameters). + +## Import + +```ts +import { createConnector } from '@wagmi/core' +``` + +## Usage + +```ts +import { createConnector } from '@wagmi/core' + +export type InjectedParameters = {} + +export function injected(parameters: InjectedParameters = {}) { + return createConnector((config) => ({ + // ... + })) +} +``` + +## Parameters + +```ts +import { type CreateConnectorFn } from '@wagmi/core' +``` + +Read [Creating Connectors](/dev/creating-connectors) for more info on the `CreateConnectorFn` type. \ No newline at end of file diff --git a/site/core/api/createStorage.md b/site/core/api/createStorage.md new file mode 100644 index 0000000000..f547a9a609 --- /dev/null +++ b/site/core/api/createStorage.md @@ -0,0 +1,6 @@ + + + diff --git a/site/core/api/errors.md b/site/core/api/errors.md new file mode 100644 index 0000000000..4a29a1c30f --- /dev/null +++ b/site/core/api/errors.md @@ -0,0 +1,11 @@ + + +# Errors + +Error classes used by Wagmi. + + + diff --git a/site/core/api/transports.md b/site/core/api/transports.md new file mode 100644 index 0000000000..cfaa9a6021 --- /dev/null +++ b/site/core/api/transports.md @@ -0,0 +1,28 @@ + + +# Transports + +[`createConfig`](/core/api/createConfig) can be instantiated with a set of Transports for each chain. A Transport is the intermediary layer that is responsible for executing outgoing JSON-RPC requests to the RPC Provider (e.g. Alchemy, Infura, etc). + +## Import + +```ts +import { http } from '@wagmi/core' +``` + +## Built-In Transports + +Available via the `'@wagmi/core'` entrypoint. + + diff --git a/site/core/api/transports/custom.md b/site/core/api/transports/custom.md new file mode 100644 index 0000000000..487c3ae930 --- /dev/null +++ b/site/core/api/transports/custom.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/transports/fallback.md b/site/core/api/transports/fallback.md new file mode 100644 index 0000000000..cefffa734e --- /dev/null +++ b/site/core/api/transports/fallback.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/transports/http.md b/site/core/api/transports/http.md new file mode 100644 index 0000000000..7fed8c6d72 --- /dev/null +++ b/site/core/api/transports/http.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/transports/unstable_connector.md b/site/core/api/transports/unstable_connector.md new file mode 100644 index 0000000000..19a9b43575 --- /dev/null +++ b/site/core/api/transports/unstable_connector.md @@ -0,0 +1,6 @@ + + + diff --git a/site/core/api/transports/webSocket.md b/site/core/api/transports/webSocket.md new file mode 100644 index 0000000000..4ed1ae3273 --- /dev/null +++ b/site/core/api/transports/webSocket.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/utilities/cookieToInitialState.md b/site/core/api/utilities/cookieToInitialState.md new file mode 100644 index 0000000000..c0470295c9 --- /dev/null +++ b/site/core/api/utilities/cookieToInitialState.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/utilities/deserialize.md b/site/core/api/utilities/deserialize.md new file mode 100644 index 0000000000..4f051cdc53 --- /dev/null +++ b/site/core/api/utilities/deserialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/utilities/normalizeChainId.md b/site/core/api/utilities/normalizeChainId.md new file mode 100644 index 0000000000..9dd43935bb --- /dev/null +++ b/site/core/api/utilities/normalizeChainId.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/api/utilities/serialize.md b/site/core/api/utilities/serialize.md new file mode 100644 index 0000000000..3672a67c17 --- /dev/null +++ b/site/core/api/utilities/serialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/core/getting-started.md b/site/core/getting-started.md new file mode 100644 index 0000000000..f5759957a5 --- /dev/null +++ b/site/core/getting-started.md @@ -0,0 +1,71 @@ + + +# Getting Started + +## Overview + +Wagmi Core is a VanillaJS library for Ethereum. You can learn more about the rationale behind the project in the [Why Wagmi](/core/why) section. + +## Manual Installation + +To manually add Wagmi to your project, install the required packages. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/core @wagmi/connectors viem@{{viemVersion}} +``` + +```bash-vue [npm] +npm install @wagmi/core @wagmi/connectors viem@{{viemVersion}} +``` + +```bash-vue [yarn] +yarn add @wagmi/core @wagmi/connectors viem@{{viemVersion}} +``` + +```bash-vue [bun] +bun add @wagmi/core @wagmi/connectors viem@{{viemVersion}} +``` +::: + +- [Wagmi Connectors](/core/api/connectors) is a collection of interfaces for linking accounts/wallets to Wagmi. +- [Viem](https://viem.sh) is a TypeScript interface for Ethereum that performs blockchain operations. +- [TypeScript](/react/typescript) is optional, but highly recommended. Learn more about [TypeScript support](/core/typescript). + +### Create Config + +Create and export a new Wagmi config using `createConfig`. + +::: code-group +<<< @/snippets/core/config.ts[config.ts] +::: + +In this example, Wagmi is configured to use the Mainnet and Sepolia chains. Check out the [`createConfig` docs](/core/api/createConfig) for more configuration options. + +### Use Wagmi + +Now that everything is set up, you can pass the `config` to use actions. + +::: code-group +```tsx [index.ts] +import { getAccount, getEnsName } from '@wagmi/core' +import { config } from './config' + +const { address } = getAccount(config) +const ensName = await getEnsName(config, { address }) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Next Steps + +For more information on what to do next, check out the following topics. + +- [**TypeScript**](/core/typescript) Learn how to get the most out of Wagmi's type-safety and inference for an enlightened developer experience. +- [**Actions**](/core/api/actions) Browse the collection of actions and learn how to use them. +- [**Framework Adapters**](/core/guides/framework-adapters) Learn how to create a Wagmi-like adapter for your favorite framework. +- [**Viem docs**](https://viem.sh) Wagmi Core is a wrapper around Viem that manages account and client reactivity. Learn more about Viem and how to use it. diff --git a/site/core/guides/chain-properties.md b/site/core/guides/chain-properties.md new file mode 100644 index 0000000000..4f0b480fe0 --- /dev/null +++ b/site/core/guides/chain-properties.md @@ -0,0 +1,91 @@ +# Chain Properties + +Some chains support additional properties related to blocks and transactions. This is powered by Viem's [formatters](https://viem.sh/docs/chains/formatters) and [serializers](https://viem.sh/docs/chains/serializers). For example, Celo, ZkSync, OP Stack chains support all additional properties. In order to use these properties in a type-safe way, there are a few things you should be aware of. + +## Narrowing Parameters + +When you pass your `config` to an action, you are ready to access chain-specific properties! For example, Celo's `feeCurrency` is available. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { simulateContract } from '@wagmi/core' +import { config } from './config' + +const result = await simulateContract(config, { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x…', // [!code focus] +}) +``` +<<< @/snippets/core/config-chain-properties.ts[config.ts] +::: + +This is great, but if you have multiple chains that support additional properties, your autocomplete could be overwhelmed with all of them. By setting the `chainId` property to a specific value (e.g. `celo.id`), you can narrow parameters to a single chain. + +::: code-group +```ts [index.ts] +import { parseEther } from 'viem' +import { simulateContract } from '@wagmi/core' +import { celo } from 'wagmi/chains' + +const result = await simulateContract({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + chainId: celo.id, // [!code focus] + feeCurrency: '0x…', // [!code focus] + // ^? (property) feeCurrency?: `0x${string}` | undefined // [!code focus] +}) +``` +<<< @/snippets/core/config-chain-properties.ts[config.ts] +::: + +## Narrowing Return Types + +Return types can also have chain-specific properties attached to them. There are a couple approaches for extracting these properties. + +### `chainId` Parameter + +Not only can you use the `chainId` parameter to [narrow parameters](#narrowing-parameters), you can also use it to narrow the return type. + +::: code-group +```ts [index.tsx] +import { waitForTransactionReceipt } from '@wagmi/core' +import { zkSync } from '@wagmi/core/chains' + +const result = await waitForTransactionReceipt({ + chainId: zkSync.id, + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +result.logs +// ^? (property) logs: ZkSyncLog[] | undefined +``` +<<< @/snippets/core/config-chain-properties.ts[config.ts] +::: + +### `chainId` Data Property + +Wagmi internally will set a `chainId` property on return types that you can use to narrow results. The `chainId` is determined from the `chainId` parameter or global state (e.g. connector). You can use this property to help TypeScript narrow the type. + +::: code-group +```ts [index.tsx] +import { waitForTransactionReceipt } from '@wagmi/core' +import { zkSync } from '@wagmi/core/chains' + +const result = await waitForTransactionReceipt({ + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +if (result.chainId === zkSync.id) { + result.logs + // ^? (property) logs: ZkSyncLog[] | undefined +} +``` +<<< @/snippets/core/config-chain-properties.ts[config.ts] +::: + +## Troubleshooting + +If chain properties aren't working, make sure [TypeScript](/core/guides/faq#type-inference-doesn-t-work) is configured correctly. Not all chains have additional properties, to check which ones do, see the [Viem repo](https://github.com/wevm/viem/tree/main/src/chains) (chains that have a top-level directory under [`src/chains`](https://github.com/wevm/viem/tree/main/src/chains) support additional properties). + diff --git a/site/core/guides/error-handling.md b/site/core/guides/error-handling.md new file mode 100644 index 0000000000..bac34a1d2c --- /dev/null +++ b/site/core/guides/error-handling.md @@ -0,0 +1,37 @@ +# Error Handling + +Every module in Wagmi Core exports an accompanying error type which you can use to strongly type your `catch` statements. + +These types come in the form of `ErrorType`. For example, the `getBlockNumber` action exports a `GetBlockNumberErrorType` type. + +Unfortunately, [TypeScript doesn't have an abstraction for typed exceptions](https://github.com/microsoft/TypeScript/issues/13219), so the most pragmatic & vanilla approach would be to explicitly cast error types in the `catch` statement. + +::: code-group +```tsx [index.tsx] +import { type GetBlockNumberErrorType, getBlockNumber } from '@wagmi/core' +import { config } from './config' + +try { + const blockNumber = await getBlockNumber(config) +} catch (e) { + const error = e as GetBlockNumberErrorType + error.name + // ^? (property) name: "Error" | "ChainDisconnectedError" | "HttpRequestError" | "InternalRpcError" | "InvalidInputRpcError" | "InvalidParamsRpcError" | "InvalidRequestRpcError" | "JsonRpcVersionUnsupportedError" | ... 16 more ... | "WebSocketRequestError" + + if (error.name === 'InternalRpcError') + error.code + // ^? (property) code: -32603 + + if (error.name === 'HttpRequestError') + error.headers + // ^? (property) headers: Headers + error.status + // ^? (property) status: number +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +::: tip +If you are using [Wagmi Hooks](/react/api/hooks), errors are [already strongly typed](/react/guides/error-handling) via the `error` property. +::: \ No newline at end of file diff --git a/site/core/guides/ethers.md b/site/core/guides/ethers.md new file mode 100644 index 0000000000..c99598a362 --- /dev/null +++ b/site/core/guides/ethers.md @@ -0,0 +1,306 @@ +# Ethers.js Adapters + +It is recommended for projects to migrate to [Viem](https://viem.sh) when using Wagmi, but there are some cases where you might still need to use [Ethers.js](https://ethers.org) in your project: + +- You may want to **incrementally migrate** Ethers.js usage to Viem +- Some **third-party libraries & SDKs** may only support Ethers.js +- Personal preference + +We have provided reference implementations for Viem → Ethers.js adapters that you can copy + paste in your project. + +## Client → Provider + +### Reference Implementation + +Copy the following reference implementation into a file of your choice: + +::: code-group + +```ts [Ethers v5] +import { type Config, getClient } from '@wagmi/core' +import { providers } from 'ethers' +import type { Client, Chain, Transport } from 'viem' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') + return new providers.FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), + ) + return new providers.JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Public Client to an ethers.js Provider. */ +export function getEthersProvider( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = getClient(config, { chainId }) + if (!client) return + return clientToProvider(client) +} +``` + +```ts [Ethers v6] +import { type Config, getClient } from '@wagmi/core' +import { FallbackProvider, JsonRpcProvider } from 'ethers' +import type { Client, Chain, Transport } from 'viem' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Client to an ethers.js Provider. */ +export function getEthersProvider( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = getClient(config, { chainId }) + if (!client) return + return clientToProvider(client) +} +``` + +::: + +### Usage + +Now you can use the `getEthersProvider` function in your components: + +::: code-group + +```ts [example.ts] +import { getEthersProvider } from './ethers' +import { config } from './config' + +function example() { + const provider = getEthersProvider(config) + ... +} +``` + +```ts [ethers.ts (Ethers v5)] +import { type Config, getClient } from '@wagmi/core' +import { providers } from 'ethers' +import type { Client, Chain, Transport } from 'viem' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') + return new providers.FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), + ) + return new providers.JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Public Client to an ethers.js Provider. */ +export function getEthersProvider( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = getClient(config, { chainId }) + if (!client) return + return clientToProvider(client) +} + +``` + +```ts [ethers.ts (Ethers v6)] +import { type Config, getClient } from '@wagmi/core' +import { FallbackProvider, JsonRpcProvider } from 'ethers' +import type { Client, Chain, Transport } from 'viem' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Client to an ethers.js Provider. */ +export function getEthersProvider( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = getClient(config, { chainId }) + if (!client) return + return clientToProvider(client) +} +``` + +::: + +## Connector Client → Signer + +### Reference Implementation + +Copy the following reference implementation into a file of your choice: + +::: code-group + +```ts [Ethers v5] +import { Config, getConnectorClient } from '@wagmi/core' +import { providers } from 'ethers' +import type { Account, Chain, Client, Transport } from 'viem' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new providers.Web3Provider(transport, network) + const signer = provider.getSigner(account.address) + return signer +} + +/** Action to convert a Viem Client to an ethers.js Signer. */ +export async function getEthersSigner( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = await getConnectorClient(config, { chainId }) + return clientToSigner(client) +} +``` + +```ts [Ethers v6] +import { Config, getConnectorClient } from '@wagmi/core' +import { BrowserProvider, JsonRpcSigner } from 'ethers' +import type { Account, Chain, Client, Transport } from 'viem' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new BrowserProvider(transport, network) + const signer = new JsonRpcSigner(provider, account.address) + return signer +} + +/** Action to convert a viem Wallet Client to an ethers.js Signer. */ +export async function getEthersSigner( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = await getConnectorClient(config, { chainId }) + return clientToSigner(client) +} + +``` + +::: + +### Usage + +Now you can use the `getEthersSigner` function in your components: + +::: code-group + +```ts [example.ts] +import { getEthersSigner } from './ethers' +import { config } from './config' + +function example() { + const provider = getEthersSigner(config) + ... +} +``` + +```ts [ethers.ts (Ethers v5)] +import { Config, getConnectorClient } from '@wagmi/core' +import { providers } from 'ethers' +import type { Account, Chain, Client, Transport } from 'viem' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new providers.Web3Provider(transport, network) + const signer = provider.getSigner(account.address) + return signer +} + +/** Action to convert a Viem Client to an ethers.js Signer. */ +export async function getEthersSigner( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = await getConnectorClient(config, { chainId }) + return clientToSigner(client) +} +``` + +```ts [ethers.ts (Ethers v6)] +import { Config, getConnectorClient } from '@wagmi/core' +import { BrowserProvider, JsonRpcSigner } from 'ethers' +import type { Account, Chain, Client, Transport } from 'viem' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new BrowserProvider(transport, network) + const signer = new JsonRpcSigner(provider, account.address) + return signer +} + +/** Action to convert a viem Wallet Client to an ethers.js Signer. */ +export async function getEthersSigner( + config: Config, + { chainId }: { chainId?: number } = {}, +) { + const client = await getConnectorClient(config, { chainId }) + return clientToSigner(client) +} + +``` + +::: diff --git a/site/core/guides/faq.md b/site/core/guides/faq.md new file mode 100644 index 0000000000..7e3ca47e10 --- /dev/null +++ b/site/core/guides/faq.md @@ -0,0 +1,9 @@ + + +# FAQ / Troubleshooting + +Collection of frequently asked questions with ideas on how to troubleshoot and resolve them. + + diff --git a/site/core/guides/framework-adapters.md b/site/core/guides/framework-adapters.md new file mode 100644 index 0000000000..1e45ba63ec --- /dev/null +++ b/site/core/guides/framework-adapters.md @@ -0,0 +1,35 @@ +# Framework Adapters + +Folks often ask if they can use Wagmi with other frameworks, like Svelte, Solid.js, and more. + +The short answer is — you already can! Wagmi Core is pure VanillaJS that you can use with any framework. For some, this answer is (understandably) unsatisfying as they want a tight integration between Wagmi Core and their favorite framework's reactivity system, e.g. what Wagmi is for React and Vue. + +Someday, we would love to support additional frameworks, but unfortunately the core team doesn't have time to build and support them in a high-quality way at the moment. This could change in the future with additional [sponsors](https://github.com/sponsors/wevm), reshuffling of the roadmap, or if someone from the community wants to lead the effort. + +In the meantime, here are some tips on how to create tighter bonds between Wagmi Core and other frameworks. + +## Dependency Injection + +Once you create a Wagmi Config, you'll need to make sure your framework has access to it inside your higher-level functions (e.g. hooks for React, composables for Vue). For example, Wagmi uses [React Context](https://react.dev/learn/passing-data-deeply-with-context) to inject the Config into React Hooks and update it if it changes. This makes it so your users don't need to pass a Config object every time they use a hook. + +## Reactivity Layer + +All frameworks approach reactivity in a different way. To hook into your favorite frameworks, reactivity system, it's often helpful to see what other popular libraries for your framework are doing. + +The most important thing to hook up Wagmi Core with your framework is to make sure changes to the Wagmi Config are tracked. This enables behavior, like switching chains or connecting accounts, to propagate throughout your app and update state. Check out [`useAccount`](https://github.com/wevm/wagmi/blob/main/packages/react/src/hooks/useAccount.ts), [`useChainId`](https://github.com/wevm/wagmi/blob/main/packages/react/src/hooks/useChainId.ts), [`useClient`](https://github.com/wevm/wagmi/blob/main/packages/react/src/hooks/useClient.ts), and [`useConnectorClient`](https://github.com/wevm/wagmi/blob/main/packages/react/src/hooks/useConnectorClient.ts) — versions of these for your framework are important to get right as they power a lot of internals. + +## TanStack Query + +Wagmi uses [TanStack Query](https://tanstack.com/query) to enable caching, deduplication, persistence, and more in React and Vue applications. Normally, you would need to find a similar library for your framework, but the good news is TanStack Query supports other frameworks! (Svelte, Solid, and Angular at the time of writing.) + +To get started with your framework, install and set up the related TanStack Query adapter. Next, import query keys/functions and mutation functions from the `'@wagmi/core/query'` entrypoint. You can plug these directly into your framework's TanStack Query adapter functions. + +If you are building a library, you'll also want to make sure that you wire up generics correctly so type-inference and safety work correctly. The best way to make sure you are doing this correctly, is to see how we do this for React with Wagmi by checking out the [source code](https://github.com/wevm/wagmi/tree/main/packages/react/src/hooks). + +## Testing + +If you are building a library, you'll want to write tests. Wagmi uses [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) to test hooks. [Testing Library](https://testing-library.com) also supports other frameworks, like Svelte, Solid, and more. You can take a look at how the React tests work and do something similar for your code. + +## Proxy Exports + +Wagmi proxies exports directly from Wagmi Core and [Viem](https://viem.sh) to make importing easier. You'll likely want to imitate this behavior for your framework. diff --git a/site/core/guides/migrate-from-v1-to-v2.md b/site/core/guides/migrate-from-v1-to-v2.md new file mode 100644 index 0000000000..6807ebafcd --- /dev/null +++ b/site/core/guides/migrate-from-v1-to-v2.md @@ -0,0 +1,585 @@ +--- +title: Migrate from v1 to v2 +titleTemplate: Wagmi Core +description: Guide for migrating from Wagmi Core v1 to v2. +--- + + + +# Migrate from v1 to v2 + +Wagmi Core v2 redesigns the core APIs to mesh better with [Viem](https://viem.sh). This major version transforms Wagmi into a light wrapper around Viem, sprinkling in multichain support and account management. As such, there are some breaking changes and deprecations to be aware of outlined in this guide. + +To get started, install the latest version of Wagmi and it's required peer dependencies. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/core viem@{{viemVersion}} @wagmi/connectors +``` + +```bash-vue [npm] +npm install @wagmi/core viem@{{viemVersion}} @wagmi/connectors +``` + +```bash-vue [yarn] +yarn add @wagmi/core viem@{{viemVersion}} @wagmi/connectors +``` + +```bash-vue [bun] +bun add @wagmi/core viem@{{viemVersion}} @wagmi/connectors +``` +::: + +::: info Wagmi Core v2 should be the last major version that will have this many actionable breaking changes. +Moving forward, new functionality will be opt-in with old functionality being deprecated alongside the new features. This means upgrading to the latest major versions will not require immediate changes. +::: + +::: info Not ready to migrate yet? +The Wagmi Core v1 docs are still available at [1.x.wagmi.sh/core](https://1.x.wagmi.sh/core). +::: + +## Dependencies + +### Dropped CommonJS support + +Wagmi v2 no longer publishes a separate `cjs` tag since very few people use this tag and ESM is the future. See [Sindre Sorhus' guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for more info about switching to ESM. + +## Actions + +### Removed `config` singleton + +Before v2, when you called [`createConfig`](/core/api/createConfig), it set a global `config` singleton that was used internally by actions. For v2, `config` is now a required first parameter for actions. + +::: code-group +```ts [index.ts] +import { getAccount, readContract } from '@wagmi/core' +import { parseAbi } from 'viem' +import { config } from './config' // [!code ++] + +const account = getAccount() // [!code --] +const account = getAccount(config) // [!code ++] + +const balanceOf = readContract({ // [!code --] +const balanceOf = readContract(config, { // [!code ++] + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + abi: parseAbi(['function balanceOf(address) view returns (uint256)']), + functionName: 'balanceOf', + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +The previous global `config` singleton made it so you couldn't use multiple `Config` objects in the same project. In addition, we think passing `config` is more explicit and makes it easier to understand what's going on. Finally, types can be inferred directly from the `config`, like [chain properties](/core/guides/chain-properties) and more. + +### Removed `getContract` + +Removed `getContract` export. Use Viem's [`getContract`](https://viem.sh/docs/contract/getContract.html) instead. + +```ts +import { getContract } from '@wagmi/core' // [!code --] +import { getContract } from 'viem' // [!code ++] + +const contract = getContract() // [!code --] +const contract = getContract() // [!code ++] +``` + +### Removed `getNetwork` and `watchNetwork` + +The `getNetwork` and `watchNetwork` actions were removed since the connected chain is typically based on the connected account. + +- Use [`config.chains`](/core/api/createConfig#chains-1) instead to get `chains`. + + ::: code-group + ```ts [index.ts] + import { getNetwork } from '@wagmi/core' // [!code --] + + const { chains } = getNetwork() // [!code --] + const chains = config.chains // [!code ++] + ``` + <<< @/snippets/core/config.ts[config.ts] + ::: + +- Use [`getAccount`](/core/api/actions/getAccount) and `config.chains` instead to get `chain`. + + ::: code-group + ```ts [index.ts] + import { getNetwork } from '@wagmi/core' // [!code --] + import { getAccount } from '@wagmi/core' // [!code ++] + import { config } from './config' // [!code ++] + + const { chain } = getNetwork() // [!code --] + const { chainId } = getAccount(config) // [!code ++] + const chain = chains.find(chain => chain.id === chainId) // [!code ++] + ``` + <<< @/snippets/core/config.ts[config.ts] + ::: + + Before v2, `getNetwork().chain` could result in an invalid chain if the active connector's `chainId` was not configured in the list of `config.chains`. Using `getAccount` and `config.chains` is more work, but ensures that chain is either valid or not defined. You can also use `getAccount(config).chain` if you don't care about the chain being `undefined` when not configured. + +- Use `watchAccount` instead of `watchNetwork`. + + ::: code-group + ```ts [index.ts] + import { watchNetwork } from '@wagmi/core' // [!code --] + import { watchAccount } from '@wagmi/core' // [!code ++] + import { config } from './config' // [!code ++] + + const unwatch = watchNetwork((data) => console.log('Changed!', data)) // [!code --] + const unwatch = watchAccount(config, { // [!code ++] + onChange(data) { // [!code ++] + const chains = config.chains // [!code ++] + const chain = chains.find(chain => chain.id === data.chainId) // [!code ++] + }, // [!code ++] + }) // [!code ++] + ``` + <<< @/snippets/core/config.ts[config.ts] + ::: + +### Removed `getWebSocketPublicClient` and `watchWebSocketPublicClient` + +Viem [Transports](https://viem.sh/docs/clients/intro.html#transports) now determine the type of client that is returned. You can use [`getPublicClient`](/core/api/actions/getPublicClient) and [`watchPublicClient`](/core/api/actions/watchPublicClient) to retrieve Viem [`PublicClient`](https://viem.sh/docs/clients/public.html) instances. + +Alternatively, you can use [`getClient`](/core/api/actions/getClient) and [`watchClient`](/core/api/actions/watchClient) to retrieve plain Viem [`Client`](https://viem.sh/docs/clients/custom.html) instances. This is a better option for users that care about optimizing bundle size to be as small as possible. + +### Removed `watchReadContract`, `watchReadContracts`, and `watchReadMulticall` + +Use [`watchBlockNumber`](/core/api/actions/watchBlockNumber) along with [`readContract`](/core/api/actions/readContract), [`readContracts`](/core/api/actions/readContracts), and [`multicall`](/core/api/actions/multicall) actions instead. Before v2, `watchReadContract`, `watchReadContracts`, and `watchReadMulticall` were all wrappers around `watchBlockNumber` and this simplifies the API. + +::: code-group +```ts [index.ts] +import { watchReadContract } from '@wagmi/core' // [!code --] +import { watchBlockNumber, readContract } from '@wagmi/core' // [!code ++] +import { config } from './config' // [!code ++] + +const unwatch = watchReadContract( // [!code --] + { // [!code --] + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // [!code --] + abi: parseAbi(['function balanceOf(address) view returns (uint256)']), // [!code --] + functionName: 'balanceOf', // [!code --] + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], // [!code --] + }, // [!code --] + (result) => console.log('Changed!', result), // [!code --] +) // [!code --] +const unwatch = watchBlockNumber(config, { // [!code ++] + onBlockNumber() { // [!code ++] + const balanceOf = readContract(config, { // [!code ++] + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // [!code ++] + abi: parseAbi(['function balanceOf(address) view returns (uint256)']), // [!code ++] + functionName: 'balanceOf', // [!code ++] + args: ['0xd2135CfB216b74109775236E36d4b433F1DF507B'], // [!code ++] + }) // [!code ++] + console.log('Changed!', balanceOf)// [!code ++] + }, // [!code ++] +}) // [!code ++] +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### Removed `fetchFeeData` + +Removed `fetchFeeData`. Use [`estimateFeesPerGas`](/core/api/actions/estimateFeesPerGas) instead. + +::: code-group +```ts [index.ts] +import { fetchFeeData } from '@wagmi/core' // [!code --] +import { estimateFeesPerGas } from '@wagmi/core' // [!code ++] +import { config } from './config' // [!code ++] + +const result = await fetchFeeData() // [!code --] +const result = await estimateFeesPerGas(config) // [!code ++] +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### Removed `prepareWriteContract` + +Removed `prepareWriteContract`. Use [`simulateContract`](/core/api/actions/simulateContract) instead. + +::: code-group +```ts [index.ts] +import { prepareWriteContract } from '@wagmi/core' // [!code --] +import { simulateContract } from '@wagmi/core' // [!code ++] +import { config } from './config' // [!code ++] + +const result = await prepareWriteContract({ ... }) // [!code --] +const result = await simulateContract(config, { ... }) // [!code ++] +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### Removed `prepareSendTransaction` + +Removed `prepareSendTransaction`. Use [`estimateGas`](/core/api/actions/estimateGas) instead. + +::: code-group +```ts [index.ts] +import { prepareSendTransaction } from '@wagmi/core' // [!code --] +import { estimateGas } from '@wagmi/core' // [!code ++] +import { config } from './config' // [!code ++] + +const result = await prepareSendTransaction({ ... }) // [!code --] +const result = await estimateGas(config, { ... }) // [!code ++] +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### Updated `sendTransaction` and `writeContract` return type + +Updated [`sendTransaction`](/core/api/actions/sendTransaction) and [`writeContract`](/core/api/actions/writeContract) return type from `` { hash: `0x${string}` } `` to `` `0x${string}` ``. + +```ts +const result = await sendTransaction({ hash: '0x...' }) +result.hash // [!code --] +result // [!code ++] +``` + +### Updated `connect` return type + +Updated [`connect`](/core/api/actions/connect) return type from `` { account: Address; chain: { id: number; unsupported?: boolean }; connector: Connector } `` to `` { accounts: readonly Address[]; chainId: number } ``. This better reflects the ability to have multiple accounts per connector. + +### Renamed parameters and return types + +All hook parameters and return types follow the naming pattern of `[PascalCaseActionName]Parameters` and `[PascalCaseActionName]ReturnType`. For example, `GetAccountParameters` and `GetAccountReturnType`. + +```ts +import { GetAccountConfig, GetAccountResult } from '@wagmi/core' // [!code --] +import { GetAccountParameters, GetAccountReturnType } from '@wagmi/core' // [!code ++] +``` + +## Connectors + +### Moved Wagmi Connectors to peer dependencies + +Wagmi Core v2 no longer exports connectors via the `'@wagmi/core/connectors/*'` entrypoints. Instead, you should install the `@wagmi/connectors` package. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/connectors +``` + +```bash-vue [npm] +npm install @wagmi/connectors +``` + +```bash-vue [yarn] +yarn add @wagmi/connectors +``` + +```bash-vue [bun] +bun add @wagmi/connectors +``` +::: + +And import connectors from there. + +```ts +import { injected } from '@wagmi/connectors' +``` + +See the [connectors documentation](/core/api/connectors) for more information. + +### Updated connector API + +In order to maximize type-safety and ease of creating connectors, the connector API changed. Follow the [Creating Connectors guide](/dev/creating-connectors) for more info on creating new connectors and converting Wagmi v1 connectors. + +### Removed individual entrypoints + +Previously, each connector had its own entrypoint to optimize tree-shaking. Since all connectors now have [`package.json#sideEffects`](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) enabled, this is no longer necessary and the entrypoint is unified. Use the `'@wagmi/connectors'` package instead. + +```ts +import { InjectedConnector } from '@wagmi/core/connectors/injected' // [!code --] +import { CoinbaseWalletConnector } from '@wagmi/core/connectors/coinbaseWallet' // [!code --] +import { coinbaseWallet, injected } from '@wagmi/connectors' // [!code ++] +``` + +### Removed `MetaMaskConnector` + +The `MetaMaskConnector` was removed since it was nearly the same thing as the `InjectedConnector`. Use the [`injected`](/core/api/connectors/injected) connector instead, along with the [`target`](/core/api/connectors/injected#target) parameter set to `'metaMask'`, for the same behavior. + +```ts +import { MetaMaskConnector } from '@wagmi/core/connectors/metaMask' // [!code --] +import { injected } from '@wagmi/connectors' // [!code ++] + +const connector = new MetaMaskConnector() // [!code --] +const connector = injected({ target: 'metaMask' }) // [!code ++] +``` + +### Renamed connectors + +In Wagmi v1, connectors were classes you needed to instantiate. In Wagmi v2, connectors are functions. As a result, the API has changed. Connectors have the following new names: + +- `CoinbaseWalletConnector` is now [`coinbaseWallet`](/core/api/connectors/coinbaseWallet). +- `InjectedConnector` is now [`injected`](/core/api/connectors/injected). +- `SafeConnector` is now [`safe`](/core/api/connectors/safe). +- `WalletConnectConnector` is now [`walletConnect`](/core/api/connectors/walletConnect). + +To create a connector, you now call the connector function with parameters. + +```ts +import { WalletConnectConnector } from '@wagmi/core/connectors/walletConnect' // [!code --] +import { walletConnect } from '@wagmi/connectors' // [!code ++] + +const connector = new WalletConnectConnector({ // [!code --] +const connector = walletConnect({ // [!code ++] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +### Removed `WalletConnectLegacyConnector` + +WalletConnect v1 was sunset June 28, 2023. Use the [`walletConnect`](/core/api/connectors/walletConnect) connector instead. + +```ts +import { WalletConnectLegacyConnector } from '@wagmi/core/connectors/walletConnectLegacy' // [!code --] +import { walletConnect } from '@wagmi/connectors' // [!code ++] + +const connector = new WalletConnectLegacyConnector({ // [!code --] +const connector = walletConnect({ // [!code ++] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +## Chains + +### Updated `'@wagmi/core/chains'` entrypoint + +Chains now live in the [Viem repository](https://github.com/wevm/viem). As a result, the `'@wagmi/core/chains'` entrypoint now proxies all chains from `'viem/chains'` directly. + +### Removed `mainnet` and `sepolia` from main entrypoint + +Since the `'@wagmi/core/chains'` entrypoint now proxies `'viem/chains'`, `mainnet` and `sepolia` were removed from the main entrypoint. Use the `'@wagmi/core/chains'` entrypoint instead. + +```ts +import { mainnet, sepolia } from '@wagmi/core' // [!code --] +import { mainnet, sepolia } from '@wagmi/core/chains' // [!code ++] +``` + +## Errors + +A number of errors were renamed to better reflect their functionality or replaced by Viem errors. + +## Miscellaneous + +### Removed internal ENS normalization + +Before v2, Wagmi handled ENS name normalization internally for `getEnsAddress`, `getEnsAvatar`, and `getEnsResolver`, using Viem's [`normalize`](https://viem.sh/docs/ens/utilities/normalize.html) function. This added extra bundle size as full normalization is quite heavy. For v2, you must normalize ENS names yourself before passing them to these actions. You can use Viem's `normalize` function or any other function that performs [UTS-46 normalization](https://unicode.org/reports/tr46). + + +::: code-group +```ts [index.ts] +import { getEnsAddress } from '@wagmi/core' +import { normalize } from 'viem' // [!code ++] +import { config } from './config' + +const result = await getEnsAddress(config, { + name: 'wevm.eth', // [!code --] + name: normalize('wevm.eth'), // [!code ++] +}) +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +By inverting control, Wagmi lets you choose how much normalization to do. For example, maybe your project only allows ENS names that are numeric so no normalization is not needed. Check out the [ENS documentation](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) for more information on normalizing names. + +### Removed `configureChains` + +The Wagmi v2 `Config` now has native multichain support using the [`chains`](/core/api/createConfig) parameter so the `configureChains` function is no longer required. + +```ts +import { configureChains, createConfig } from '@wagmi/core' // [!code --] +import { http, createConfig } from '@wagmi/core' // [!code ++] +import { mainnet, sepolia } from '@wagmi/core/chains' + +const { chains, publicClient } = configureChains( // [!code --] + [mainnet, sepolia], // [!code --] + [publicProvider(), publicProvider()], // [!code --] +) // [!code --] + +export const config = createConfig({ + publicClient, // [!code --] + chains: [mainnet, sepolia], // [!code ++] + transports: { // [!code ++] + [mainnet.id]: http(), // [!code ++] + [sepolia.id]: http(), // [!code ++] + }, // [!code ++] +}) +``` + +### Removed ABI exports + +Import from Viem instead. + +```ts +import { erc20ABI } from '@wagmi/core' // [!code --] +import { erc20Abi } from 'viem' // [!code ++] +``` + +### Removed `'@wagmi/core/providers/*` entrypoints + +It never made sense that we would have provider URLs hardcoded in the Wagmi codebase. Use [Viem transports](https://viem.sh/docs/clients/intro.html#transports) along with RPC provider URLs instead. + +```ts +import { alchemyProvider } from '@wagmi/core/providers/alchemy' // [!code --] +import { http } from 'viem' // [!code ++] + +const transport = http('https://mainnet.example.com') +``` + +### Updated `createConfig` parameters + +- Removed `autoConnect`. The reconnecting behavior must be managed manually and is not related to the Wagmi `Config`. Use the [`reconnect`](/core/api/actions/reconnect) action instead. +- Removed `publicClient` and `webSocketPublicClient`. Use [`transports`](/core/api/createConfig#transports) or [`client`](/core/api/createConfig#client) instead. +- Removed `logger`. Wagmi no longer logs debug information to console. + +### Updated `Config` object + +- Removed `config.connector`. Use `config.state.connections.get(config.state.current)?.connector` instead. +- Removed `config.data`. Use `config.state.connections.get(config.state.current)` instead. +- Removed `config.error`. Was unused and not needed. +- Removed `config.lastUsedChainId`. Use `config.state.connections.get(config.state.current)?.chainId` instead. +- Removed `config.publicClient`. Use [`config.getClient()`](/core/api/createConfig#getclient) or [`getPublicClient`](/core/api/actions/getPublicClient) instead. +- Removed `config.status`. Use [`config.state.status`](/core/api/createConfig#status) instead. +- Removed `config.webSocketClient`. Use [`config.getClient()`](/core/api/createConfig#getclient) or [`getPublicClient`](/core/api/actions/getPublicClient) instead. +- Removed `config.clearState`. Was unused and not needed. +- Removed `config.autoConnect()`. Use [`reconnect`](/core/api/actions/reconnect) action instead. +- Renamed `config.setConnectors`. Use `config._internal.setConnectors` instead. +- Removed `config.setLastUsedConnector`. Use `config.storage?.setItem('recentConnectorId', connectorId)` instead. +- Removed `getConfig`. `config` should be passed explicitly to actions instead of using global `config`. + +## Deprecations + +### Deprecated `getBalance` `token` parameter + +Moving forward, `getBalance` will only work for native currencies, thus the `token` parameter is no longer supported. Use [`readContracts`](/core/api/actions/readContracts) instead. + +```ts +import { getBalance } from '@wagmi/core' // [!code --] +import { readContracts } from '@wagmi/core' // [!code ++] +import { erc20Abi } from 'viem' // [!code ++] +import { config } from './config' // [!code ++] + +const result = await getBalance(config, { // [!code --] + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code --] + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code --] +}) // [!code --] +const result = await readContracts(config, { // [!code ++] + allowFailure: false, // [!code ++] + contracts: [ // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'balanceOf', // [!code ++] + args: ['0x4557B18E779944BFE9d78A672452331C186a9f48'], // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'decimals', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'symbol', // [!code ++] + }, // [!code ++] + ] // [!code ++] +}) // [!code ++] +``` + +### Deprecated `getBalance` `unit` parameter and `formatted` return value + +Moving forward, `getBalance` will not accept the `unit` parameter or return a `formatted` value. Instead you can call `formatUnits` from Viem directly or use another number formatting library, like [dnum](https://github.com/bpierre/dnum) instead. + +```ts +import { formatUnits } from 'viem' // [!code ++] +import { getBalance } from '@wagmi/core' + +const result = await getBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + unit: 'ether', // [!code --] +}) +result.formatted // [!code --] +formatUnits(result.value, result.decimals) // [!code ++] +``` + +### Deprecated `getToken` + +Moving forward, `getToken` is no longer supported. Use [`readContracts`](/core/api/actions/readContracts) instead. + +```ts +import { getToken } from '@wagmi/core' // [!code --] +import { readContracts } from '@wagmi/core' // [!code ++] +import { erc20Abi } from 'viem' // [!code ++] +import { config } from './config' // [!code ++] + +const result = await getToken(config, { // [!code --] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code --] +}) // [!code --] +const result = await readContracts(config, { // [!code ++] + allowFailure: false, // [!code ++] + contracts: [ // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'decimals', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'name', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'symbol', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'totalSupply', // [!code ++] + }, // [!code ++] + ] // [!code ++] +}) // [!code ++] +``` + +### Deprecated `formatUnits` parameters and return values + +The `formatUnits` parameter and related return values (e.g. `result.formatted`) are deprecated for the following actions: + +- [`estimateFeesPerGas`](/core/api/actions/estimateFeesPerGas) +- [`getToken`](/core/api/actions/getToken) + +Instead you can call `formatUnits` from Viem directly or use another number formatting library, like [dnum](https://github.com/bpierre/dnum) instead. + +```ts +import { formatUnits } from 'viem' // [!code ++] +import { getToken } from '@wagmi/core' + +const result = await getToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + formatUnits: 'ether', +}) +result.totalSupply.formatted // [!code --] +formatUnits(result.totalSupply.value, 18) // [!code ++] +``` + +This allows us to invert control to users so they can handle number formatting however they want, taking into account precision, localization, and more. + +### Renamed actions + +The following actions were renamed to better reflect their functionality and underlying [Viem](https://viem.sh) actions: + +- `fetchBalance` is now [`getBalance`](/core/api/actions/getBalance) +- `fetchBlockNumber` is now [`getBlockNumber`](/core/api/actions/getBlockNumber) +- `fetchEnsAddress` is now [`getEnsAddress`](/core/api/actions/getEnsAddress) +- `fetchEnsAvatar` is now [`getEnsAvatar`](/core/api/actions/getEnsAvatar) +- `fetchEnsName` is now [`getEnsName`](/core/api/actions/getEnsName) +- `fetchEnsResolver` is now [`getEnsResolver`](/core/api/actions/getEnsResolver) +- `fetchToken` is now [`getToken`](/core/api/actions/getToken) +- `fetchTransaction` is now [`getTransaction`](/core/api/actions/getTransaction) +- `switchNetwork` is now [`switchChain`](/core/api/actions/switchChain) +- `waitForTransaction` is now [`waitForTransactionReceipt`](/core/api/actions/waitForTransactionReceipt) diff --git a/site/core/guides/testing.md b/site/core/guides/testing.md new file mode 100644 index 0000000000..383062dbea --- /dev/null +++ b/site/core/guides/testing.md @@ -0,0 +1,3 @@ +# Testing + + diff --git a/site/core/guides/viem.md b/site/core/guides/viem.md new file mode 100644 index 0000000000..275ed86949 --- /dev/null +++ b/site/core/guides/viem.md @@ -0,0 +1,197 @@ +# Viem + +[Viem](https://viem.sh) is a low-level TypeScript Interface for Ethereum that enables developers to interact with the Ethereum blockchain, including: JSON-RPC API abstractions, Smart Contract interaction, wallet & signing implementations, coding/parsing utilities and more. + +**Wagmi Core** is essentially a wrapper over **Viem** that provides multi-chain functionality via [Wagmi Config](/core/api/createConfig) and automatic account management via [Connectors](/core/api/connectors). + +## Leveraging Viem Actions + +All of the core [Wagmi Actions](/core/api/actions) are friendly wrappers around [Viem Actions](https://viem.sh/docs/actions/public/introduction.html) that inject a multi-chain and connector aware [Wagmi Config](/core/api/createConfig). + +There may be cases where you might want to dig deeper and utilize Viem Actions directly (maybe an Action doesn't exist in Wagmi yet). In these cases, you can import Viem Actions directly via `viem/actions` and plug in a Viem Client returned by the [`getClient` Action](/core/api/actions/getClient). + +The example below demonstrates two different ways to utilize Viem Actions: + +1. **Tree-shakable Actions (recommended):** Uses `getClient` (for public actions) and `getConnectorClient` (for wallet actions). +2. **Client Actions:** Uses `getPublicClient` (for public actions) and `getWalletClient` (for wallet actions). + +::: tip + +It is highly recommended to use the **tree-shakable** method to ensure that you are only pulling modules you use, and keep your bundle size low. + +::: + +::: code-group + +```tsx [Tree-shakable Actions] +// 1. Import modules. +import { http, createConfig, getClient, getConnectorClient } from '@wagmi/core' +import { base, mainnet, optimism, zora } from '@wagmi/core/chains' +import { getLogs, watchAsset } from 'viem/actions' // [!code hl] + +// 2. Set up a Wagmi Config +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +// 3. Extract a Viem Client for the current active chain. +const publicClient = getClient(config) +const logs = await getLogs(publicClient, /* ... */) // [!code hl] + +// 4. Extract a Viem Client for the current active chain & account. +const walletClient = getConnectorClient(config) +const success = await watchAsset(walletClient, /* ... */) // [!code hl] +``` + +```tsx [Client Actions] +// 1. Import modules. +import { http, createConfig, getPublicClient, getWalletClient } from '@wagmi/core' +import { base, mainnet, optimism, zora } from '@wagmi/core/chains' + +// 2. Set up a Wagmi Config +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +// 3. Extract a Viem Public Client for the current active chain. +const publicClient = getPublicClient(config) +const logs = await publicClient.getLogs(publicClient, /* ... */) // [!code hl] + +// 4. Extract a Viem Wallet Client for the current active chain & account. +const walletClient = getWalletClient(config) +const success = await walletClient.watchAsset(walletClient, /* ... */) // [!code hl] +``` + +::: + +## Multi-chain Viem Client + +The [Viem Client](https://viem.sh/docs/client) provides an interface to interact with an JSON-RPC Provider. By nature, JSON-RPC Providers are single-chain, so the Viem Client is designed to be instantiated with a single `chain`. As a result, setting up Viem to be multi-chain aware can get a bit verbose. + +The good news is that you can create a **"multi-chain Viem Client"** with **Wagmi** by utilizing [`createConfig`](/core/api/createConfig) and [`getClient`](/core/api/actions/getClient). + +::: code-group + +```tsx [Wagmi Usage] +// 1. Import modules. +import { http, createConfig, getClient, getConnectorClient } from '@wagmi/core' +import { base, mainnet, optimism, zora } from '@wagmi/core/chains' +import { getBlockNumber, sendTransaction } from 'viem/actions' // [!code hl] + +// 2. Set up a Wagmi Config +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +// 3. Extract a Viem Client for the current active chain. +const publicClient = getClient(config) +const blockNumber = await getBlockNumber(publicClient) // [!code hl] + +// 4. Extract a Viem Client for the current active chain & account. +const walletClient = getConnectorClient(config) +const hash = await sendTransaction(walletClient, /* ... */) // [!code hl] +``` + +```tsx [Viem Usage] +// Manually set up Viem Clients without wagmi. Don't do this, it's only here +// to demonstrate the amount of boilerplate required. + +import { createPublicClient, createWalletClient, http } from 'viem' +import { base, mainnet, optimism, zora } from 'viem/chains' + +const publicClient = { + base: createPublicClient({ + chain: base, + transport: http() + }), + mainnet: createPublicClient({ + chain: mainnet, + transport: http() + }), + optimism: createPublicClient({ + chain: optimism, + transport: http() + }), + zora: createPublicClient({ + chain: zora, + transport: http() + }) +} as const + +const walletClient = { + base: createWalletClient({ + chain: base, + transport: custom(window.ethereum) + }), + mainnet: createWalletClient({ + chain: mainnet, + transport: custom(window.ethereum) + }), + optimism: createWalletClient({ + chain: optimism, + transport: custom(window.ethereum) + }), + zora: createWalletClient({ + chain: zora, + transport: custom(window.ethereum) + }) +} as const + +const blockNumber = await publicClient.mainnet.getBlockNumber() +const hash = await walletClient.mainnet.sendTransaction(/* ... */) +``` + +::: + +## Private Key & Mnemonic Accounts + +It is possible to utilize Viem's [Private Key & Mnemonic Accounts](https://viem.sh/docs/accounts/local.html) with Wagmi by explicitly passing through the account via the `account` argument on Wagmi Actions. + +```tsx +import { http, createConfig, sendTransaction } from '@wagmi/core' +import { base, mainnet, optimism, zora } from '@wagmi/core/chains' +import { parseEther } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +const account = privateKeyToAccount('0x...') // [!code hl] + +const hash = await sendTransaction({ + account, // [!code hl] + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + value: parseEther('0.001') +}) +``` + +::: info + +Wagmi currently does not support hoisting Private Key & Mnemonic Accounts to the top-level Wagmi Config – meaning you have to explicitly pass through the account to every Action. If you feel like this is a feature that should be added, please [open a discussion](https://github.com/wevm/wagmi/discussions/new?category=ideas). + +::: diff --git a/site/core/installation.md b/site/core/installation.md new file mode 100644 index 0000000000..858ebc6879 --- /dev/null +++ b/site/core/installation.md @@ -0,0 +1,52 @@ + + +# Installation + +Install Wagmi Core via your package manager, a ` +``` + + diff --git a/site/core/typescript.md b/site/core/typescript.md new file mode 100644 index 0000000000..e41d942828 --- /dev/null +++ b/site/core/typescript.md @@ -0,0 +1,241 @@ + + +# TypeScript + +## Requirements + +Wagmi Core is designed to be as type-safe as possible! Things to keep in mind: + +- Types currently require using TypeScript {{typescriptVersion}}. +- [TypeScript doesn't follow semver](https://www.learningtypescript.com/articles/why-typescript-doesnt-follow-strict-semantic-versioning) and often introduces breaking changes in minor releases. +- Changes to types in this repository are considered non-breaking and are usually released as patch changes (otherwise every type enhancement would be a major version!). +- It is highly recommended that you lock your `@wagmi/core` and `typescript` versions to specific patch releases and upgrade with the expectation that types may be fixed or upgraded between any release. +- The non-type-related public API of Wagmi Core still follows semver very strictly. + +To ensure everything works correctly, make sure your `tsconfig.json` has [`strict`](https://www.typescriptlang.org/tsconfig#strict) mode set to `true`. + +::: code-group +```json [tsconfig.json] +{ + "compilerOptions": { + "strict": true + } +} +``` +::: + +## Const-Assert ABIs & Typed Data + +Wagmi Core can infer types based on [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html#json) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data definitions, powered by [Viem](https://viem.sh) and [ABIType](https://github.com/wevm/abitype). This achieves full end-to-end type-safety from your contracts to your frontend and enlightened developer experience by autocompleting ABI item names, catching misspellings, inferring argument and return types (including overloads), and more. + +For this to work, you must either [const-assert](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) ABIs and Typed Data (more info below) or define them inline. For example, `useReadContract`'s `abi` configuration parameter: + +```ts +const result = await readContract({ + abi: […], // <--- defined inline // [!code focus] +}) +``` + +```ts +const abi = […] as const // <--- const assertion // [!code focus] +const result = readContract({ abi }) +``` + +If type inference isn't working, it's likely you forgot to add a `const` assertion or define the configuration parameter inline. Also, make sure your ABIs, Typed Data definitions, and [TypeScript configuration](#requirements) are valid and set up correctly. + +::: tip +Unfortunately [TypeScript doesn't support importing JSON `as const` yet](https://github.com/microsoft/TypeScript/issues/32063). Check out the [Wagmi CLI](/cli/getting-started) to help with this! It can automatically fetch ABIs from Etherscan and other block explorers, resolve ABIs from your Foundry/Hardhat projects, and more. +::: + +Anywhere you see the `abi` or `types` configuration property, you can likely use const-asserted or inline ABIs and Typed Data to get type-safety and inference. These properties are also called out in the docs. + +Here's what [`readContract`](/core/api/actions/readContract) looks like with and without a const-asserted `abi` property. + +::: code-group +```ts twoslash [Const-Asserted] +import { createConfig, http } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) + +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { readContract } from '@wagmi/core' + +const result = await readContract(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +result +// ^? +``` +```ts twoslash [Not Const-Asserted] +import { createConfig, http } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) + +declare const erc721Abi: { + name: string; + type: string; + stateMutability: string; + inputs: { + type: string; + name: string; + }[]; + outputs: { + type: string; + }[]; +}[] +// ---cut--- +import { readContract } from '@wagmi/core' + +const result = await readContract(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +result +// ^? +``` +::: + +You can prevent runtime errors and be more productive by making sure your ABIs and Typed Data definitions are set up appropriately. 🎉 + +```ts twoslash +// @errors: 2820 +import { createConfig, http } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) + +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { readContract } from '@wagmi/core' + +readContract(config, { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanecOf', + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], +}) +``` + +## Configure Internal Types + +For advanced use-cases, you may want to configure wagmi's internal types. Most of wagmi's types relating to ABIs and EIP-712 Typed Data are powered by [ABIType](https://github.com/wevm/abitype). See the [ABIType docs](https://abitype.dev) for more info on how to configure types. diff --git a/site/core/why.md b/site/core/why.md new file mode 100644 index 0000000000..d7ed951f02 --- /dev/null +++ b/site/core/why.md @@ -0,0 +1,46 @@ +# Why Wagmi Core + +## The Problems + +Building Ethereum applications is hard. Apps need to support connecting wallets, multiple chains, signing messages and data, sending transactions, listening for events and state changes, refreshing stale blockchain data, and much more. This is all on top of solving for app-specific use-cases and providing polished user experiences. + +The ecosystem is also continuously evolving, meaning you need to adapt to new improvements or get left behind. App developers should not need to worry about connecting tens of different wallets, the intricacies of multi-chain support, typos accidentally sending an order of magnitude more ETH or calling a misspelled contract function, or accidentally spamming their RPC provider, costing thousands in compute units. + +Wagmi Core solves all these problems and more — allowing app developers to focus on building high-quality and performant experiences for Ethereum — by focusing on **developer experience**, **performance**, **feature coverage**, and **stability.** + +## Developer Experience + +Wagmi Core delivers a great developer experience through modular and composable APIs, automatic type safety and inference, and comprehensive documentation. + +It provides developers with intuitive building blocks to build their Ethereum apps. While Wagmi Core's APIs might seem more verbose at first, it makes Wagmi Core's modular building blocks extremely flexible. Easy to move around, change, and remove. It also allows developers to better understand Ethereum concepts as well as understand _what_ and _why_ certain properties are being passed through. Learning how to use Wagmi Core is a great way to learn how to interact with Ethereum in general. + +Wagmi Core also provides [strongly typed APIs](/core/typescript), allowing consumers to get the best possible experience through [autocomplete](https://twitter.com/awkweb/status/1555678944770367493), [type inference](https://twitter.com/jakemoxey/status/1570244174502588417?s=20), as well as static validation. You often just need to provide an ABI and Wagmi Core can help you autocomplete your way to success, identify type errors before your users do, drill into blockchain errors [at compile and runtimes](/core/guides/error-handling) with surgical precision, and much more. + +The API documentation is comprehensive and contains usage info for _every_ module in Wagmi Core. The core team uses a [documentation](https://gist.github.com/zsup/9434452) and [test driven](https://en.wikipedia.org/wiki/Test-driven_development#:~:text=Test%2Ddriven%20development%20(TDD),software%20against%20all%20test%20cases.) development approach to building modules, which leads to predictable and stable APIs. + +## Performance + +Performance is critical for applications on all sizes. Slow page load and interactions can cause users to stop using applications. Wagmi Core uses and is built by the same team behind [Viem](https://viem.sh), the most performant production-ready Ethereum library. + +End users should not be required to download a module of over 100kB in order to interact with Ethereum. Wagmi Core is optimized for tree-shaking and dead-code elimination, allowing apps to minimize bundle size for fast page load times. + +Data layer performance is also critical. Slow, unnecessary, and manual data fetching can make apps unusable and cost thousands in RPC compute units. Wagmi Core supports caching, deduplication, persistence, and much more through [TanStack Query](/react/guides/tanstack-query) via the `'@wagmi/core/query'` entrypoint so you can [plug it into your framework](/core/guides/framework-adapters) of choice, like Vue, Svelte, and more. + +## Feature Coverage + +Wagmi Core supports the most popular and commonly-used Ethereum features out of the box with 40+ VanillaJS Actions for accounts, wallets, contracts, transactions, signing, ENS, and more. Wagmi Core also supports just about any wallet out there through its official [connectors](/core/api/connectors), [EIP-6963 support](/core/api/createConfig#multiinjectedproviderdiscovery), and [extensible API](/dev/creating-connectors). + +If you need lower-level control, you can always drop down to [Viem](https://viem.sh), which Wagmi Core uses internally to perform blockchain operations. Wagmi Core also manages multi-chain support automatically so developers can focus on their applications instead of adding custom code. + +Finally, Wagmi Core has a [CLI](/cli/getting-started) to manage ABIs as well as a robust ecosystem of third-party libraries, like [ConnectKit](https://docs.family.co/connectkit), [RainbowKit](https://www.rainbowkit.com), [AppKit](https://walletconnect.com/appkit), [Dynamic](https://www.dynamic.xyz), [Privy](https://privy.io), and many more, so you can get started quickly without needing to build everything from scratch. + +## Stability + +Stability is a fundamental principle for Wagmi Core. Many organizations, large and small, rely heavily on Wagmi Core and expect it to be entirely stable for their users and applications. + +Wagmi Core's test suite runs against forked Ethereum nodes to make sure functions work across chains. The test suite also runs type tests against many different versions of peer dependencies, like TypeScript, to ensure compatibility with the latest releases of other popular software. + +Wagmi Core follows semver so developers can upgrade between versions with confidence. Starting with Wagmi Core v2, new functionality will be opt-in with old functionality being deprecated alongside the new features. This means upgrading to the latest major versions will not require immediate changes. + +Lastly, the core team works full-time on Wagmi Core and [related projects](https://github.com/wevm), and is constantly improving Wagmi Core and keeping it up-to-date with industry trends and changes. + diff --git a/site/dev/contributing.md b/site/dev/contributing.md new file mode 100644 index 0000000000..f5c0d8335a --- /dev/null +++ b/site/dev/contributing.md @@ -0,0 +1,171 @@ + + +# Contributing + +Thanks for your interest in contributing to Wagmi! Please take a moment to review this document **before submitting a pull request.** + +## Overview + +This guide is intended to help you get started with contributing. By following these steps, you will understand the development process and workflow. If you want to contribute, but aren't sure where to start, you can create a [new discussion](https://github.com/wevm/wagmi/discussions/new/choose). + +:::warning +**Please ask first before starting work on any significant new features. This includes things like adding new hooks, actions, connectors, etc.** + +It's never a fun experience to have your pull request declined after investing time and effort into a new feature. To avoid this from happening, we request that contributors first create a [feature request](https://github.com/wevm/wagmi/discussions/new?category=ideas) to discuss any API changes or significant new ideas. +::: + +## 1. Cloning the repository + +To start contributing to the project, clone it to your local machine using git: + +```bash +git clone https://github.com/wevm/wagmi.git +``` + +Or the [GitHub CLI](https://cli.github.com): + +```bash +gh repo clone wevm/wagmi +``` + +## 2. Installing Node.js and pnpm + +Wagmi uses Node.js with [pnpm workspaces](https://pnpm.io/workspaces) to manage multiple projects. You can run the following command in your terminal to check your local Node.js version. + +```bash +node -v +``` + +If **`node@{{nodeVersion}}`** is not installed, you can install via [fnm](https://github.com/Schniz/fnm) or from the [official website](https://nodejs.org). + +Once Node.js is installed, run the following to install [Corepack](https://nodejs.org/api/corepack.html). Corepack automatically installs and manages **`{{packageManager}}`**. + +```bash +corepack enable +``` + +## 3. Installing dependencies + +Once in the project's root directory, run the following command to install pnpm (via Corepack) and the project's dependencies: + +```bash +pnpm install +``` + +After the install completes, pnpm links packages across the project for development and [git hooks](https://github.com/toplenboren/simple-git-hooks) are set up. + +## 4. Adding the env variables + +The [dev playgrounds](#_5-running-the-dev-playgrounds) and [test suite](#_6-running-the-test-suite) require environment variables to be set. Copy over the following environment variables to `.env`, and fill them out. + +```bash +VITE_MAINNET_FORK_URL=https://eth.merkle.io +VITE_OPTIMISM_FORK_URL=https://mainnet.optimism.io + +NEXT_PUBLIC_WC_PROJECT_ID=3fbb6bba6f1de962d911bb5b5c9dba88 +NUXT_PUBLIC_WC_PROJECT_ID=3fbb6bba6f1de962d911bb5b5c9dba88 +VITE_WC_PROJECT_ID=3fbb6bba6f1de962d911bb5b5c9dba88 + +NEXT_TELEMETRY_DISABLED=1 +NUXT_TELEMETRY_DISABLED=1 +``` + +You might want to change `*_FORK_URL` to a paid RPC provider for better performance. + +## 5. Running the dev playgrounds + +To start the local development playgrounds, run one of the following commands. These commands run playground apps, located at `./playgrounds`, that are set up for trying out code while making changes. + +```bash +pnpm dev # `wagmi` playground +pnpm dev:core # `@wagmi/core` playground +pnpm dev:create-wagmi # `create-wagmi` cli tool +pnpm dev:cli # `@wagmi/cli` tool +pnpm dev:next # `wagmi` playground with Next.js +pnpm dev:nuxt # `@wagmi/vue` playground with Nuxt.js +pnpm dev:react # `wagmi` playground (same as `pnpm dev`) +pnpm dev:vue # `@wagmi/vue` playground +``` + +Once a playground dev server is running, you can make changes to any of the package source files (e.g. `packages/react`) and it will automatically update the playground. + +## 6. Running the test suite + +Wagmi uses [Vitest](https://vitest.dev) to run tests and [Prool](https://github.com/wevm/prool) to execute tests against locally running chain forks. First, install [Anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil) via [Foundryup](https://book.getfoundry.sh/getting-started/installation). + +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +``` + +Next, make sure you have set up your [env variables](#_4-adding-the-env-variables). Now you are ready to run the tests! You have the following options for running tests: + +- `pnpm test [package?]` — runs tests in watch mode +- `pnpm test:cov` — runs tests and reports coverage +- `pnpm test:core` — runs `@wagmi/core` tests +- `pnpm test:react` — runs `wagmi` tests +- `pnpm test:vue` — runs `@wagmi/vue` tests + +When adding new features or fixing bugs, it's important to add test cases to cover the new or updated behavior. If snapshot tests fail, you can run the `test:update` command to update the snapshots. + +## 7. Writing documentation + +Documentation is crucial to helping developers of all experience levels use Wagmi. Wagmi uses [VitePress](https://vitepress.dev) for the documentation site (located at `./site`). To start the site in dev mode, run: + +```bash +pnpm docs:dev +``` + +Try to keep documentation brief and use plain language so folks of all experience levels can understand. If you think something is unclear or could be explained better, you are welcome to open a pull request. + +## 8. Submitting a pull request + +When you're ready to submit a pull request, you can follow these naming conventions: + +- Pull request titles use the [Imperative Mood](https://en.wikipedia.org/wiki/Imperative_mood) (e.g., `Add something`, `Fix something`). +- [Changesets](#versioning) use past tense verbs (e.g., `Added something`, `Fixed something`). + +When you submit a pull request, GitHub will automatically lint, build, and test your changes. If you see an ❌, it's most likely a bug in your code. Please, inspect the logs through the GitHub UI to find the cause. + +**Please make sure that "Allow edits from maintainers" is enabled so the core team can make updates to your pull request if necessary.** + +## 9. Versioning + +When adding new features or fixing bugs, we'll need to bump the package versions. We use [Changesets](https://github.com/changesets/changesets) to do this. + +::: tip +Only changes to the codebase that affect the public API or existing behavior (e.g. bugs) need changesets. +::: + +Each changeset defines which packages should be published and whether the change should be a major/minor/patch release, as well as providing release notes that will be added to the changelog upon release. + +To create a new changeset, run `pnpm changeset`. This will run the Changesets CLI, prompting you for details about the change. You’ll be able to edit the file after it’s created — don’t worry about getting everything perfect up front. + +Even though you can technically use any markdown formatting you like, headings should be avoided since each changeset will ultimately be nested within a bullet list. Instead, bold text should be used as section headings. + +If your PR is making changes to an area that already has a changeset (e.g. there’s an existing changeset covering theme API changes but you’re making further changes to the same API), you should update the existing changeset in your PR rather than creating a new one. + +### Releasing to npm + +The first time a PR with a changeset is merged after a release, a new PR will automatically be created called `chore: version packages`. Any subsequent PRs with changesets will automatically update this existing version packages PR. Merging this PR triggers the release process by publishing to npm and cleaning up the changeset files. + +### Creating a snapshot release + +If a PR has changesets, you can create a [snapshot release](https://github.com/changesets/changesets/blob/main/docs/snapshot-releases.md) by [manually dispatching](https://github.com/wevm/wagmi/actions/workflows/canary.yml) the Canary workflow. This publishes a tagged version to npm with the PR branch name and timestamp. + +## 10. Updating dependencies + +Use [Taze](https://github.com/antfu/taze) by running: + +```bash +pnpm deps # prints outdated deps +pnpm deps patch # print outdated deps with new patch versions +pnpm deps -w # updates deps (best done with clean working tree) +``` + +[Socket](https://socket.dev) checks pull requests for vulnerabilities when new dependencies and versions are added, but you should also be vigilant! When updating dependencies, you should check release notes and source code as well as lock versions when possible. diff --git a/site/dev/creating-connectors.md b/site/dev/creating-connectors.md new file mode 100644 index 0000000000..c9e82b04b1 --- /dev/null +++ b/site/dev/creating-connectors.md @@ -0,0 +1,155 @@ +# Creating Connectors + +Thanks for your interest in adding a new connector to Wagmi! Please take a moment to review this document **before starting work on a new connector.** + +## Overview + +This guide details how to create new connectors and upstream them back into Wagmi. By following these steps, you will understand the development process, workflow, and requirements for new connectors. **Not all connectors will be accepted into Wagmi** for a variety of reasons outlined in this document. + +In addition, for connector requests to be accepted, the team creating the connector must [sponsor Wagmi](https://github.com/sponsors/wevm). It takes time and effort to maintain third-party connectors. Wagmi is an OSS project that depends on sponsors and grants to continue our work. Please get in touch via [dev@wevm.dev](mailto:dev@wevm.dev) if you have questions about sponsoring. + +::: warning **Please ask first before starting work on a new connector.** +To avoid having your pull request declined after investing time and effort into a new connector, we ask that contributors create a [Connector Request](https://github.com/wevm/wagmi/discussions/new?category=connector-request) before starting work. This ensures the connector solves for an important or general use-case of interest to Wagmi users and is well supported by the Wagmi and connector teams. +::: + +## 1. Follow the contributing guide + +Check out the [Contributing Guide](/dev/contributing) to get your local development environment set up and learn more about the contributing workflow. + +## 2. Create a new file for the connector + +Create a new file in `packages/connector/src` named after the connector you want to add. + +For example, if you want to add Foo, you would create a file named `foo.ts`. File names should be camel-cased and as short as possible. + +## 3. Create the connector object. + +Import `createConnector` from `@wagmi/core` and export a new function that accepts a parameters object and returns the `createConnector` result. This is the base of all connectors. The name of the connector name should be the same as the file name. + +```ts +import { createConnector } from '@wagmi/core' + +export type FooBarBazParameters = {} + +export function fooBarBaz(parameters: FooBarBazParameters = {}) { + return createConnector((config) => ({})) +} +``` + +## 4. Add the missing properties to the object + +Now that the base of the connector is set up, you should see a type error that looks something like this: + +```ts twoslash +// @errors: 2740 +import { createConnector } from '@wagmi/core' +// ---cut--- +createConnector((config) => ({})) +``` + +The type error tells you what properties are missing from `createConnector`'s return type. Add them all in! + +#### Properties + +- `icon`: Optional icon URL for the connector. +- `id`: The ID for the connector. This should be camel-cased and as short as possible. Example: `fooBarBaz`. +- `name`: Human-readable name for the connector. Example: `'Foo Bar Baz'`. +- `rdns`: Optional reverse DNS for the connector. This is used to filter out duplicate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) injected providers when `createConfig#multiInjectedProviderDiscovery` is enabled. + +#### Methods + +- `connect`: Function for connecting the connector. +- `disconnect`: Function for disconnecting the connector. +- `getAccounts`: Function that returns the connected accounts for the connector. +- `getChainId`: Function that returns the connected chain ID for the connector. +- `getProvider`: Function that returns the underlying provider interface for internal use throughout the connector. +- `isAuthorized`: Function that returns whether the connector has connected previously and is still authorized. +- `setup`: Optional function for running when the connector is first created. +- `switchChain`: Optional function for switching the connector's active chain. + +#### Events + +- `onAccountsChanged`: Function for subscribing to account changes internally in the connector. +- `onChainChanged`: Function for subscribing to chain changes internally in the connector. +- `onConnect`: Function for subscribing to connection events internally in the connector. +- `onDisconnect`: Function for subscribing to disconnection events internally in the connector. +- `onMessage`: Optional function for subscribing to messages internally in the connector. + +#### Parameters + +`createConnector` also has the following config properties you can use within the connector: + +- `chains`: List of chains configured by the user. +- `emitter`: Emitter for emitting events. Used to sync connector state with Wagmi `Config`. The following events are available: + - `change`: Emitted when the connected accounts or chain changes. + - `connect`: Emitted when the connector connects. + - `disconnect`: Emitted when the connector disconnects. + - `error`: Emitted when the connector receives an error. + - `message`: Emitted when the connector receives a message. +- `storage`: Optional storage configured by the user. Defaults to wrapper around localStorage. + +::: tip +If you plan to use a third-party SDK, it should have minimal dependencies (limit bundle size, supply chain attacks, etc.) and use the most permissive license possible (ideally MIT). Any third-party packages, should also have [`"sideEffects": false`](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) in their `package.json` file for maximum tree-shakability support. +::: + +::: tip +All address values returned and emitted by the connector should be checksummed using Viem's [`getAddress`](https://viem.sh/docs/utilities/getAddress). +::: + +## 5. Export the connector + +Export the connector from `packages/connector/src/exports/index.ts` in alphabetic order. + +```ts +export { fooBarBaz } from './fooBarBaz.js' +``` + +## 6. Try out the connector and add tests + +While building a connector, it can be useful to try it out with Wagmi. You can use the [development playgrounds](/dev/contributing#_5-running-the-dev-playgrounds) for testing your changes. + +Ideally, you should also be able to add tests for the connector in a `connectorName.test.ts` file. This isn't always easy so at a minimum please create a test file with instructions for how to test the connector manually. The test file should include actual tests or "instruction tests" for the following: + +- How to connect the connector. +- How to disconnect the connector. +- How to switch the connector's active chain (if applicable). + +Remember to include all info required to test the connector, like software to install (browser extension, mobile app, etc.), smart contracts to interact with/deploy, etc. + +Finally, you should also update the test file in `packages/connectors/src/exports/index.test.ts` to include the new connector. You can do this manually or by running: + +```bash +pnpm test:update packages/connectors/src/exports/index.test.ts +``` + +## 7. Add your team to CODEOWNERS + +It is critical that connectors are updated in a timely manner and actively maintained so that users of Wagmi can rely on them in production settings. + +The Wagmi core team will provide as much assistance as possible to keep connectors up-to-date with breaking changes from Wagmi, but it is your responsibility to ensure that any dependencies and issues/discussions related to the connector are handled in a timely manner. If issues are not resolved in a timely manner, the connector may be removed from Wagmi. + +In support of this goal, add at least one member of your team to the [CODEOWNERS](https://github.com/wevm/wagmi/blob/main/.github/CODEOWNERS) file so that you get notified of pull requests, issues, etc. related to the connector. You can add your team like this: + +``` +/packages/connectors/src/fooBarBaz @tmm @jxom +``` + +For more info about GitHub code owners, check out the [GitHub Documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners). + +## 8. Document the connector + +The connector should be documented. Follow the step on [writing documentation](/dev/contributing#_7-writing-documentation) to get set up with running the docs site locally and add the required pages. + +## 9. Create a changeset + +Now that the connector works and has tests, it's time to create a changeset to prepare for release. Run the following to create a changeset: + +```bash +pnpm changeset +``` + +The changeset should be a `patch` applied to the `@wagmi/connectors` repository with the description `Added [ConnectorName]`, For example, `Added Foo Bar Baz`. + +## 10. Create a pull request + +The connector is ready to go! Create a [pull request](/dev/contributing#_8-submitting-a-pull-request) and the connector should make it into a future release of Wagmi after some review. diff --git a/site/index.md b/site/index.md new file mode 100644 index 0000000000..23b40a7f13 --- /dev/null +++ b/site/index.md @@ -0,0 +1,58 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +description: Type Safe, Extensible, and Modular by design. Build high-performance blockchain frontends. +title: 'Wagmi | Reactivity for Ethereum apps' +titleTemplate: false + +hero: + name: Wagmi + text: Reactivity for Ethereum apps + tagline: Type Safe, Extensible, and Modular by design. Build high-performance blockchain frontends. + actions: + - theme: brand + text: Get Started + link: /react/getting-started + - theme: alt + text: Why Wagmi + link: /react/why + - theme: alt + text: View on GitHub + link: https://github.com/wevm/wagmi + image: + src: /logo-dark.svg + alt: Wagmi Logo + +features: + - icon: 🚀 + title: 20+ React Hooks + details: React Hooks for accounts, wallets, contracts, transactions, signing, ENS, and more. + link: /react/api/hooks + linkText: See all hooks + - icon: 🦄 + title: TypeScript Ready + details: Infer types from ABIs and EIP-712 Typed Data and autocomplete your way to productivity. + link: /react/typescript + linkText: Learn about TypeScript support + - icon: 💼 + title: Connect Wallet + details: Official connectors for MetaMask, EIP-6963, WalletConnect, Coinbase Wallet, and more. + link: /react/api/connectors + linkText: See all connectors + - icon: 👟 + title: Caching. Deduplication. Persistence. + details: Built-in caching, deduplication, persistence powered by TanStack Query. + link: /react/guides/tanstack-query + linkText: How to use TanStack Query + - icon: 🌳 + title: Modular By Design + details: Don't use React or Vue? Use VanillaJS or build an adapter for your favorite framework. + link: /core/getting-started + linkText: Learn about Wagmi Core + - icon: ✌️ + title: Built on Viem + details: The modern, low-level TypeScript interface for Ethereum that performs blockchain operations. + link: https://viem.sh + linkText: Check out Viem +--- diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000000..f032eeab66 --- /dev/null +++ b/site/package.json @@ -0,0 +1,28 @@ +{ + "name": "site", + "private": true, + "type": "module", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "@shikijs/vitepress-twoslash": "1.22.2", + "@tanstack/query-core": "catalog:", + "@tanstack/react-query": "catalog:", + "@tanstack/vue-query": "catalog:", + "@types/react": "catalog:", + "@wagmi/connectors": "workspace:*", + "@wagmi/core": "workspace:*", + "@wagmi/vue": "workspace:*", + "abitype": "*", + "nuxt": "^3.19.0", + "react": "catalog:", + "unocss": "^0.59.4", + "viem": "2.*", + "vitepress": "1.5.0", + "vue": "catalog:", + "wagmi": "workspace:*" + } +} diff --git a/site/public/browsers/chrome.png b/site/public/browsers/chrome.png new file mode 100644 index 0000000000..3024356395 Binary files /dev/null and b/site/public/browsers/chrome.png differ diff --git a/site/public/browsers/edge.png b/site/public/browsers/edge.png new file mode 100644 index 0000000000..931cb1c1bf Binary files /dev/null and b/site/public/browsers/edge.png differ diff --git a/site/public/browsers/firefox.png b/site/public/browsers/firefox.png new file mode 100644 index 0000000000..fc7123205a Binary files /dev/null and b/site/public/browsers/firefox.png differ diff --git a/site/public/browsers/opera.png b/site/public/browsers/opera.png new file mode 100644 index 0000000000..472a152348 Binary files /dev/null and b/site/public/browsers/opera.png differ diff --git a/site/public/browsers/safari.png b/site/public/browsers/safari.png new file mode 100644 index 0000000000..aba5f86d1f Binary files /dev/null and b/site/public/browsers/safari.png differ diff --git a/site/public/favicon.svg b/site/public/favicon.svg new file mode 100644 index 0000000000..48c440d660 --- /dev/null +++ b/site/public/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/site/public/logo-dark.svg b/site/public/logo-dark.svg new file mode 100644 index 0000000000..df0c2945ce --- /dev/null +++ b/site/public/logo-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/site/public/logo-light.svg b/site/public/logo-light.svg new file mode 100644 index 0000000000..cc571bc094 --- /dev/null +++ b/site/public/logo-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/site/public/og.png b/site/public/og.png new file mode 100644 index 0000000000..8b3a05f556 Binary files /dev/null and b/site/public/og.png differ diff --git a/site/react/api/WagmiProvider.md b/site/react/api/WagmiProvider.md new file mode 100644 index 0000000000..7330f4569b --- /dev/null +++ b/site/react/api/WagmiProvider.md @@ -0,0 +1,112 @@ +# WagmiProvider + +React Context Provider for Wagmi. + +## Import + +```ts +import { WagmiProvider } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [app.tsx] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +function App() { + return ( + + {/** ... */} + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WagmiProviderProps } from 'wagmi' +``` + +### config + +[`Config`](/react/api/createConfig#config) object to inject with context. + +::: code-group +```tsx [app.tsx] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +function App() { + return ( + + {/** ... */} + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### initialState + +`State | undefined` + +- Initial state to hydrate into the [Wagmi Config](/react/api/createConfig). Useful for SSR. + +::: code-group +```tsx [app.tsx] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +function App() { + return ( + + {/** ... */} + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### reconnectOnMount + +`boolean | undefined` + +- Whether or not to reconnect previously connected [connectors](/react/api/createConfig#connectors) on mount. +- Defaults to `true`. + +::: code-group +```tsx [app.tsx] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +function App() { + return ( + + {/** ... */} + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Context + +```ts +import { type WagmiContext } from 'wagmi' +``` diff --git a/site/react/api/actions.md b/site/react/api/actions.md new file mode 100644 index 0000000000..af367bb025 --- /dev/null +++ b/site/react/api/actions.md @@ -0,0 +1,28 @@ +# Actions + +Sometimes the declarative nature of React Hooks doesn't work for parts of your app. For those cases, you can use Wagmi Core Actions directly! + +All the Wagmi Core Actions are importable using the `wagmi/actions` entrypoint. For example, you can use the `watchBlockNumber` action to watch for block number changes. + +::: code-group +```ts [index.tsx] +import { useConfig } from 'wagmi' +import { watchBlockNumber } from 'wagmi/actions' +import { useEffect } from 'react' + +function App() { + const config = useConfig() + + useEffect(() => { + return watchBlockNumber(config, { + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + }) + }, []) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +See the [Wagmi Core docs](/core/api/actions) for more info on what actions are available. diff --git a/site/react/api/chains.md b/site/react/api/chains.md new file mode 100644 index 0000000000..4a7e600447 --- /dev/null +++ b/site/react/api/chains.md @@ -0,0 +1,26 @@ + + +# Chains + +Viem `Chain` objects. More info at the [Viem docs](https://viem.sh/docs/chains/introduction). + +## Import + +Import via the `'wagmi/chains'` entrypoint (proxies all chains from `'viem/chains'`). + +```ts +import { mainnet } from 'wagmi/chains' +``` + +## Available Chains + +Chain definitions as of `viem@{{viemVersion}}`. For `viem@latest`, visit the [Viem repo](https://github.com/wevm/viem/blob/main/src/chains/index.ts). + + + + diff --git a/site/react/api/connectors.md b/site/react/api/connectors.md new file mode 100644 index 0000000000..49096a1641 --- /dev/null +++ b/site/react/api/connectors.md @@ -0,0 +1,28 @@ + + +# Connectors + +Connectors for popular wallet providers and protocols. + +## Import + +Import via the `'wagmi/connectors'` entrypoint. + +```ts +import { injected } from 'wagmi/connectors' +``` + +## Available Connectors + + diff --git a/site/react/api/connectors/coinbaseWallet.md b/site/react/api/connectors/coinbaseWallet.md new file mode 100644 index 0000000000..b8597b11ae --- /dev/null +++ b/site/react/api/connectors/coinbaseWallet.md @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/site/react/api/connectors/injected.md b/site/react/api/connectors/injected.md new file mode 100644 index 0000000000..56573881f4 --- /dev/null +++ b/site/react/api/connectors/injected.md @@ -0,0 +1,7 @@ + + + diff --git a/site/react/api/connectors/metaMask.md b/site/react/api/connectors/metaMask.md new file mode 100644 index 0000000000..e16d849037 --- /dev/null +++ b/site/react/api/connectors/metaMask.md @@ -0,0 +1,7 @@ + + + diff --git a/site/react/api/connectors/mock.md b/site/react/api/connectors/mock.md new file mode 100644 index 0000000000..3a111297a1 --- /dev/null +++ b/site/react/api/connectors/mock.md @@ -0,0 +1,6 @@ + + + diff --git a/site/react/api/connectors/safe.md b/site/react/api/connectors/safe.md new file mode 100644 index 0000000000..375ddb7b2d --- /dev/null +++ b/site/react/api/connectors/safe.md @@ -0,0 +1,6 @@ + + + diff --git a/site/react/api/connectors/walletConnect.md b/site/react/api/connectors/walletConnect.md new file mode 100644 index 0000000000..3e1a4a5c24 --- /dev/null +++ b/site/react/api/connectors/walletConnect.md @@ -0,0 +1,6 @@ + + + diff --git a/site/react/api/createConfig.md b/site/react/api/createConfig.md new file mode 100644 index 0000000000..f42bec2539 --- /dev/null +++ b/site/react/api/createConfig.md @@ -0,0 +1,7 @@ + + + diff --git a/site/react/api/createStorage.md b/site/react/api/createStorage.md new file mode 100644 index 0000000000..738e998f0b --- /dev/null +++ b/site/react/api/createStorage.md @@ -0,0 +1,6 @@ + + + diff --git a/site/react/api/errors.md b/site/react/api/errors.md new file mode 100644 index 0000000000..d431a6c4fe --- /dev/null +++ b/site/react/api/errors.md @@ -0,0 +1,20 @@ + + +# Errors + +Error classes used by Wagmi. + + + +## React + +### WagmiProviderNotFoundError + +When a Wagmi hook is used outside of a [`WagmiProvider`](/react/api/WagmiProvider). + +```ts +import { WagmiProviderNotFoundError } from 'wagmi' +``` \ No newline at end of file diff --git a/site/react/api/hooks.md b/site/react/api/hooks.md new file mode 100644 index 0000000000..9d8a65c718 --- /dev/null +++ b/site/react/api/hooks.md @@ -0,0 +1,25 @@ + + +# Hooks + +React Hooks for accounts, wallets, contracts, transactions, signing, ENS, and more. + +## Import + +```ts +import { useAccount } from 'wagmi' +``` + +## Available Hooks + + diff --git a/site/react/api/hooks/useAccount.md b/site/react/api/hooks/useAccount.md new file mode 100644 index 0000000000..d5b9e2964c --- /dev/null +++ b/site/react/api/hooks/useAccount.md @@ -0,0 +1,65 @@ +--- +title: useAccount +description: Hook for getting current account. +--- + +# useAccount + +Hook for getting current account. + +## Import + +```ts +import { useAccount } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useAccount } from 'wagmi' + +function App() { + const account = useAccount() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseAccountParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useAccount } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const account = useAccount({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseAccountReturnType } from 'wagmi' +``` + + + +## Action + +- [`getAccount`](/core/api/actions/getAccount) diff --git a/site/react/api/hooks/useAccountEffect.md b/site/react/api/hooks/useAccountEffect.md new file mode 100644 index 0000000000..6636f4bbaa --- /dev/null +++ b/site/react/api/hooks/useAccountEffect.md @@ -0,0 +1,113 @@ +--- +title: useAccountEffect +description: Hook for listening to account lifecycle events. +--- + +# useAccountEffect + +Hook for listening to account lifecycle events. + +## Import + +```ts +import { useAccountEffect } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from 'wagmi' + +function App() { + useAccountEffect({ + onConnect(data) { + console.log('Connected!', data) + }, + onDisconnect() { + console.log('Disconnected!') + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseAccountEffectParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + useAccountEffect({ + config, // [!code focus] + onConnect(data) { + console.log('Connected!', data) + }, + onDisconnect() { + console.log('Disconnected!') + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### onConnect + +`` ((data: { address: `0x${string}`; addresses: readonly [`0x${string}`, ...`0x${string}`[]]; chain: Chain | undefined chainId: number; connector: Connector; isReconnected: boolean }) => void) | undefined `` + +Callback that is called when accounts are connected. + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from 'wagmi' + +function App() { + useAccountEffect({ + onConnect(data) { // [!code focus] + console.log('Connected!', data) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### onDisconnect + +`(() => void) | undefined` + +Callback that is called when no more accounts are connected. + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from 'wagmi' + +function App() { + useAccountEffect({ + onDisconnect() { // [!code focus] + console.log('Disconnected!') // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Action + +- [`getAccount`](/core/api/actions/getAccount) +- [`watchAccount`](/core/api/actions/watchAccount) diff --git a/site/react/api/hooks/useBalance.md b/site/react/api/hooks/useBalance.md new file mode 100644 index 0000000000..deddbb720d --- /dev/null +++ b/site/react/api/hooks/useBalance.md @@ -0,0 +1,226 @@ +--- +title: useBalance +description: Hook for fetching native currency or token balance. +--- + + + +# useBalance + +Hook for fetching native currency or token balance. + +## Import + +```ts +import { useBalance } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBalanceParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +Address to get balance for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```tsx [index.tsx] +import { useBalance } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get balance at. + +::: code-group +```ts [index.ts] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockNumber: 17829139n, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get balance at. + +::: code-group +```ts [index.ts] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useBalance } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useBalance } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### token [deprecated](/react/guides/migrate-from-v1-to-v2#deprecated-usebalance-token-parameter) + +`Address | undefined` + +ERC-20 token address to get balance for. + +::: code-group +```ts [index.ts] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### unit [deprecated](/react/guides/migrate-from-v1-to-v2#deprecated-usebalance-unit-parameter-and-formatted-return-value) + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { useBalance } from 'wagmi' + +function App() { + const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + unit: 'ether', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBalanceReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getBalance`](/core/api/actions/getBalance) diff --git a/site/react/api/hooks/useBlock.md b/site/react/api/hooks/useBlock.md new file mode 100644 index 0000000000..f0962ee4c1 --- /dev/null +++ b/site/react/api/hooks/useBlock.md @@ -0,0 +1,227 @@ +--- +title: useBlock +description: Hook for fetching information about a block at a block number, hash or tag. +--- + + + +# useBlock + +Hook for fetching information about a block at a block number, hash or tag. + +## Import + +```ts +import { useBlock } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBlockParameters } from 'wagmi' +``` + +### blockHash + +`` `0x${string}` `` + +Information at a given block hash. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockHash: '0x89644bbd5c8d682a2e9611170e6c1f02573d866d286f006cbf517eec7254ec2d' // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`` bigint `` + +Information at a given block number. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockNumber: 42069n // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`` 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' `` + +Information at a given block tag. Defaults to `'latest'`. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockTag: 'pending' // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useBlock({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBlock({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### includeTransactions + +`boolean` + +Whether or not to include transactions as objects. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { config } from './config' + +function App() { + const result = useBlock({ + includeTransactions: true // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { config } from './config' + +function App() { + const result = useBlock({ + scopeKey: 'foo' // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### watch + +`boolean | UseWatchBlockParameters | undefined` + +- Enables/disables listening for block changes. +- Can pass a subset of [`UseWatchBlocksParameters`](/react/api/hooks/useWatchBlocks#parameters) directly to [`useWatchBlocks`](/react/api/hooks/useWatchBlocks). + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + watch: true, // [!code focus] + }) +} +``` + +```tsx [index-2.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + watch: { // [!code focus] + pollingInterval: 4_000, // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBlockReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getBlock`](/core/api/actions/getBlock) +- [`watchBlockNumber`](/core/api/actions/watchBlockNumber) diff --git a/site/react/api/hooks/useBlockNumber.md b/site/react/api/hooks/useBlockNumber.md new file mode 100644 index 0000000000..e0c6ff7242 --- /dev/null +++ b/site/react/api/hooks/useBlockNumber.md @@ -0,0 +1,169 @@ +--- +title: useBlockNumber +description: Hook for fetching the number of the most recent block seen. +--- + + + +# useBlockNumber + +Hook for fetching the number of the most recent block seen. + +## Import + +```ts +import { useBlockNumber } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' + +function App() { + const result = useBlockNumber() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBlockNumberParameters } from 'wagmi' +``` + +### cacheTime + +`number | undefined` + +Time in milliseconds that cached block number will remain in memory. + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' + +function App() { + const result = useBlockNumber({ + cacheTime: 4_000, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useBlockNumber({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBlockNumber({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBlockNumber({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### watch + +`boolean | UseWatchBlockNumberParameters | undefined` + +- Enables/disables listening for block number changes. +- Can pass a subset of [`UseWatchBlockNumberParameters`](/react/api/hooks/useWatchBlockNumber#parameters)directly to [`useWatchBlockNumber`](/react/api/hooks/useWatchBlockNumber). + +::: code-group +```tsx [index.tsx] +import { useBlockNumber } from 'wagmi' + +function App() { + const result = useBlockNumber({ + watch: true, // [!code focus] + }) +} +``` + +```tsx [index-2.tsx] +import { useBlockNumber } from 'wagmi' + +function App() { + const result = useBlockNumber({ + watch: { // [!code focus] + pollingInterval: 4_000, // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBlockNumberReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getBlockNumber`](/core/api/actions/getBlockNumber) +- [`watchBlockNumber`](/core/api/actions/watchBlockNumber) diff --git a/site/react/api/hooks/useBlockTransactionCount.md b/site/react/api/hooks/useBlockTransactionCount.md new file mode 100644 index 0000000000..80cb99de32 --- /dev/null +++ b/site/react/api/hooks/useBlockTransactionCount.md @@ -0,0 +1,175 @@ +--- +title: useBlockTransactionCount +description: Hook for fetching the number of Transactions at a block number, hash or tag. +--- + + + +# useBlockTransactionCount + +Hook for fetching the number of Transactions at a block number, hash or tag. + +## Import + +```ts +import { useBlockTransactionCount } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useBlockTransactionCount } from 'wagmi' + +function App() { + const result = useBlockTransactionCount() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBlockTransactionCountParameters } from 'wagmi' +``` + +### blockHash + +`` `0x${string}` `` + +Transaction count at a given block hash. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockHash: '0x89644bbd5c8d682a2e9611170e6c1f02573d866d286f006cbf517eec7254ec2d' // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`` bigint `` + +Transaction count at a given block number. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockNumber: 42069n // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`` 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' `` + +Transaction count at a given block tag. Defaults to `'latest'`. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' + +function App() { + const result = useBlock({ + blockTag: 'pending' // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useBlock({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useBlockTransactionCount } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBlockTransactionCount({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBlockTransactionCount } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBlockTransactionCount({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + + +## Return Type + +```ts +import { type UseBlockTransactionCountReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getBlockTransactionCount`](/core/api/actions/getBlockTransactionCount) diff --git a/site/react/api/hooks/useBytecode.md b/site/react/api/hooks/useBytecode.md new file mode 100644 index 0000000000..573cee32c0 --- /dev/null +++ b/site/react/api/hooks/useBytecode.md @@ -0,0 +1,181 @@ +--- +title: useBytecode +description: Hook for retrieving the bytecode at an address. +--- + + + +# useBytecode + +Hook for retrieving the bytecode at an address. + +## Import + +```ts +import { useBytecode } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' + +function App() { + const result = useBytecode({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBytecodeParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +The contract address. + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' + +function App() { + const result = useBytecode({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +The block number to check the bytecode at. + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' + +function App() { + const result = useBytecode({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockNumber: 16280770n, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to check the bytecode at. + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' + +function App() { + const result = useBytecode({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockTag: 'safe', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The chain ID to check the bytecode at. + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useBytecode({ + chainId: mainnet.id, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useBytecode({ + config, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBytecode } from 'wagmi' +import { config } from './config' + +function App() { + const result = useBytecode({ + scopeKey: 'foo' // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBytecodeReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getBytecode`](/core/api/actions/getBytecode) diff --git a/site/react/api/hooks/useCall.md b/site/react/api/hooks/useCall.md new file mode 100644 index 0000000000..9bc2250cf2 --- /dev/null +++ b/site/react/api/hooks/useCall.md @@ -0,0 +1,397 @@ +--- +title: useCall +description: Hook for executing a new message call immediately without submitting a transaction to the network. +--- + + + +# useCall + +Hook for executing a new message call immediately without submitting a transaction to the network. + +## Import + +```ts +import { useCall } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseCallParameters } from 'wagmi' +``` + +### account + +`Account | Address | undefined` + +The Account to call from. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### data + +`0x${string} | undefined` + +A contract hashed method call with encoded args. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### to + +`Address | undefined` + +The contract address or recipient. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + accessList: [ // [!code focus:6] + { + address: '0x1', + storageKeys: ['0x1'], + }, + ], + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +The gas provided for transaction execution. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + gas: 1_000_000n, // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### gasPrice + +`bigint | undefined` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas (in wei), inclusive of `maxPriorityFeePerGas`. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas (in wei). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### nonce + +`number | undefined` + +Unique number identifying this transaction. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + nonce: 420, // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value (in wei) sent with this transaction. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`number | undefined` + +The block number to perform the call against. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + blockNumber: 15121123n, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to perform the call against. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' + +function App() { + const result = useCall({ + blockTag: 'safe', // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The block tag to perform the call against. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' +import { mainnet } from '@wagmi/core/chains' + +function App() { + const result = useCall({ + chainId: mainnet.id, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useCall({ + config, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useCall } from 'wagmi' +import { config } from './config' + +function App() { + const result = useCall({ + scopeKey: 'foo' // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseCallReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`call`](/core/api/actions/call) diff --git a/site/react/api/hooks/useCallsStatus.md b/site/react/api/hooks/useCallsStatus.md new file mode 100644 index 0000000000..48135c53eb --- /dev/null +++ b/site/react/api/hooks/useCallsStatus.md @@ -0,0 +1,143 @@ +--- +title: useCallsStatus +description: Hook for fetching the number of the most recent block seen. +--- + + + +# useCallsStatus + +Hook to fetch the status and receipts of a call batch that was sent via [`useSendCalls`](/react/api/hooks/useSendCalls). + + + +## Import + +```ts +import { useCallsStatus } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi' + +function App() { + const result = useCallsStatus({ + id: '0x...', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseCallsStatusParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useCallsStatus({ + config, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```tsx [index.tsx] +import { useCallsStatus, useConnections } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections() + const result = useCallsStatus({ + connector: connections[0]?.connector, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { useCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await useCallsStatus({ + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useCallsStatus } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useCallsStatus({ + id: '0x...', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseCallsStatusReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getCallsStatus`](https://viem.sh/experimental/eip5792/getCallsStatus) \ No newline at end of file diff --git a/site/react/api/hooks/useCapabilities.md b/site/react/api/hooks/useCapabilities.md new file mode 100644 index 0000000000..d9d6fc74a7 --- /dev/null +++ b/site/react/api/hooks/useCapabilities.md @@ -0,0 +1,138 @@ +--- +title: useCapabilities +description: Hook for fetching the number of the most recent block seen. +--- + + + +# useCapabilities + +Hook to extract capabilities (grouped by chain ID) that a connected wallet supports (e.g. paymasters, session keys, etc). + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_getcapabilities) + +## Import + +```ts +import { useCapabilities } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi' + +function App() { + const result = useCapabilities() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseCapabilitiesParameters } from 'wagmi' +``` + +### account + +`Account | Address | undefined` + +Fetch capabilities for the provided account. + +::: code-group +```ts [index.ts] +import { useCapabilities } from '@wagmi/core' +import { config } from './config' + +const status = await useCapabilities({ + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useCapabilities({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```tsx [index.tsx] +import { useCapabilities, useConnections } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections() + const result = useCapabilities({ + connector: connections[0]?.connector, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useCapabilities } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useCapabilities({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseCapabilitiesReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getCapabilities`](https://viem.sh/experimental/eip5792/getCapabilities) \ No newline at end of file diff --git a/site/react/api/hooks/useChainId.md b/site/react/api/hooks/useChainId.md new file mode 100644 index 0000000000..509f5a1e4c --- /dev/null +++ b/site/react/api/hooks/useChainId.md @@ -0,0 +1,74 @@ +--- +title: useChainId +description: Hook for getting current chain ID. +--- + +# useChainId + +Hook for getting current chain ID. + +## Import + +```ts +import { useChainId } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useChainId } from 'wagmi' + +function App() { + const chainId = useChainId() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseChainIdParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useChainId } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const chainId = useChainId({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseChainIdReturnType } from 'wagmi' +``` + +`number` + +Current chain ID from [`config.state.chainId`](/react/api/createConfig#chainid). + +::: info +Only returns chain IDs for chains configured via `createConfig`'s [`chains`](/react/api/createConfig#chains) parameter. + +If the active [connection](/react/api/createConfig#connection) [`chainId`](/react/api/createConfig#chainid-1) is not from a chain included in your Wagmi `Config`, `useChainId` will return the last configured chain ID. +::: + +## Action + +- [`getChainId`](/core/api/actions/getChainId) +- [`watchChainId`](/core/api/actions/watchChainId) diff --git a/site/react/api/hooks/useChains.md b/site/react/api/hooks/useChains.md new file mode 100644 index 0000000000..286f794276 --- /dev/null +++ b/site/react/api/hooks/useChains.md @@ -0,0 +1,67 @@ +--- +title: useChains +description: Hook for getting configured chains +--- + +# useChains + +Hook for getting configured chains + +## Import + +```ts +import { useChains } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useChains } from 'wagmi' + +function App() { + const chains = useChains() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseChainsParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useChains } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const chains = useChains({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseChainsReturnType } from 'wagmi' +``` + +`readonly [Chain, ...Chain[]]` + +Chains from [`config.chains`](/react/api/createConfig#chains). + +## Action + +- [`getChains`](/core/api/actions/getChains) diff --git a/site/react/api/hooks/useClient.md b/site/react/api/hooks/useClient.md new file mode 100644 index 0000000000..fc87a15e47 --- /dev/null +++ b/site/react/api/hooks/useClient.md @@ -0,0 +1,89 @@ +--- +title: useClient +description: Hook for getting Viem `Client` instance. +--- + +# useClient + +Hook for getting Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. + +## Import + +```ts +import { useClient } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useClient } from 'wagmi' + +function App() { + const client = useClient() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseClientParameters } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when getting Viem Client. + +::: code-group +```ts [index.ts] +import { useClient } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { config } from './config' + +function App() { + const client = useClient({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useClient } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const client = useClient({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseClientReturnType } from 'wagmi' +``` + +`Client | undefined` + +Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. + +## Action + +- [`getClient`](/core/api/actions/getClient) +- [`watchClient`](/core/api/actions/watchClient) diff --git a/site/react/api/hooks/useConfig.md b/site/react/api/hooks/useConfig.md new file mode 100644 index 0000000000..6f70b6e821 --- /dev/null +++ b/site/react/api/hooks/useConfig.md @@ -0,0 +1,75 @@ +--- +title: useConfig +description: Hook for getting `Config` from nearest `WagmiProvider`. +--- + +# useConfig + +Hook for getting [`Config`](/react/api/createConfig#config) from nearest [`WagmiProvider`](/react/api/WagmiProvider). + +## Import + +```ts +import { useConfig } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useConfig } from 'wagmi' + +function App() { + const config = useConfig() +} +``` + +::: + +## Return Type + +```ts +import { type UseConfigReturnType } from 'wagmi' +``` + +If you use TypeScript and [register your `Config`](/react/typescript#register-config), the return type will be inferred. + +::: code-group +```ts twoslash [index.tsx] +import { type Config } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module 'wagmi' { + interface Register { + config: Config + } +} +// ---cut--- +import { useConfig } from 'wagmi' + +function App() { + const config = useConfig() + // ^? +} +``` + +```ts [config.ts] +import { createConfig, http } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +::: diff --git a/site/react/api/hooks/useConnect.md b/site/react/api/hooks/useConnect.md new file mode 100644 index 0000000000..ba52ee2aaf --- /dev/null +++ b/site/react/api/hooks/useConnect.md @@ -0,0 +1,117 @@ +--- +title: useConnect +description: Hook for connecting accounts with connectors. +--- + + + +# useConnect + +Hook for connecting accounts with [connectors](/react/api/connectors). + +## Import + +```ts +import { useConnect } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useConnect } from 'wagmi' +import { injected } from 'wagmi/connectors' + +function App() { + const { connect } = useConnect() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useConnect } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useConnect({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseConnectReturnType } from 'wagmi' +``` + +### connectors + +`readonly Connector[]` + +Globally configured connectors via [`createConfig`](/react/api/createConfig#connectors). Useful for rendering a list of available connectors. + +::: code-group +```tsx [index.tsx] +import { useConnect } from 'wagmi' + +function App() { + const { connect, connectors } = useConnect() + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +::: tip +Not all connectors support connecting directly to a `chainId` (e.g. they don't support programmatic chain switching). In those cases, the connector will connect to whatever chain the connector's provider (e.g. wallet) is connected to. +::: + + + +## Action + +- [`connect`](/core/api/actions/connect) diff --git a/site/react/api/hooks/useConnections.md b/site/react/api/hooks/useConnections.md new file mode 100644 index 0000000000..f6f18b14fa --- /dev/null +++ b/site/react/api/hooks/useConnections.md @@ -0,0 +1,64 @@ +--- +title: useConnections +description: Hook for getting active connections. +--- + +# useConnections + +Hook for getting active connections. + +## Import + +```ts +import { useConnections } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useConnections } from 'wagmi' + +function App() { + const connections = useConnections() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectionsParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useConnections } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseConnectionsReturnType } from 'wagmi' +``` + +## Action + +- [`getConnections`](/core/api/actions/getConnections) +- [`watchConnections`](/core/api/actions/watchConnections) diff --git a/site/react/api/hooks/useConnectorClient.md b/site/react/api/hooks/useConnectorClient.md new file mode 100644 index 0000000000..60aa74b77d --- /dev/null +++ b/site/react/api/hooks/useConnectorClient.md @@ -0,0 +1,128 @@ +--- +title: useConnectorClient +description: Hook for getting a Viem `Client` object for the current or provided connector. +--- + + + +# useConnectorClient + +Hook for getting a Viem [`Client`](https://viem.sh/docs/clients/custom.html) object for the current or provided connector. + +## Import + +```ts +import { useConnectorClient } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useConnectorClient } from 'wagmi' + +function App() { + const result = useConnectorClient() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectorClientParameters } from 'wagmi' +``` + +### account + +`Address | Account | undefined` + +Account to use with client. Throws if account is not found on [`connector`](#connector). + +```ts +import { useConnectorClient } from 'wagmi' + +function App() { + const result = useConnectorClient({ + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +} +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use with client. + +```ts +import { useConnectorClient } from 'wagmi' + +function App() { + const result = useConnectorClient({ + chainId: mainnet.id, // [!code focus] + }) +} +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useConnectorClient } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useConnectorClient({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to get client for. +- Defaults to current connector. + +```ts +import { useConnections, useConnectorClient } from 'wagmi' + +function App() { + const connections = useConnections(config) + const result = useConnectorClient({ + connector: connections[0]?.connector, // [!code focus] + }) +} +``` + + + +## Return Type + +```ts +import { type UseConnectorClientReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getConnectorClient`](/core/api/actions/getConnectorClient) diff --git a/site/react/api/hooks/useConnectors.md b/site/react/api/hooks/useConnectors.md new file mode 100644 index 0000000000..f02e2dfd2f --- /dev/null +++ b/site/react/api/hooks/useConnectors.md @@ -0,0 +1,41 @@ +--- +title: useConnectors +description: Hook for getting configured connectors. +--- + +# useConnectors + +Hook for getting configured connectors. + +## Import + +```ts +import { useConnectors } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useConnectors } from 'wagmi' + +function App() { + const connectors = useConnectors() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseConnectorsReturnType } from 'wagmi' +``` + +`readonly Connector[]` + +Connectors from [`config.connectors`](/react/api/createConfig#connectors-1). + +## Action + +- [`getConnectors`](/core/api/actions/getConnectors) diff --git a/site/react/api/hooks/useDeployContract.md b/site/react/api/hooks/useDeployContract.md new file mode 100644 index 0000000000..42b2e9ffe3 --- /dev/null +++ b/site/react/api/hooks/useDeployContract.md @@ -0,0 +1,145 @@ +--- +title: useDeployContract +description: Hook for deploying a contract to the network, given bytecode & constructor arguments. +--- + + + +# useDeployContract + +Hook for deploying a contract to the network, given bytecode, and constructor arguments. + +## Import + +```ts +import { useDeployContract } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useDeployContract } from 'wagmi' +import { parseEther } from 'viem' +import { wagmiAbi } from './abi' + +function App() { + const { deployContract } = useDeployContract() + + return ( + + ) +} +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Deploying with Constructor Args + +::: code-group +```tsx [index.tsx] +import { useDeployContract } from 'wagmi' +import { parseEther } from 'viem' +import { wagmiAbi } from './abi' + +function App() { + const { deployContract } = useDeployContract() + + return ( + + ) +} +``` +```ts [abi.ts] +export const wagmiAbi = [ + ... + { + inputs: [{ name: "x", type: "uint32" }], + stateMutability: "nonpayable", + type: "constructor", + }, + ... +] as const; +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type useDeployContractParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useDeployContract } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useDeployContract({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type useDeployContractReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`deployContract`](/core/api/actions/deployContract) diff --git a/site/react/api/hooks/useDisconnect.md b/site/react/api/hooks/useDisconnect.md new file mode 100644 index 0000000000..5e537a846d --- /dev/null +++ b/site/react/api/hooks/useDisconnect.md @@ -0,0 +1,113 @@ +--- +title: useDisconnect +description: Hook for disconnecting connections. +--- + + + +# useDisconnect + +Hook for disconnecting connections. + +## Import + +```ts +import { useDisconnect } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useDisconnect } from 'wagmi' + +function App() { + const { disconnect } = useDisconnect() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseDisconnectParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useDisconnect } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useDisconnect({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseDisconnectReturnType } from 'wagmi' +``` + +### connectors + +`readonly Connector[]` + +Connectors that are currently connected. Useful for rendering a list of connectors to disconnect. + +::: code-group +```tsx [index.tsx] +import { useDisconnect } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const { connectors, disconnect } = useDisconnect() + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + + + +## Action + +- [`disconnect`](/core/api/actions/disconnect) diff --git a/site/react/api/hooks/useEnsAddress.md b/site/react/api/hooks/useEnsAddress.md new file mode 100644 index 0000000000..b4807384f1 --- /dev/null +++ b/site/react/api/hooks/useEnsAddress.md @@ -0,0 +1,238 @@ +--- +title: useEnsAddress +description: Hook for fetching ENS address for name. +--- + + + +# useEnsAddress + +Hook for fetching ENS address for name. + +## Import + +```ts +import { useEnsAddress } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsAddress`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsAddressParameters } from 'wagmi' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS address at. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS address at. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + name: normalize('wevm.eth'), + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEnsAddress } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### coinType + +`number | undefined` + +The [ENSIP-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution) coin type to fetch the address for. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + coinType: 60, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' +import { config } from './config' // [!code focus] + +function App() { + const result = useEnsAddress({ + config, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### name + +`string | undefined` + +Name to get the address for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + name: normalize('wevm.eth'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + name: normalize('wevm.eth'), + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAddress({ + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsAddressReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getEnsAddress`](/core/api/actions/getEnsAddress) diff --git a/site/react/api/hooks/useEnsAvatar.md b/site/react/api/hooks/useEnsAvatar.md new file mode 100644 index 0000000000..ec09af5e31 --- /dev/null +++ b/site/react/api/hooks/useEnsAvatar.md @@ -0,0 +1,262 @@ +--- +title: useEnsAvatar +description: Hook for fetching ENS avatar for name. +--- + + + +# useEnsAvatar + +Hook for fetching ENS avatar for name. + +## Import + +```ts +import { useEnsAvatar } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsAvatar`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsAvatarParameters } from 'wagmi' +``` + +--- + +### assetGatewayUrls + +`{ ipfs?: string | undefined; arweave?: string | undefined } | undefined` + +Gateway urls to resolve IPFS and/or Arweave assets. + +::: code-group +```ts [index.ts] +import { getEnsAvatar } from '@wagmi/core' +import { normalize } from 'viem/ens' +import { config } from './config' + +function App() { + const result = useEnsAvatar({ + assetGatewayUrls: { // [!code focus] + ipfs: 'https://cloudflare-ipfs.com', // [!code focus] + }, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Block number to get ENS avatar at. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS avatar at. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + name: normalize('wevm.eth'), + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEnsAvatar } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + chainId: mainnet.id, // [!code focus], + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' +import { config } from './config' // [!code focus] + +function App() { + const result = useEnsAvatar({ + config, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### gatewayUrls + +`string[] | undefined` + +A set of Universal Resolver gateways, used for resolving CCIP-Read requests made through the ENS Universal Resolver Contract. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + gatewayUrls: ['https://cloudflare-ipfs.com'] { // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### name + +`string | undefined` + +Name to get the avatar for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + name: normalize('wevm.eth'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + name: normalize('wevm.eth'), + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { useEnsAvatar } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsAvatar({ + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsAvatarReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getEnsAvatar`](/core/api/actions/getEnsAvatar) diff --git a/site/react/api/hooks/useEnsName.md b/site/react/api/hooks/useEnsName.md new file mode 100644 index 0000000000..71d128b1bf --- /dev/null +++ b/site/react/api/hooks/useEnsName.md @@ -0,0 +1,206 @@ +--- +title: useEnsName +description: Hook for fetching primary ENS name for address. +--- + + + +# useEnsName + +Hook for fetching primary ENS name for address. + +## Import + +```ts +import { useEnsName } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEnsNameParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +Name to get the resolver for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```ts [index.ts] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS name at. + +::: code-group +```ts [index.ts] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + blockNumber: 17829139n, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS name at. + +::: code-group +```ts [index.ts] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEnsName } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEnsName } from 'wagmi' +import { normalize } from 'viem/ens' +import { config } from './config' // [!code focus] + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { useEnsName } from 'wagmi' + +function App() { + const result = useEnsName({ + address: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsNameReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getEnsName`](/core/api/actions/getEnsName) diff --git a/site/react/api/hooks/useEnsResolver.md b/site/react/api/hooks/useEnsResolver.md new file mode 100644 index 0000000000..b66e17fa03 --- /dev/null +++ b/site/react/api/hooks/useEnsResolver.md @@ -0,0 +1,217 @@ +--- +title: useEnsResolver +description: Hook for fetching ENS resolver for name. +--- + + + +# useEnsResolver + +Hook for fetching ENS resolver for name. + +## Import + +```ts +import { useEnsResolver } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsResolver`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsResolverParameters } from 'wagmi' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS resolver at. + +::: code-group +```ts [index.ts] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS resolver at. + +::: code-group +```ts [index.ts] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + name: normalize('wevm.eth'), + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEnsResolver } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' +import { config } from './config' // [!code focus] + +function App() { + const result = useEnsResolver({ + config, // [!code focus] + name: normalize('wevm.eth'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### name + +`string | undefined` + +Name to get the resolver for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```ts [index.ts] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + name: normalize('wevm.eth'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + name: normalize('wevm.eth'), + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Resolver of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { useEnsResolver } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsResolver({ + name: normalize('wevm.eth'), + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsResolverReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getEnsResolver`](/core/api/actions/getEnsResolver) diff --git a/site/react/api/hooks/useEnsText.md b/site/react/api/hooks/useEnsText.md new file mode 100644 index 0000000000..769bae66ce --- /dev/null +++ b/site/react/api/hooks/useEnsText.md @@ -0,0 +1,247 @@ +--- +title: useEnsText +description: Hook for fetching a text record for a specified ENS name and key. +--- + + + +# useEnsText + +Hook for fetching a text record for a specified ENS name and key. + +## Import + +```ts +import { useEnsText } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsText`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsTextParameters } from 'wagmi' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get the text at. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + blockNumber: 17829139n, // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get the text at. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + blockTag: 'latest', // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEnsText } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + chainId: mainnet.id, // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### key + +`string | undefined` + +ENS key to get Text for. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + name: normalize('wevm.eth'), + key: 'com.twitter', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +### name + +`string | undefined` + +Name to get the text for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + name: normalize('wevm.eth'), // [!code focus] + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' +import { config } from './config' // [!code focus] + +function App() { + const result = useEnsText({ + config, // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + scopeKey: 'foo', // [!code focus] + name: normalize('wevm.eth'), + key: 'com.twitter', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Resolver of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```ts [index.ts] +import { useEnsText } from 'wagmi' +import { normalize } from 'viem/ens' + +function App() { + const result = useEnsText({ + name: normalize('wevm.eth'), + key: 'com.twitter', + universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsTextReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getEnsText`](/core/api/actions/getEnsText) diff --git a/site/react/api/hooks/useEstimateFeesPerGas.md b/site/react/api/hooks/useEstimateFeesPerGas.md new file mode 100644 index 0000000000..29b8711673 --- /dev/null +++ b/site/react/api/hooks/useEstimateFeesPerGas.md @@ -0,0 +1,155 @@ +--- +title: useEstimateFeesPerGas +description: Hook for fetching an estimate for the fees per gas (in wei) for a transaction to be likely included in the next block. +--- + + + +# useEstimateFeesPerGas + +Hook for fetching an estimate for the fees per gas (in wei) for a transaction to be likely included in the next block. + +## Import + +```ts +import { useEstimateFeesPerGas } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEstimateFeesPerGas } from 'wagmi' + +function App() { + const result = useEstimateFeesPerGas() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEstimateFeesPerGas } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEstimateFeesPerGas } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useEstimateFeesPerGas({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEstimateFeesPerGas } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useEstimateFeesPerGas({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### formatUnits + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { useEstimateFeesPerGas } from 'wagmi' + +function App() { + const result = useEstimateFeesPerGas({ + formatUnits: 'ether', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEstimateFeesPerGas } from 'wagmi' + +function App() { + const result = useEstimateFeesPerGas({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559'` + +- Defaults to `'eip1559'` + +::: code-group +```ts [index.ts] +import { useEstimateFeesPerGas } from 'wagmi' + +function App() { + const result = useEstimateFeesPerGas({ + type: 'legacy', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEstimateFeesPerGasReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`estimateFeesPerGas`](/core/api/actions/estimateFeesPerGas) diff --git a/site/react/api/hooks/useEstimateGas.md b/site/react/api/hooks/useEstimateGas.md new file mode 100644 index 0000000000..db0707ca1a --- /dev/null +++ b/site/react/api/hooks/useEstimateGas.md @@ -0,0 +1,387 @@ +--- +title: useEstimateGas +description: Hook for estimating the gas necessary to complete a transaction without submitting it to the network. +--- + + + +# useEstimateGas + +Hook for estimating the gas necessary to complete a transaction without submitting it to the network. + +## Import + +```ts +import { useEstimateGas } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEstimateGas } from 'wagmi' + +function App() { + const result = useEstimateGas() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEstimateGasParameters } from 'wagmi' +``` + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when estimating gas. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to target when estimating gas. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { mainnet } from '@wagmi/core/chains' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + chainId: mainnet.id, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to estimate with. If no [`account`](#account) is provided, will use default account from connector. + +::: code-group +```ts [index.ts] +import { getConnections, estimateGas } from '@wagmi/core' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const connections = getConnections(config) + const result = useEstimateGas({ + connector: connections[0]?.connector, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded function data. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + gas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + gasPrice: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther, parseGwei } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + nonce: 123, // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + scopeKey: 'foo', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### to + +`Address | undefined` + +The transaction recipient or contract address. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + type: 'eip1559', // [!code focus] + value: parseEther('0.01'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```ts [index.ts] +import { useEstimateGas } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = useEstimateGas({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEstimateGasReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`estimateGas`](/core/api/actions/estimateGas) diff --git a/site/react/api/hooks/useEstimateMaxPriorityFeePerGas.md b/site/react/api/hooks/useEstimateMaxPriorityFeePerGas.md new file mode 100644 index 0000000000..affb165711 --- /dev/null +++ b/site/react/api/hooks/useEstimateMaxPriorityFeePerGas.md @@ -0,0 +1,116 @@ +--- +title: useEstimateMaxPriorityFeePerGas +description: Hook for fetching an estimate for the max priority fee per gas (in wei) for a transaction to be likely included in the next block. +--- + + + +# useEstimateMaxPriorityFeePerGas + +Hook for fetching an estimate for the fees per gas (in wei) for a transaction to be likely included in the next block. + +## Import + +```ts +import { useEstimateMaxPriorityFeePerGas } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useEstimateMaxPriorityFeePerGas } from 'wagmi' + +function App() { + const result = useEstimateMaxPriorityFeePerGas() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEstimateMaxPriorityFeePerGas } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useEstimateMaxPriorityFeePerGas } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useEstimateMaxPriorityFeePerGas({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useEstimateMaxPriorityFeePerGas } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useEstimateMaxPriorityFeePerGas({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useEstimateMaxPriorityFeePerGas } from 'wagmi' + +function App() { + const result = useEstimateMaxPriorityFeePerGas({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEstimateMaxPriorityFeePerGasReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`estimateMaxPriorityFeePerGas`](/core/api/actions/estimateMaxPriorityFeePerGas) diff --git a/site/react/api/hooks/useFeeHistory.md b/site/react/api/hooks/useFeeHistory.md new file mode 100644 index 0000000000..42bf60d89f --- /dev/null +++ b/site/react/api/hooks/useFeeHistory.md @@ -0,0 +1,210 @@ +--- +title: useFeeHistory +description: Hook for fetching a collection of historical gas information. +--- + + + +# useFeeHistory + +Hook for fetching a collection of historical gas information. + +## Import + +```ts +import { useFeeHistory } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' + +function App() { + const result = useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75] +}) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseFeeHistoryParameters } from 'wagmi' +``` + +### blockCount + +`number | undefined` + +Number of blocks in the requested range. Between 1 and 1024 blocks can be requested in a single query. Less than requested may be returned if not all blocks are available. + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' + +function App() { + const result = useFeeHistory({ + blockCount: 4, // [!code focus] + rewardPercentiles: [25, 75] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### rewardPercentiles + +`number[] | undefined` + +A monotonically increasing list of percentile values to sample from each block's effective priority fees per gas in ascending order, weighted by gas used. + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' + +function App() { + const result = useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75] // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Highest number block of the requested range. + + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' + +function App() { + const result = useFeeHistory({ + blockCount: 4, + blockNumber: 1551231n, // [!code focus] + rewardPercentiles: [25, 75] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag of the highest number block of the requested range. + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' + +function App() { + const result = useFeeHistory({ + blockCount: 4, + blockTag: 'safe', // [!code focus] + rewardPercentiles: [25, 75] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' +import { mainnet } from '@wagmi/core/chains' + +function App() { + const result = useFeeHistory({ + blockCount: 4, + chainId: mainnet.id, // [!code focus] + rewardPercentiles: [25, 75] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75] + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useFeeHistory } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useFeeHistory({ + blockCount: 4, + rewardPercentiles: [25, 75] + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseFeeHistoryReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getFeeHistory`](/core/api/actions/getFeeHistory) diff --git a/site/react/api/hooks/useGasPrice.md b/site/react/api/hooks/useGasPrice.md new file mode 100644 index 0000000000..2dfa13aa19 --- /dev/null +++ b/site/react/api/hooks/useGasPrice.md @@ -0,0 +1,117 @@ +--- +title: useGasPrice +description: Hook for fetching the current price of gas (in wei). +--- + + + +# useGasPrice + +Hook for fetching the current price of gas (in wei). + +## Import + +```ts +import { useGasPrice } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useGasPrice } from 'wagmi' + +function App() { + const result = useGasPrice() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseGasPriceParameters } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useGasPrice } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useGasPrice({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useGasPrice } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useGasPrice({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useGasPrice } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useGasPrice({ + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseGasPriceReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getGasPrice`](/core/api/actions/getGasPrice) diff --git a/site/react/api/hooks/useInfiniteReadContracts.md b/site/react/api/hooks/useInfiniteReadContracts.md new file mode 100644 index 0000000000..bc25df59f8 --- /dev/null +++ b/site/react/api/hooks/useInfiniteReadContracts.md @@ -0,0 +1,358 @@ +--- +title: useInfiniteReadContracts +description: Hook for calling multiple read methods on a contract with "infinite scroll"/"fetch more" support. +--- + + + +# useInfiniteReadContracts + +Hook for calling multiple contract read-only methods with "infinite scrolling"/"fetch more" support. + +## Import + +```ts +import { useInfiniteReadContracts } from 'wagmi' +``` + +## Usage + +The example below shows how to demonstrate how to fetch a set of [mloot](https://etherscan.io/address/0x1dfe7ca09e99d10835bf73044a23b73fc20623df) attributes (chestwear, footwear, and handwear) with "fetch more" support. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +const mlootContractConfig = { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, +} as const + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + const args = [pageParam] as const + return [ + { ...mlootContractConfig, functionName: 'getChest', args }, + { ...mlootContractConfig, functionName: 'getFoot', args }, + { ...mlootContractConfig, functionName: 'getHand', args }, + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +In the above example, we are setting a few things: + +- [`cacheKey`](#cachekey): A unique key to store the data in the cache. +- [`query.initialPageParam`](#initialpageparam): An initial page parameter to use when fetching the first set of contracts. +- [`query.getNextPageParam`](#getnextpageparam): A function that returns the next page parameter to use when fetching the next set of contracts. +- [`contracts`](#contracts): A function that provides `pageParam` (derived from the above) as an argument and expects to return an array of contracts. + +### Paginated Parameters + +We can also leverage properties like `getNextPageParam` with a custom `limit` variable to achieve "pagination" of parameters. For example, we can fetch the first 10 contract functions, then fetch the next 10 contract functions, and so on. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function Example({ limit = 10 }: { limit?: number } = {}) { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + return [...new Array(limit)].map( + (_, i) => + ({ + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, + functionName: 'getHand', + args: [BigInt(pageParam + i)], + }) as const, + ) + }, + query: { + initialPageParam: 1, + getNextPageParam(_lastPage, _allPages, lastPageParam) { + return lastPageParam + limit + }, + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + +## Parameters + +```ts +import { type UseInfiniteReadContractsParameters } from 'wagmi' +``` + +### cacheKey + +`string` + +A unique key to store the data in the cache. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', // [!code hl] + contracts(pageParam) { + // ... + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### contracts + +`(pageParam: {{TPageParam}}) => Contract[]` + +A function that provides `pageParam` (derived from the above) as an argument and expects to return an array of contracts. + +#### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + const args = [pageParam] as const + return [ + // ... + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, // [!code hl] + functionName: 'getChest', + args + }, + // ... + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +#### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + const args = [pageParam] as const + return [ + // ... + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', // [!code hl] + abi, + functionName: 'getChest', + args + }, + // ... + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +#### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + const args = [pageParam] as const + return [ + // ... + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, + functionName: 'getChest', // [!code hl] + args + }, + // ... + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +#### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + return [ + // ... + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, + functionName: 'getChest', + args: [pageParam] // [!code hl] + }, + // ... + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +#### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useInfiniteReadContracts } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useInfiniteReadContracts({ + cacheKey: 'mlootAttributes', + contracts(pageParam) { + const args = [pageParam] as const + return [ + // ... + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi, + functionName: 'getChest', + args, + chainId: 1 // [!code hl] + }, + // ... + ] + } + query: { + initialPageParam: 0, + getNextPageParam: (_lastPage, _allPages, lastPageParam) => { + return lastPageParam + 1 + } + } + }) +} +``` +<<< @/snippets/abi-infinite-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseInfiniteReadContractsReturnType } from 'wagmi' +``` + + \ No newline at end of file diff --git a/site/react/api/hooks/usePrepareTransactionRequest.md b/site/react/api/hooks/usePrepareTransactionRequest.md new file mode 100644 index 0000000000..431fdda482 --- /dev/null +++ b/site/react/api/hooks/usePrepareTransactionRequest.md @@ -0,0 +1,368 @@ +--- +title: usePrepareTransactionRequest +description: Hook for preparing a transaction request for signing by populating a nonce, gas limit, fee values, and a transaction type. +--- + + + +# usePrepareTransactionRequest + +Hook for preparing a transaction request for signing by populating a nonce, gas limit, fee values, and a transaction type. + +## Import + +```ts +import { usePrepareTransactionRequest } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UsePrepareTransactionRequestParameters } from 'wagmi' +``` + +### account + +`Account | Address | undefined` + +The Account to send the transaction from. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### to + +`` `0x${string}` | undefined `` + +The transaction recipient or contract address. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + accessList: [ // [!code focus:6] + { + address: '0x1', + storageKeys: ['0x1'], + }, + ], + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to prepare the transaction request for. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + chainId: mainnet.id, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded args. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### gasPrice + +`bigint | undefined` + +The price (in wei) to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther, parseGwei } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + gasPrice: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas (in wei), inclusive of `maxPriorityFeePerGas`. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther, parseGwei } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + maxFeePerGas: parseGwei('20'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas (in wei). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther, parseGwei } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### nonce + +`number | undefined` + +Unique number identifying this transaction. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + nonce: 5, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### parameters + +`("fees" | "gas" | "nonce" | "type")[] | undefined` + +Parameters to prepare. + +For instance, if `["gas", "nonce"]` is provided, then only the `gas` and `nonce` parameters will be prepared. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + parameters: ['gas', 'nonce'], // [!code focus] + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const result = usePrepareTransactionRequest({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' // [!code focus] + +function App() { + const result = usePrepareTransactionRequest({ + config, // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { usePrepareTransactionRequest } from 'wagmi' +import { parseEther } from 'viem' +import { config } from './config' + +function App() { + const result = usePrepareTransactionRequest({ + scopeKey: 'foo' // [!code focus] + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1'), + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UsePrepareTransactionRequestReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`prepareTransactionRequest`](/core/api/actions/prepareTransactionRequest) diff --git a/site/react/api/hooks/useProof.md b/site/react/api/hooks/useProof.md new file mode 100644 index 0000000000..76adcdca4d --- /dev/null +++ b/site/react/api/hooks/useProof.md @@ -0,0 +1,224 @@ +--- +title: useProof +description: Hook for return the account and storage values of the specified account including the Merkle-proof. +--- + + + +# useProof + +Hook for return the account and storage values of the specified account including the Merkle-proof. + +## Import + +```ts +import { useProof } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' + +function App() { + const result = useProof({ + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseProofParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +The account address to get the proof for. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' + +function App() { + const result = useProof({ + address: '0x4200000000000000000000000000000000000016', // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### storageKeys + +`` `0x${string}`[] | undefined `` + +Array of storage-keys that should be proofed and included. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' + +function App() { + const result = useProof({ + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ // [!code focus:3] + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Proof at a given block number. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' + +function App() { + const result = useProof({ + address: '0x4200000000000000000000000000000000000016', + blockNumber: 42069n, // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Proof at a given block tag. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' + +function App() { + const result = useProof({ + address: '0x4200000000000000000000000000000000000016', + blockTag: 'latest', // [!code focus] + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The ID of chain to get the proof for. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' +import { optimism } from 'wagmi/chains' + +function App() { + const result = useProof({ + chainId: optimism.id, // [!code focus] + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useProof({ + config, // [!code focus] + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useProof } from 'wagmi' +import { config } from './config' + +function App() { + const result = useProof({ + scopeKey: 'foo' // [!code focus] + address: '0x4200000000000000000000000000000000000016', + storageKeys: [ + '0x4a932049252365b3eedbc5190e18949f2ec11f39d3bef2d259764799a1b27d99', + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseProofReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getProof`](/core/api/actions/getProof) diff --git a/site/react/api/hooks/usePublicClient.md b/site/react/api/hooks/usePublicClient.md new file mode 100644 index 0000000000..9f0411055b --- /dev/null +++ b/site/react/api/hooks/usePublicClient.md @@ -0,0 +1,93 @@ +--- +title: usePublicClient +description: Hook for getting Viem `PublicClient` instance. +--- + +# usePublicClient + +Hook for getting Viem [`PublicClient`](https://viem.sh/docs/clients/public.html) instance. + +## Import + +```ts +import { usePublicClient } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { usePublicClient } from 'wagmi' + +function App() { + const client = usePublicClient() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +If you want to optimize bundle size, you should use [`useClient`](/react/api/hooks/useClient) along with Viem's [tree-shakable actions](https://viem.sh/docs/clients/custom.html#tree-shaking) instead. Since Public Client has all public actions attached directly to it. +::: + +## Parameters + +```ts +import { type UsePublicClientParameters } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when getting Viem Public Client. + +::: code-group +```ts [index.ts] +import { usePublicClient } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { config } from './config' + +function App() { + const client = usePublicClient({ + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { usePublicClient } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const client = usePublicClient({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UsePublicClientReturnType } from 'wagmi' +``` + +`PublicClient | undefined` + +Viem [`PublicClient`](https://viem.sh/docs/clients/public.html) instance. + +## Action + +- [`getPublicClient`](/core/api/actions/getPublicClient) +- [`watchPublicClient`](/core/api/actions/watchPublicClient) diff --git a/site/react/api/hooks/useReadContract.md b/site/react/api/hooks/useReadContract.md new file mode 100644 index 0000000000..07be91e010 --- /dev/null +++ b/site/react/api/hooks/useReadContract.md @@ -0,0 +1,406 @@ +--- +title: useReadContract +description: Hook for calling a read-only function on a contract, and returning the response. +--- + + + +# useReadContract + +Hook for calling a **read-only** function on a contract, and returning the response. + +A **read-only** function (constant function) on a Solidity contract is denoted by a pure or view keyword. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas. + +## Import + +```ts +import { useReadContract } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseReadContractParameters } from 'wagmi' +``` + +### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' // [!code focus] + +function App() { + const result = useReadContract({ + abi, // [!code focus] + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### account + +`Account | undefined` + +Account to use when calling the contract (`msg.sender`). + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + functionName: 'totalSupply', + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to call contract at. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + blockNumber: 17829139n, // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to call contract at. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + blockTag: 'safe', // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' // [!code focus] + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'totalSupply', + config, // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', // [!code focus] + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useReadContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseReadContractReturnType } from 'wagmi' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and the return type. See the Wagmi [TypeScript docs](/react/typescript) for more information. + +::: code-group +```ts twoslash [Inline] +import { createConfig, http, useReadContract } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const result = useReadContract({ + abi: [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, + ], + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + // ^? + + + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + // ^? +}) + +result.data +// ^? +``` +```ts twoslash [Const-Asserted] +import { createConfig, http, useReadContract } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const abi = [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, +] as const + +const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + // ^? + + + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + // ^? +}) + +result.data +// ^? +``` +::: + + + +## Action + +- [`readContract`](/core/api/actions/readContract) diff --git a/site/react/api/hooks/useReadContracts.md b/site/react/api/hooks/useReadContracts.md new file mode 100644 index 0000000000..48436c9362 --- /dev/null +++ b/site/react/api/hooks/useReadContracts.md @@ -0,0 +1,394 @@ +--- +title: useReadContracts +description: Hook for calling multiple read methods on a contract. +--- + +# useReadContracts + +Hook for calling multiple read methods on a contract. + +## Import + +```ts +import { useReadContracts } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +const wagmigotchiContract = { + address: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1', + abi: wagmigotchiABI, +} as const +const mlootContract = { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, +} as const + +function App() { + const result = useReadContracts({ + contracts: [ + { + ...wagmigotchiContract, + functionName: 'getAlive', + }, + { + ...wagmigotchiContract, + functionName: 'getBoredom', + }, + { + ...mlootContract, + functionName: 'getChest', + args: [69], + }, + { + ...mlootContract, + functionName: 'getWaist', + args: [69], + }, + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseReadContractsParameters } from 'wagmi' +``` + +### contracts + +`readonly Contract[]` + +Set of contracts to call. + +#### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, // [!code hl] + functionName: 'getChest', + args: [69], + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', // [!code hl] + abi: mlootABI, + functionName: 'getChest', + args: [69], + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', // [!code hl] + args: [69], + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], // [!code hl] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +#### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69], + chainId: 1 // [!code hl] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +### allowFailure + +`boolean` + +Whether or not the Hook should throw if a call reverts. If set to `true` (default), and a call reverts, then `useReadContracts` will fail silently and its error will be logged in the results array. Defaults to `true`. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + allowFailure: false, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + +### batchSize + +`number` + +The maximum size (in bytes) for each calldata chunk. Set to `0` to disable the size limit. Defaults to `1024`. + +> Note: Some RPC Providers limit the amount of calldata (`data`) that can be sent in a single `eth_call` request. It is best to check with your RPC Provider to see if there are any calldata size limits to `eth_call` requests. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + batchSize: 1024, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`number` + +The block number to perform the read against. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + blockNumber: 69420n, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to read against. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + blockTag: 'safe', // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' +import { config } from './config' + +function App() { + const result = useReadContracts({ + config, // [!code hl] + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### multicallAddress + +`Address` + +Address of multicall contract. + +::: code-group +```tsx [index.tsx] +import { useReadContracts } from 'wagmi' + +function App() { + const result = useReadContracts({ + contracts: [ + { + address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df', + abi: mlootABI, + functionName: 'getChest', + args: [69] + }, + // ... + ], + multicallAddress: '0xca11bde05977b3631167028862be2a173976ca11', // [!code hl] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseReadContractsReturnType } from 'wagmi' +``` + + + +## Action + +- [`readContracts`](/core/api/actions/readContracts) diff --git a/site/react/api/hooks/useReconnect.md b/site/react/api/hooks/useReconnect.md new file mode 100644 index 0000000000..7aff4945c9 --- /dev/null +++ b/site/react/api/hooks/useReconnect.md @@ -0,0 +1,111 @@ +--- +title: useReconnect +description: Hook for reconnecting connectors. +--- + + + +# useReconnect + +Hook for reconnecting [connectors](/core/api/connectors). + +## Import + +```ts +import { useReconnect } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useReconnect } from 'wagmi' +import { useEffect } from 'react' + +function App() { + const { reconnect } = useReconnect() + + useEffect(() => { + reconnect() + }, []) +} +``` + +::: + +::: tip +When [`WagmiProvider['reconnectOnMount']`](/react/api/WagmiProvider#reconnectonmount) is `true`, `reconnect` is called automatically on mount. +::: + +## Parameters + +```ts +import { type UseReconnectParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useReconnect } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useReconnect({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseReconnectReturnType } from 'wagmi' +``` + +### connectors + +`readonly Connector[]` + +Globally configured connectors via [`createConfig`](/react/api/createConfig#connectors). + +::: code-group +```tsx [index.tsx] +import { useReconnect } from 'wagmi' +import { mainnet } from 'wagmi/chains' +import { useEffect } from 'react' + +function App() { + const { reconnect, connectors } = useReconnect() + + useEffect(() => { + reconnect({ connectors }) + }, []) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + + + +## Action + +- [`reconnect`](/core/api/actions/reconnect) diff --git a/site/react/api/hooks/useSendCalls.md b/site/react/api/hooks/useSendCalls.md new file mode 100644 index 0000000000..d7020fd8bf --- /dev/null +++ b/site/react/api/hooks/useSendCalls.md @@ -0,0 +1,103 @@ +--- +title: useSendCalls +description: Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. +--- + + + +# useSendCalls + +Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +## Import + +```ts +import { useSendCalls } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSendCalls } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const { sendCalls } = useSendCalls() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSendCallsParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSendCalls } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSendCalls({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSendCallsReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`sendCalls`](/core/api/actions/sendCalls) diff --git a/site/react/api/hooks/useSendTransaction.md b/site/react/api/hooks/useSendTransaction.md new file mode 100644 index 0000000000..c9d4bf441b --- /dev/null +++ b/site/react/api/hooks/useSendTransaction.md @@ -0,0 +1,93 @@ +--- +title: useSendTransaction +description: Hook for creating, signing, and sending transactions to networks. +--- + + + +# useSendTransaction + +Hook for creating, signing, and sending transactions to networks. + +## Import + +```ts +import { useSendTransaction } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSendTransaction } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const { sendTransaction } = useSendTransaction() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSendTransactionParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSendTransaction } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSendTransaction({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSendTransactionReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`sendTransaction`](/core/api/actions/sendTransaction) diff --git a/site/react/api/hooks/useShowCallsStatus.md b/site/react/api/hooks/useShowCallsStatus.md new file mode 100644 index 0000000000..6fb26e9f05 --- /dev/null +++ b/site/react/api/hooks/useShowCallsStatus.md @@ -0,0 +1,96 @@ +--- +title: useShowCallsStatus +description: Action to request for the wallet to show information about a call batch +--- + + + +# useShowCallsStatus + +Action to request for the wallet to show information about a call batch that was sent via `useShowCalls`. + +[Read more.](https://github.com/ethereum/EIPs/blob/1663ea2e7a683285f977eda51c32cec86553f585/EIPS/eip-5792.md#wallet_showcallsstatus) + + + +## Import + +```ts +import { useShowCallsStatus } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useShowCallsStatus } from 'wagmi' +import { parseEther } from 'viem' + +function App() { + const { showCallsStatus } = useShowCallsStatus() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseShowCallsStatusParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useShowCallsStatus } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useShowCallsStatus({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseShowCallsStatusReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`showCallsStatus`](/core/api/actions/showCallsStatus) diff --git a/site/react/api/hooks/useSignMessage.md b/site/react/api/hooks/useSignMessage.md new file mode 100644 index 0000000000..7b0ffb3a4a --- /dev/null +++ b/site/react/api/hooks/useSignMessage.md @@ -0,0 +1,85 @@ +--- +title: useSignMessage +description: Hook for signing messages. +--- + + + +# useSignMessage + +Hook for signing messages. + +## Import + +```ts +import { useSignMessage } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSignMessage } from 'wagmi' + +function App() { + const { signMessage } = useSignMessage() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSignMessageParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSignMessage } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSignMessage({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSignMessageReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`signMessage`](/core/api/actions/signMessage) diff --git a/site/react/api/hooks/useSignTypedData.md b/site/react/api/hooks/useSignTypedData.md new file mode 100644 index 0000000000..140b0e86a3 --- /dev/null +++ b/site/react/api/hooks/useSignTypedData.md @@ -0,0 +1,217 @@ +--- +title: useSignTypedData +description: Hook for signing typed data and calculating an Ethereum-specific EIP-712 signature. +--- + + + +# useSignTypedData + +Hook for signing typed data and calculating an Ethereum-specific [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature. + +## Import + +```ts +import { useSignTypedData } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSignTypedData } from 'wagmi' + + +function App() { + const { signTypedData } = useSignTypedData() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSignTypedDataParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSignTypedData } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSignTypedData({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSignTypedDataReturnType } from 'wagmi' +``` + + + +## Type Inference + +With [`types`](/core/api/actions/signTypedData#types) setup correctly, TypeScript will infer the correct types for [`domain`](/core/api/actions/signTypedData#domain), [`message`](/core/api/actions/signTypedData#message), and [`primaryType`](/core/api/actions/signTypedData#primarytype). See the Wagmi [TypeScript docs](/react/typescript) for more information. + +::: code-group +```ts twoslash [Inline] +import { useSignTypedData } from 'wagmi' +// ---cut--- +const { signTypedData } = useSignTypedData() + +signTypedData({ + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +```ts twoslash [Const-Asserted] +import { useSignTypedData } from 'wagmi' +// ---cut--- +const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const + +const { signTypedData } = useSignTypedData() + +signTypedData({ + types, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +::: + + + +## Action + +- [`signTypedData`](/core/api/actions/signTypedData) diff --git a/site/react/api/hooks/useSimulateContract.md b/site/react/api/hooks/useSimulateContract.md new file mode 100644 index 0000000000..03872f53e5 --- /dev/null +++ b/site/react/api/hooks/useSimulateContract.md @@ -0,0 +1,686 @@ +--- +title: useSimulateContract +description: Hook for simulating/validating a contract interaction. +--- + + + +# useSimulateContract + +Hook for simulating/validating a contract interaction. + +## Import + +```ts +import { useSimulateContract } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Parameters + +```ts +import { type UseSimulateContractParameters } from 'wagmi' +``` + +### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' // [!code focus] + +function App() { + const result = useSimulateContract({ + abi, // [!code focus] + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + accessList: [{ // [!code focus] + address: '0x1', // [!code focus] + storageKeys: ['0x1'], // [!code focus] + }], // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### account + +`Account | undefined` + +Account to use when calling the contract (`msg.sender`). Throws if account is not found on [`connector`](#connector). + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ // [!code focus] + '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus] + 123n, // [!code focus] + ], // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to call contract at. + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + blockNumber: 17829139n, // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to call contract at. + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + blockTag: 'safe', // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + config, // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/react/api/connectors) to simulate transaction with. + +::: code-group +```tsx [index.ts] +import { useConnectorClient, useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + const { data: connector } = useConnectorClient() + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + connector, // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### dataSuffix + +`` `0x${string}` | undefined `` + +Data to append to the end of the calldata. Useful for adding a ["domain" tag](https://opensea.notion.site/opensea/Seaport-Order-Attributions-ec2d69bf455041a5baa490941aad307f). + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + dataSuffix: '0xdeadbeef', // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', // [!code focus] + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gas: parseGwei('20'), // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + gasPrice: parseGwei('20'), // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseGwei } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + maxFeePerGas: parseGwei('20'), + maxPriorityFeePerGas: parseGwei('2'), // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + nonce: 123, // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + type: 'eip1559', // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { parseEther } from 'viem' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + value: parseEther('0.01'), // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.ts] +import { useSimulateContract } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + const result = useSimulateContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'transferFrom', + args: [ + '0xd2135CfB216b74109775236E36d4b433F1DF507B', + '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + 123n, + ], + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSimulateContractReturnType } from 'wagmi' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and [`value`](#value). See the Wagmi [TypeScript docs](/react/typescript) for more information. + + + +## Action + +- [`simulateContract`](/core/api/actions/simulateContract) diff --git a/site/react/api/hooks/useStorageAt.md b/site/react/api/hooks/useStorageAt.md new file mode 100644 index 0000000000..12c524ac1e --- /dev/null +++ b/site/react/api/hooks/useStorageAt.md @@ -0,0 +1,208 @@ +--- +title: useStorageAt +description: Hook for returning the value from a storage slot at a given address. +--- + + + +# useStorageAt + +Hook for for returning the value from a storage slot at a given address. + +## Import + +```ts +import { useStorageAt } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' + +function App() { + const result = useStorageAt({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseStorageAtParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +The contract address. + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' + +function App() { + const result = useStorageAt({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### slot + +`Hex | undefined` + +The storage position (as a hex encoded value). + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' + +function App() { + const result = useStorageAt({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## blockNumber + +`bigint | undefined` + +The block number to check the storage at. + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' + +function App() { + const result = useStorageAt({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockNumber: 16280770n, // [!code focus] + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to check the storage at. + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' + +function App() { + const result = useStorageAt({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + blockTag: 'safe', // [!code focus] + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The chain ID to check the storage at. + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' +import { mainnet } from '@wagmi/core/chains' + +function App() { + const result = useStorageAt({ + chainId: mainnet.id, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useStorageAt({ + config, // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useStorageAt } from 'wagmi' +import { config } from './config' + +function App() { + const result = useStorageAt({ + scopeKey: 'foo' // [!code focus] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + slot: '0x0', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseStorageAtReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getStorageAt`](/core/api/actions/getStorageAt) diff --git a/site/react/api/hooks/useSwitchAccount.md b/site/react/api/hooks/useSwitchAccount.md new file mode 100644 index 0000000000..8dc3395a70 --- /dev/null +++ b/site/react/api/hooks/useSwitchAccount.md @@ -0,0 +1,116 @@ +--- +title: useSwitchAccount +description: Hook for switching the current account. +--- + + + +# useSwitchAccount + +Hook for switching the current account. + +## Import + +```ts +import { useSwitchAccount } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSwitchAccount } from 'wagmi' + +function App() { + const { connectors, switchAccount } = useSwitchAccount() + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSwitchAccountParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSwitchAccount } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSwitchAccount({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSwitchAccountReturnType } from 'wagmi' +``` + +### connectors + +`readonly Connector[]` + +Globally configured and actively connected connectors. Useful for rendering a list of available connectors to switch to. + +::: code-group +```tsx [index.tsx] +import { useSwitchAccount } from 'wagmi' + +function App() { + const { connectors, switchAccount } = useSwitchAccount() + + return ( +
+ {connectors.map((connector) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + + + +## Action + +- [`switchAccount`](/core/api/actions/switchAccount) diff --git a/site/react/api/hooks/useSwitchChain.md b/site/react/api/hooks/useSwitchChain.md new file mode 100644 index 0000000000..3b139bd701 --- /dev/null +++ b/site/react/api/hooks/useSwitchChain.md @@ -0,0 +1,120 @@ +--- +title: useSwitchChain +description: Hook for switching the target chain for a connector or the Wagmi `Config`. +--- + + + +# useSwitchChain + +Hook for switching the target chain for a connector or the Wagmi [`Config`](/react/api/createConfig#config). + +## Import + +```ts +import { useSwitchChain } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useSwitchChain } from 'wagmi' + +function App() { + const { chains, switchChain } = useSwitchChain() + + return ( +
+ {chains.map((chain) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: tip +When connected, `switchChain` will switch the target chain for the connector. When not connected, `switchChain` will switch the target chain for the Wagmi [`Config`](/react/api/createConfig#config). +::: + +## Parameters + +```ts +import { type UseSwitchChainParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useSwitchChain } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useSwitchChain({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSwitchChainReturnType } from 'wagmi' +``` + +### chains + +`readonly [Chain, ...Chain[]]` + +Globally configured chains. Useful for rendering a list of available chains to switch to. + +::: code-group +```tsx [index.tsx] +import { useSwitchChain } from 'wagmi' + +function App() { + const { chains, switchChain } = useSwitchChain() + + return ( +
+ {chains.map((chain) => ( + + ))} +
+ ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + + + +## Action + +- [`switchChain`](/core/api/actions/switchChain) diff --git a/site/react/api/hooks/useToken.md b/site/react/api/hooks/useToken.md new file mode 100644 index 0000000000..cd4c6dbec2 --- /dev/null +++ b/site/react/api/hooks/useToken.md @@ -0,0 +1,162 @@ +--- +title: useToken +description: Hook for fetching token info. +--- + + + +# useToken [deprecated](/react/guides/migrate-from-v1-to-v2#deprecated-usetoken) + +Hook for fetching token info. + +## Import + +```ts +import { useToken } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useToken } from 'wagmi' + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTokenParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +Address to get token for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```tsx [index.tsx] +import { useToken } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useToken } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useToken } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### formatUnits + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```ts [index.ts] +import { useToken } from 'wagmi' + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + formatUnits: 'ether', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```ts [index.ts] +import { useToken } from 'wagmi' + +function App() { + const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseTokenReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getToken`](/core/api/actions/getToken) diff --git a/site/react/api/hooks/useTransaction.md b/site/react/api/hooks/useTransaction.md new file mode 100644 index 0000000000..391f5bf802 --- /dev/null +++ b/site/react/api/hooks/useTransaction.md @@ -0,0 +1,184 @@ +--- +title: useTransaction +description: Hook for fetching transactions given hashes or block identifiers. +--- + + + +# useTransaction + +Hook for fetching transactions given hashes or block identifiers. + +## Import + +```ts +import { useTransaction } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionParameters } from 'wagmi' +``` + +--- + +### blockHash + +`bigint | undefined` + +Block hash to get transaction at (with [`index`](#index)). + +```ts +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + blockHash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] + index: 0, + }) +} +``` + +### blockNumber + +`bigint | undefined` + +Block number to get transaction at (with [`index`](#index)). + +```ts +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + blockNumber: 17829139n, // [!code focus] + index: 0, + }) +} +``` + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get transaction at (with [`index`](#index)). + +```ts +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + blockTag: 'safe', // [!code focus] + index: 0, + }) +} +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +```ts +import { useTransaction } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useTransaction({ + chainId: mainnet.id, // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useTransaction } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useTransaction({ + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### hash + +`` `0x${string}` | undefined `` + +Hash to get transaction. [`enabled`](#enabled) set to `false` if `hash` and [`index`](#index) are `undefined`. + +```ts +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', // [!code focus] + }) +} +``` + +### index + +`number | undefined` + +An index to be used with a block identifier ([hash](#blockhash), [number](#blocknumber), or [tag](#blocktag)). [`enabled`](#enabled) set to `false` if `index` and [`hash`](#hash) are `undefined`. + +```ts +import { useTransaction } from 'wagmi' + +function App() { + const result = useTransaction({ + blockTag: 'safe', + index: 0 // [!code focus] + }) +} +``` + + + +## Return Type + +```ts +import { type UseTransactionReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getTransaction`](/core/api/actions/getTransaction) diff --git a/site/react/api/hooks/useTransactionConfirmations.md b/site/react/api/hooks/useTransactionConfirmations.md new file mode 100644 index 0000000000..6dec7c1319 --- /dev/null +++ b/site/react/api/hooks/useTransactionConfirmations.md @@ -0,0 +1,153 @@ +--- +title: useTransactionConfirmations +description: Hook for fetching the number of blocks passed (confirmations) since the transaction was processed on a block. +--- + + + +# useTransactionConfirmations + +Hook for fetching the number of blocks passed (confirmations) since the transaction was processed on a block. If confirmations is 0, then the Transaction has not been confirmed & processed yet. + +## Import + +```ts +import { useTransactionConfirmations } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useTransactionConfirmations } from 'wagmi' + +function App() { + const result = useTransactionConfirmations({ + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionConfirmationsParameters } from 'wagmi' +``` + +--- + +### hash + +`` `0x${string}` | undefined `` + +The hash of the transaction. + +```ts +import { useTransactionConfirmations } from 'wagmi' + +function App() { + const result = useTransactionConfirmations({ + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', // [!code focus] + }) +} +``` + +### transactionReceipt + +`TransactionReceipt | undefined` + +The transaction receipt. + +```ts +import { useTransactionConfirmations } from 'wagmi' + +function App() { + const result = useTransactionConfirmations({ + transactionReceipt: { ... }, // [!code focus] + }) +} +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +```ts +import { useTransactionConfirmations } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useTransactionConfirmations({ + chainId: mainnet.id, // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useTransactionConfirmations } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useTransactionConfirmations({ + config, // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useBlock } from 'wagmi' +import { config } from './config' + +function App() { + const result = useTransactionConfirmations({ + scopeKey: 'foo' // [!code focus] + hash: '0x0fa64daeae54e207aa98613e308c2ba8abfe274f75507e741508cc4db82c8cb5', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseTransactionConfirmationsReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getTransactionConfirmations`](/core/api/actions/getTransactionConfirmations) diff --git a/site/react/api/hooks/useTransactionCount.md b/site/react/api/hooks/useTransactionCount.md new file mode 100644 index 0000000000..142453af4b --- /dev/null +++ b/site/react/api/hooks/useTransactionCount.md @@ -0,0 +1,185 @@ +--- +title: useTransactionCount +description: Hook for fetching the number of transactions an Account has broadcast / sent. +--- + + + +# useTransactionCount + +Hook for fetching the number of transactions an Account has sent. + +## Import + +```ts +import { useTransactionCount } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useTransactionCount } from 'wagmi' + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionCountParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +Address to get the transaction count for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```tsx [index.tsx] +import { useTransactionCount } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get the transaction count at. + +::: code-group +```ts [index.ts] +import { useTransactionCount } from 'wagmi' + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockNumber: 17829139n, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get the transaction count at. + +::: code-group +```ts [index.ts] +import { useTransactionCount } from 'wagmi' + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + blockTag: 'latest', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useTransactionCount } from 'wagmi' +import { mainnet } from 'wagmi/chains' // [!code focus] + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + chainId: mainnet.id, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useTransactionCount } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useTransactionCount } from 'wagmi' + +function App() { + const result = useTransactionCount({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseTransactionCountReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getTransactionCount`](/core/api/actions/getTransactionCount) diff --git a/site/react/api/hooks/useTransactionReceipt.md b/site/react/api/hooks/useTransactionReceipt.md new file mode 100644 index 0000000000..2f909ee891 --- /dev/null +++ b/site/react/api/hooks/useTransactionReceipt.md @@ -0,0 +1,142 @@ +--- +title: useTransactionReceipt +description: Hook for return the Transaction Receipt given a Transaction hash. +--- + + + +# useTransactionReceipt + +Hook for return the [Transaction Receipt](https://viem.sh/docs/glossary/terms.html#transaction-receipt) given a [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + +## Import + +```ts +import { useTransactionReceipt } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useTransactionReceipt } from 'wagmi' + +function App() { + const result = useTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionReceiptParameters } from 'wagmi' +``` + +### hash + +`` `0x${string}` | undefined `` + +A transaction hash. + +::: code-group +```tsx [index.tsx] +import { useTransactionReceipt } from 'wagmi' + +function App() { + const result = useTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The ID of chain to return the transaction receipt from. + +::: code-group +```tsx [index.tsx] +import { useTransactionReceipt } from 'wagmi' +import { mainnet } from 'wagmi/chains' + + +function App() { + const result = useTransactionReceipt({ + chainId: mainnet.id, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useTransactionReceipt } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useTransactionReceipt({ + config, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useTransactionReceipt } from 'wagmi' +import { config } from './config' + +function App() { + const result = useTransactionReceipt({ + scopeKey: 'foo' // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseTransactionReceiptReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getTransactionReceipt`](/core/api/actions/getTransactionReceipt) diff --git a/site/react/api/hooks/useVerifyMessage.md b/site/react/api/hooks/useVerifyMessage.md new file mode 100644 index 0000000000..e7750d79f3 --- /dev/null +++ b/site/react/api/hooks/useVerifyMessage.md @@ -0,0 +1,257 @@ +--- +title: useVerifyMessage +description: Hook for verify that a message was signed by the provided address. +--- + + + +# useVerifyMessage + +Hook for verify that a message was signed by the provided address. It supports verifying messages that were signed by either a Smart Contract Account or Externally Owned Account. + +## Import + +```ts +import { useVerifyMessage } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' + +function App() { + const result = useVerifyMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseVerifyMessageParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +The Ethereum address that signed the original message. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' + +function App() { + const result = useVerifyMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### message + +`string | { raw: Hex | ByteArray } | undefined` + +The message to be verified. + +By default, wagmi verifies the UTF-8 representation of the message. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' + +function App() { + const result = useVerifyMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: tip +By default, viem signs the UTF-8 representation of the message. To sign the data representation of the message, you can use the `raw` attribute. + +```ts +import { useVerifyMessage } from 'wagmi' + +function App() { + const result = useVerifyMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: { raw: '0x68656c6c6f20776f726c64' } // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +::: + +### signature + +`Hex | ByteArray | undefined` + +The signature that was generated by signing the message with the address's signer. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' + +function App() { + const result = useVerifyMessage({ + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The ID of chain to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useVerifyMessage({ + chainId: mainnet.id, // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useVerifyMessage({ + blockNumber: 12345678n, // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Only used when verifying a message that was signed by a Smart Contract Account. The block tag to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useVerifyMessage({ + blockTag: 'pending', // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useVerifyMessage({ + config, // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useVerifyMessage } from 'wagmi' +import { config } from './config' + +function App() { + const result = useVerifyMessage({ + scopeKey: 'foo' // [!code focus] + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + message: 'hello world', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseVerifyMessageReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`verifyMessage`](/core/api/actions/verifyMessage) diff --git a/site/react/api/hooks/useVerifyTypedData.md b/site/react/api/hooks/useVerifyTypedData.md new file mode 100644 index 0000000000..4d9f8bc24e --- /dev/null +++ b/site/react/api/hooks/useVerifyTypedData.md @@ -0,0 +1,724 @@ +--- +title: useVerifyTypedData +description: Hook for verify that a typed data was signed by the provided address. +--- + + + +# useVerifyTypedData + +Hook for verify that a typed data was signed by the provided address. It supports verifying typed data that were signed by either a Smart Contract Account or Externally Owned Account. + +## Import + +```ts +import { useVerifyTypedData } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseVerifyTypedDataParameters } from 'wagmi' +``` + +### address + +`Address | undefined` + +The Ethereum address that signed the original typed data. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### domain + +`TypedDataDomain | undefined` + +The typed data domain. + +::: code-group +```tsx [index.tsx] +import { types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain: { // [!code focus:6] + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### types + +The type definitions for the typed data. + +::: code-group +```tsx [index.tsx] +import { domain } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types: { // [!code focus:11] + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### primaryType + +`string | undefined` + +The primary `type` to extract from types and use in `value`. + +::: code-group +```tsx [index.tsx] +import { domain } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ // [!code focus:5] + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', // [!code focus] + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### message + +Type inferred from `types` & `primaryType`. + +The message to be verified. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types, + message: { // [!code focus:11] + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### signature + +`Hex | ByteArray | undefined` + +The signature that was generated by signing the typed data with the address's signer. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', // [!code focus] + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The ID of chain to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useVerifyTypedData({ + chainId: mainnet.id, // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The block number to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + blockNumber: 12345678n, // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Only used when verifying a typed data that was signed by a Smart Contract Account. The block tag to check if the contract was already deployed. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + blockTag: 'latest', // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useVerifyTypedData({ + config, // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { domain, types } from './data' +import { useVerifyTypedData } from 'wagmi' + +function App() { + const result = useVerifyTypedData({ + scopeKey: 'foo' // [!code focus] + domain, + types, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + primaryType: 'Mail', + address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + signature: '0x66edc32e2ab001213321ab7d959a2207fcef5190cc9abb6da5b0d2a8a9af2d4d2b0700e2c317c4106f337fd934fbbb0bf62efc8811a78603b33a8265d3b8f8cb1c', + }) +} +``` +```ts [data.ts] +// All properties on a domain are optional +export const domain = { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', +} as const + +// The named list of all type definitions +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseVerifyTypedDataReturnType } from 'wagmi' +``` + + + +## Type Inference + +With [`types`](#types) setup correctly, TypeScript will infer the correct types for [`domain`](#domain), [`message`](#message), and [`primaryType`](#primarytype). See the Wagmi [TypeScript docs](/react/typescript) for more information. + + + +## Action + +- [`verifyTypedData`](/core/api/actions/verifyTypedData) diff --git a/site/react/api/hooks/useWaitForCallsStatus.md b/site/react/api/hooks/useWaitForCallsStatus.md new file mode 100644 index 0000000000..39e520de52 --- /dev/null +++ b/site/react/api/hooks/useWaitForCallsStatus.md @@ -0,0 +1,181 @@ +--- +title: useWaitForCallsStatus +description: Waits for a call bundle to be confirmed & included on a block. +--- + + + +# useWaitForCallsStatus + +Waits for a call bundle to be confirmed & included on a block before returning the status & receipts. + + + +## Import + +```ts +import { useWaitForCallsStatus } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWaitForCallsStatus } from 'wagmi' + +function App() { + const result = useWaitForCallsStatus({ + id: '0x...', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWaitForCallsStatusParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWaitForCallsStatus } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWaitForCallsStatus({ + config, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to get call statuses with. + +::: code-group +```tsx [index.tsx] +import { useWaitForCallsStatus, useConnections } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const connections = useConnections() + const result = useWaitForCallsStatus({ + connector: connections[0]?.connector, // [!code focus] + id: '0x...', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### id + +`string` + +Identifier of the call batch. + +::: code-group +```ts [index.ts] +import { useWaitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await useWaitForCallsStatus({ + id: '0x1234567890abcdef', // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +Polling interval in milliseconds. + +::: code-group +```ts [index.ts] +import { useWaitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await useWaitForCallsStatus({ + id: '0x1234567890abcdef', + pollingInterval: 1_000, // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```tsx [index.tsx] +import { useWaitForCallsStatus } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWaitForCallsStatus({ + id: '0x...', + scopeKey: 'foo', // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### timeout + +`number | undefined` + +Timeout in milliseconds. + +::: code-group +```ts [index.ts] +import { useWaitForCallsStatus } from '@wagmi/core' +import { config } from './config' + +const status = await useWaitForCallsStatus({ + id: '0x1234567890abcdef', + timeout: 10_000, // [!code focus] +}) +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWaitForCallsStatusReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`waitForCallsStatus`](https://viem.sh/experimental/eip5792/waitForCallsStatus) \ No newline at end of file diff --git a/site/react/api/hooks/useWaitForTransactionReceipt.md b/site/react/api/hooks/useWaitForTransactionReceipt.md new file mode 100644 index 0000000000..78dbe7bc54 --- /dev/null +++ b/site/react/api/hooks/useWaitForTransactionReceipt.md @@ -0,0 +1,168 @@ +--- +title: useWaitForTransactionReceipt +description: Hook that waits for the transaction to be included on a block, and then returns the transaction receipt. If the transaction reverts, then the action will throw an error. Replacement detection (e.g. sped up transactions) is also supported. +--- + + + +# useWaitForTransactionReceipt + +Hook that waits for the transaction to be included on a block, and then returns the transaction receipt. If the transaction reverts, then the action will throw an error. Replacement detection (e.g. sped up transactions) is also supported. + +## Import + +```ts +import { useWaitForTransactionReceipt } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWaitForTransactionReceipt } from 'wagmi' + +function App() { + const result = useWaitForTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWaitForTransactionReceiptParameters } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +```ts [index.ts] +import { useWaitForTransactionReceipt } from 'wagmi' +import { mainnet } from 'wagmi/chains' + +function App() { + const result = useWaitForTransactionReceipt({ + chainId: mainnet.id, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWaitForTransactionReceipt } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWaitForTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### confirmations + +`number | undefined` + +The number of confirmations (blocks that have passed) to wait before resolving. + +```ts [index.ts] +import { useWaitForTransactionReceipt } from 'wagmi' + +function App() { + const result = useWaitForTransactionReceipt({ + confirmations: 2, // [!code focus] + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + }) +} +``` + +### onReplaced + +` +(({ reason: 'replaced' | 'repriced' | 'cancelled'; replacedTransaction: Transaction; transaction: Transaction; transactionReceipt: TransactionReceipt }) => void) | undefined +` + +Optional callback to emit if the transaction has been replaced. + +```ts [index.ts] +import { useWaitForTransactionReceipt } from 'wagmi' + +function App() { + const result = useWaitForTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + onReplaced: replacement => console.log(replacement), // [!code focus] + }) +} +``` + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/react/api/createConfig#pollinginterval). + +```ts [index.ts] +import { useWaitForTransactionReceipt } from 'wagmi' + +function App() { + const result = useWaitForTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', + pollingInterval: 1_000, // [!code focus] + }) +} +``` + +### hash + +`` `0x${string}` | undefined `` + +The transaction hash to wait for. [`enabled`](#enabled) set to `false` if `hash` is `undefined`. + +```ts [index.ts] +import { useWaitForTransactionReceipt } from 'wagmi' + +function App() { + const result = useWaitForTransactionReceipt({ + hash: '0x4ca7ee652d57678f26e887c149ab0735f41de37bcad58c9f6d3ed5824f15b74d', // [!code focus] + }) +} +``` + + + +## Return Type + +```ts +import { type UseWaitForTransactionReceiptReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`waitForTransactionReceipt`](/core/api/actions/waitForTransactionReceipt) diff --git a/site/react/api/hooks/useWalletClient.md b/site/react/api/hooks/useWalletClient.md new file mode 100644 index 0000000000..e6ee792c25 --- /dev/null +++ b/site/react/api/hooks/useWalletClient.md @@ -0,0 +1,132 @@ +--- +title: useWalletClient +description: Hook for getting a Viem `WalletClient` object for the current or provided connector. +--- + + + +# useWalletClient + +Hook for getting a Viem [`WalletClient`](https://viem.sh/docs/clients/wallet.html) object for the current or provided connector. + +## Import + +```ts +import { useWalletClient } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWalletClient } from 'wagmi' + +function App() { + const result = useWalletClient() +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +::: warning +If you want to optimize bundle size, you should use [`useConnectorClient`](/react/api/hooks/useConnectorClient) along with Viem's [tree-shakable actions](https://viem.sh/docs/clients/custom.html#tree-shaking) instead. Since Wallet Client has all wallet actions attached directly to it. +::: + +## Parameters + +```ts +import { type UseWalletClientParameters } from 'wagmi' +``` + +### account + +`Address | Account | undefined` + +Account to use with client. Throws if account is not found on [`connector`](#connector). + +```ts +import { useWalletClient } from 'wagmi' + +function App() { + const result = useWalletClient({ + account: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +} +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use with client. + +```ts +import { useWalletClient } from 'wagmi' + +function App() { + const result = useWalletClient({ + chainId: mainnet.id, // [!code focus] + }) +} +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWalletClient } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWalletClient({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to get client for. +- Defaults to current connector. + +```ts +import { useConnections, useWalletClient } from 'wagmi' + +function App() { + const connections = useConnections(config) + const result = useWalletClient({ + connector: connections[0]?.connector, // [!code focus] + }) +} +``` + + + +## Return Type + +```ts +import { type UseWalletClientReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`getWalletClient`](/core/api/actions/getWalletClient) diff --git a/site/react/api/hooks/useWatchAsset.md b/site/react/api/hooks/useWatchAsset.md new file mode 100644 index 0000000000..03f756d569 --- /dev/null +++ b/site/react/api/hooks/useWatchAsset.md @@ -0,0 +1,94 @@ +--- +title: useWatchAsset +description: Hook for requesting user tracks the token in their wallet. Returns a boolean indicating if the token was successfully added. +--- + + + +# useWatchAsset + +Hook for requesting user tracks the token in their wallet. Returns a boolean indicating if the token was successfully added. + +## Import + +```ts +import { useWatchAsset } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWatchAsset } from 'wagmi' + +function App() { + const { watchAsset } = useWatchAsset() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchAssetParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWatchAsset } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWatchAsset({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWatchAssetReturnType } from 'wagmi' +``` + + + + + +## Action + +- [`watchAsset`](/core/api/actions/watchAsset) diff --git a/site/react/api/hooks/useWatchBlockNumber.md b/site/react/api/hooks/useWatchBlockNumber.md new file mode 100644 index 0000000000..925dddfc1a --- /dev/null +++ b/site/react/api/hooks/useWatchBlockNumber.md @@ -0,0 +1,276 @@ +--- +title: useWatchBlockNumber +description: Hook that watches for block number changes. +--- + +# useWatchBlockNumber + +Hook that watches for block number changes. + +## Import + +```ts +import { useWatchBlockNumber } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + console.log('Block number changed!', blockNumber) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchBlockNumberParameters } from 'wagmi' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to watch blocks at. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + chainId: 1, // [!code focus] + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' +import { config } from './config' + +function App() { + useWatchBlockNumber({ + config, // [!code focus] + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitMissed + +`boolean` + +Whether or not to emit missed blocks to the callback. Defaults to `false`. + +Missed blocks may occur in instances where internet connection is lost, or the block time is lesser than the polling interval of the client. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + emitMissed: true, // [!code focus] + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitOnBegin + +`boolean` + +Whether or not to emit the block to the callback when the subscription opens. Defaults to `false`. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + emitOnBegin: true, // [!code focus] + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### enabled + +`boolean` + +Whether or not to watch for blocks. Defaults to `true`. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + enabled: false, // [!code focus] + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onBlockNumber + +`(block: Block, prevblock: Block | undefined) => void` + +Callback for when block changes. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { // [!code focus] + console.log('New block number', blockNumber) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + }, + onError(error) { // [!code focus] + console.error('Block error', error) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + } + poll: true, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + } + pollingInterval: 1_000, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { useWatchBlockNumber } from 'wagmi' + +function App() { + useWatchBlockNumber({ + onBlockNumber(blockNumber) { + console.log('New block number', blockNumber) + } + syncConnectedChain: false, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseWatchBlockNumberReturnType } from 'wagmi' +``` + +Function for cleaning up watcher. + +## Action + +- [`watchBlockNumber`](/core/api/actions/watchBlockNumber) diff --git a/site/react/api/hooks/useWatchBlocks.md b/site/react/api/hooks/useWatchBlocks.md new file mode 100644 index 0000000000..ec2b17326c --- /dev/null +++ b/site/react/api/hooks/useWatchBlocks.md @@ -0,0 +1,318 @@ +--- +title: useWatchBlocks +description: Hook that watches for block changes. +--- + +# useWatchBlocks + +Hook that watches for block changes. + +## Import + +```ts +import { useWatchBlocks } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchBlocksParameters } from 'wagmi' +``` + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to watch blocks at. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + blockTag: 'latest', // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to watch blocks at. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + chainId: 1, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' +import { config } from './config' + +function App() { + useWatchBlocks({ + config, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitMissed + +`boolean` + +Whether or not to emit missed blocks to the callback. Defaults to `false`. + +Missed blocks may occur in instances where internet connection is lost, or the block time is lesser than the polling interval of the client. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + emitMissed: true, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### emitOnBegin + +`boolean` + +Whether or not to emit the block to the callback when the subscription opens. Defaults to `false`. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + emitOnBegin: true, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### enabled + +`boolean` + +Whether or not to watch for blocks. Defaults to `true`. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + enabled: false, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### includeTransactions + +`boolean` + +Whether or not to unwrap transactions as objects (instead of hashes) in blocks. Defaults to `false`. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + includeTransactions: true, // [!code focus] + onBlock(block) { + console.log('New block', block.number) + }, + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onBlock + +`(block: Block, prevblock: Block | undefined) => void` + +Callback for when block changes. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { // [!code focus] + console.log('New block', block.number) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { + console.log('New block', block.number) + }, + onError(error) { // [!code focus] + console.error('Block error', error) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { + console.log('New block', block.number) + } + poll: true, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { + console.log('New block', block.number) + } + pollingInterval: 1_000, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```ts [index.ts] +import { useWatchBlocks } from 'wagmi' + +function App() { + useWatchBlocks({ + onBlock(block) { + console.log('New block', block.number) + } + syncConnectedChain: false, // [!code focus] + }) +} +``` +<<< @/snippets/core/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseWatchBlocksReturnType } from 'wagmi' +``` + +## Action + +- [`watchBlocks`](/core/api/actions/watchBlocks) diff --git a/site/react/api/hooks/useWatchContractEvent.md b/site/react/api/hooks/useWatchContractEvent.md new file mode 100644 index 0000000000..1bdcfb2622 --- /dev/null +++ b/site/react/api/hooks/useWatchContractEvent.md @@ -0,0 +1,413 @@ +--- +title: useWatchContractEvent +description: Hook that watches and returns emitted contract event logs. +--- + +# useWatchContractEvent + +Hook that watches and returns emitted contract event logs. + +## Import + +```ts +import { useWatchContractEvent } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchContractEventParameters } from 'wagmi' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, // [!code focus] + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // [!code focus] + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### args + +`object | readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`eventName`](#eventname). + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + args: { // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B' // [!code focus] + } // [!code focus] + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### batch + +`boolean | undefined` + +- Whether or not the events should be batched on each invocation. +- Defaults to `true`. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + batch: false, // [!code focus] + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + chainId: 1, // [!code focus] + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + config, // [!code focus] + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### eventName + +`string` + +- Event to listen for the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' +import { config } from './config' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', // [!code focus] + onLogs(logs) { + console.log('New logs!', logs) + }, + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block number. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + onError(error) { // [!code focus] + console.log('Error', error) // [!code focus] + } // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### onLogs + +`(logs: Log[], prevLogs: Log[] | undefined) => void` + +Callback for when logs changes. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { // [!code focus] + console.log('New logs!', logs) // [!code focus] + } // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + poll: true // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + pollingInterval: 1_000 // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### strict + +`boolean | undefined` + +- Defaults to `false`. + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + strict: true // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```tsx [index.tsx] +import { useWatchContractEvent } from 'wagmi' +import { abi } from './abi' + +function App() { + useWatchContractEvent({ + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + abi, + eventName: 'Transfer', + onLogs(logs) { + console.log('New logs!', logs) + }, + syncConnectedChain: true // [!code focus] + }) +} +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseWatchContractEventReturnType } from 'wagmi' +``` + +Hook returns `void` + +## Action + +- [`watchContractEvent`](/core/api/actions/watchContractEvent) diff --git a/site/react/api/hooks/useWatchPendingTransactions.md b/site/react/api/hooks/useWatchPendingTransactions.md new file mode 100644 index 0000000000..0bfbb8136d --- /dev/null +++ b/site/react/api/hooks/useWatchPendingTransactions.md @@ -0,0 +1,229 @@ +--- +title: useWatchPendingTransactions +description: Hook that watches and returns pending transaction hashes. +--- + +# useWatchPendingTransactions + +Hook that watches and returns pending transaction hashes. + +## Import + +```ts +import { useWatchPendingTransactions } from 'wagmi' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchPendingTransactionsParameters } from 'wagmi' +``` + +### batch + +`boolean | undefined` + +- Whether or not the transactions should be batched on each invocation. +- Defaults to `true`. + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + batch: true // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + chainId: 1 // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' +import { config } from './config' + +function App() { + useWatchPendingTransactions({ + config // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from watching pending transactions. + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onError(error) { // [!code focus] + console.log('Error', error) // [!code focus] + }, // [!code focus] + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### onTransactions + +`(transactions: Hash[], prevTransactions: Hash[] | undefined) => void` + +Callback when new incoming pending transactions are detected. + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onTransactions(transactions) { // [!code focus] + console.log('New transactions!', transactions) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new pending transactions instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + poll: true, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + pollingInterval: 1_000, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +::: code-group +```tsx [index.tsx] +import { useWatchPendingTransactions } from 'wagmi' + +function App() { + useWatchPendingTransactions({ + onTransactions(transactions) { + console.log('New transactions!', transactions) + }, + syncConnectedChain: false, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseWatchPendingTransactionsReturnType } from 'wagmi' +``` + +## Action + +- [`watchPendingTransactions`](/core/api/actions/watchPendingTransactions) diff --git a/site/react/api/hooks/useWriteContract.md b/site/react/api/hooks/useWriteContract.md new file mode 100644 index 0000000000..bc575ce88b --- /dev/null +++ b/site/react/api/hooks/useWriteContract.md @@ -0,0 +1,116 @@ +--- +title: useWriteContract +description: Action for executing a write function on a contract. +--- + + + +# useWriteContract + +Action for executing a write function on a contract. + +A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state. + +## Import + +```ts +import { useWriteContract } from 'wagmi' +``` + +## Usage + +::: code-group + +```tsx [index.tsx] +import { useWriteContract } from 'wagmi' +import { abi } from './abi' + +function App() { + const { writeContract } = useWriteContract() + + return ( + + ) +} +``` + +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/react/config.ts[config.ts] +::: + + + + + +## Parameters + +```ts +import { type UseWriteContractParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group + +```tsx [index.tsx] +import { useWriteContract } from 'wagmi' +import { config } from './config' // [!code focus] + +function App() { + const result = useWriteContract({ + config, // [!code focus] + }) +} +``` + +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWriteContractReturnType } from 'wagmi' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/react/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](/core/api/actions/writeContract#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](/core/api/actions/writeContract#functionname), [`args`](/core/api/actions/writeContract#args), and the [`value`](/core/api/actions/writeContract##value). See the Wagmi [TypeScript docs](/react/typescript) for more information. + + + +## Action + +- [`writeContract`](/core/api/actions/writeContract) diff --git a/site/react/api/hooks/useWriteContracts.md b/site/react/api/hooks/useWriteContracts.md new file mode 100644 index 0000000000..0e8e94aa79 --- /dev/null +++ b/site/react/api/hooks/useWriteContracts.md @@ -0,0 +1,119 @@ +--- +title: useWriteContracts +description: Hook that requests for the wallet to sign and broadcast a batch of calls (transactions) to the network. +--- + + + +# useWriteContracts + +Hook that requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. + +[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) + +## Import + +```ts +import { useWriteContracts } from 'wagmi/experimental' +``` + +## Usage + +::: code-group +```tsx [index.tsx] +import { useWriteContracts } from 'wagmi/experimental' +import { parseAbi } from 'viem' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +function App() { + const { writeContracts } = useWriteContracts() + + return ( + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWriteContractsParameters } from 'wagmi/experimental' +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```tsx [index.tsx] +import { useWriteContracts } from 'wagmi/experimental' +import { config } from './config' // [!code focus] + +function App() { + const result = useWriteContracts({ + config, // [!code focus] + }) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWriteContractsReturnType } from 'wagmi/experimental' +``` + + + + + +## Action + +- [`writeContracts`](/core/api/actions/writeContracts) diff --git a/site/react/api/transports.md b/site/react/api/transports.md new file mode 100644 index 0000000000..5620b3c115 --- /dev/null +++ b/site/react/api/transports.md @@ -0,0 +1,28 @@ + + +# Transports + +[`createConfig`](/react/api/createConfig) can be instantiated with a set of Transports for each chain. A Transport is the intermediary layer that is responsible for executing outgoing JSON-RPC requests to the RPC Provider (e.g. Alchemy, Infura, etc). + +## Import + +```ts +import { http } from 'wagmi' +``` + +## Built-In Transports + +Available via the `'wagmi'` entrypoint. + + diff --git a/site/react/api/transports/custom.md b/site/react/api/transports/custom.md new file mode 100644 index 0000000000..d2ba5de554 --- /dev/null +++ b/site/react/api/transports/custom.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/transports/fallback.md b/site/react/api/transports/fallback.md new file mode 100644 index 0000000000..cffcd08f05 --- /dev/null +++ b/site/react/api/transports/fallback.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/transports/http.md b/site/react/api/transports/http.md new file mode 100644 index 0000000000..31d80d8a8d --- /dev/null +++ b/site/react/api/transports/http.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/transports/unstable_connector.md b/site/react/api/transports/unstable_connector.md new file mode 100644 index 0000000000..008b9aa3d4 --- /dev/null +++ b/site/react/api/transports/unstable_connector.md @@ -0,0 +1,6 @@ + + + diff --git a/site/react/api/transports/webSocket.md b/site/react/api/transports/webSocket.md new file mode 100644 index 0000000000..6eacdaabed --- /dev/null +++ b/site/react/api/transports/webSocket.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/utilities/cookieToInitialState.md b/site/react/api/utilities/cookieToInitialState.md new file mode 100644 index 0000000000..98363c60e6 --- /dev/null +++ b/site/react/api/utilities/cookieToInitialState.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/utilities/deserialize.md b/site/react/api/utilities/deserialize.md new file mode 100644 index 0000000000..24e9aa22c4 --- /dev/null +++ b/site/react/api/utilities/deserialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/utilities/normalizeChainId.md b/site/react/api/utilities/normalizeChainId.md new file mode 100644 index 0000000000..deceae7a4e --- /dev/null +++ b/site/react/api/utilities/normalizeChainId.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/api/utilities/serialize.md b/site/react/api/utilities/serialize.md new file mode 100644 index 0000000000..9ff2ff3225 --- /dev/null +++ b/site/react/api/utilities/serialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/react/comparisons.md b/site/react/comparisons.md new file mode 100644 index 0000000000..8c614ed552 --- /dev/null +++ b/site/react/comparisons.md @@ -0,0 +1,90 @@ +# Comparison + +There are multiple options when it comes to React libraries for Ethereum that help manage wallet connections, provide utility methods/hooks, etc. + +::: tip +Comparisons strive to be as accurate and as unbiased as possible. If you use any of these libraries and feel the information could be improved, feel free to suggest changes. +::: + +## Overview + +| | [wagmi](https://github.com/wagmi-dev/wagmi) | [web3-react](https://github.com/NoahZinsmeister/web3-react) | [useDApp](https://github.com/EthWorks/useDApp) | +| -------------------- | :---------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------- | +| GitHub Stars | ![wagmi star count](https://img.shields.io/github/stars/wagmi-dev/wagmi?colorB=27292E&label=) | ![web3-react star count](https://img.shields.io/github/stars/Uniswap/web3-react?colorB=27292E&label=) | ![useDApp star count](https://img.shields.io/github/stars/EthWorks/useDApp?colorB=27292E&label=) | +| Open Issues | ![wagmi issue count](https://img.shields.io/github/issues/wagmi-dev/wagmi?colorB=27292E&label=) | ![web3-react issue count](https://img.shields.io/github/issues/Uniswap/web3-react?colorB=27292E&label=) | ![useDApp issue count](https://img.shields.io/github/issues/EthWorks/useDApp?colorB=27292E&label=) | +| Downloads | ![wagmi downloads](https://img.shields.io/npm/dw/wagmi?colorB=27292E&label=) | ![web3-react downloads](https://img.shields.io/npm/dw/@web3-react/core?colorB=27292E&label=) | ![useDApp downloads](https://img.shields.io/npm/dw/@usedapp/core?colorB=27292E&label=) | +| License | ![wagmi license](https://img.shields.io/github/license/wagmi-dev/wagmi?colorB=27292E&label=) | ![web3-react license](https://img.shields.io/github/license/Uniswap/web3-react?colorB=27292E&label=) | ![useDApp license](https://img.shields.io/github/license/EthWorks/useDApp?colorB=27292E&label=) | +| Their Comparison | – | none | none | +| Supported Frameworks | React, Vanilla JS | React | React | +| Documentation | ✅ | 🛑 | ✅ | +| TypeScript | ✅ | 🔶 | 🔶 | +| EIP-6963 Support | ✅ | 🔴 | 🔴 | +| Test Suite | ✅ | 🔶 | 🔶 | +| Examples | ✅ | 🔶 | ✅ | + +::: details Comparison Key + +1. Documentation + - Comprehensive documentation for all library features ✅ + - No documentation 🔴 +2. Typescript + - Infer types from ABIs, EIP-712 Typed Data, etc. ✅ + - Can add types with explicit generics, type annotations, etc. 🔶 +3. Test Suite + - Runs against forked Ethereum network(s) ✅ + - Mocking functionality (i.e. RPC calls) is 🔶 +4. EIP-6963 Support + - Fully compatible with EIP-6963 ✅ + - Not compatible with EIP-6963 🔴 +5. Examples + - Has multiple examples ✅ + - Has single example 🔶 +::: + +## [Wagmi](https://github.com/wagmi-dev/wagmi) + +### Pros + +- 20+ hooks for working with wallets, ENS, contracts, transactions, signing, etc. +- Built-in wallet connectors for injected providers (EIP-6963 support), WalletConnect, MetaMask, Coinbase Wallet +- Caching, request deduplication, and persistence powered by TanStack Query +- Auto-refresh data on wallet, block, and network changes +- Multicall support +- Test suite running against forked Ethereum networks +- TypeScript ready (infer types from ABIs and EIP-712 Typed Data) +- Extensive documentation and examples +- Used by Coinbase, Stripe, Shopify, Uniswap, Optimism, ENS, Sushi, and [many more](https://github.com/wagmi-dev/wagmi/discussions/201) +- MIT License + +### Cons + +- Not as many built-in connectors as `web3-react` + +## [web3-react](https://github.com/Uniswap/web3-react) + +### Pros + +- Supports many different connectors (conceptually similar to Wagmi's connectors) +- Basic hooks for managing account + +### Cons + +- Need to set up connectors and method for connecting wallet on your own +- Need to install connectors separately +- Almost no tests or documentation; infrequent updates +- GPL-3.0 License + +## [useDApp](https://github.com/EthWorks/useDApp) + +### Pros + +- Auto-refresh on new blocks and wallet changes +- Multicall support +- Transaction notifications +- Chrome extension and Firefox add-on +- MIT License + +### Cons + +- Non-standard hook API + diff --git a/site/react/getting-started.md b/site/react/getting-started.md new file mode 100644 index 0000000000..3e4111cb49 --- /dev/null +++ b/site/react/getting-started.md @@ -0,0 +1,214 @@ + + +# Getting Started + +## Overview + +Wagmi is a React Hooks library for Ethereum. You can learn more about the rationale behind the project in the [Why Wagmi](/react/why) section. + +## Automatic Installation + +For new projects, it is recommended to set up your Wagmi app using the [`create-wagmi`](/cli/create-wagmi) command line interface (CLI). This will create a new Wagmi project using TypeScript and install the required dependencies. + +::: code-group +```bash [pnpm] +pnpm create wagmi +``` + +```bash [npm] +npm create wagmi@latest +``` + +```bash [yarn] +yarn create wagmi +``` + +```bash [bun] +bun create wagmi +``` +::: + +Once the command runs, you'll see some prompts to complete. + +```ansi +Project name: wagmi-project +Select a framework: React / Vanilla +... +``` + +After the prompts, `create-wagmi` will create a directory with your project name and install the required dependencies. Check out the `README.md` for further instructions (if required). + +## Manual Installation + +To manually add Wagmi to your project, install the required packages. + +::: code-group +```bash-vue [pnpm] +pnpm add wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [npm] +npm install wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [yarn] +yarn add wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [bun] +bun add wagmi viem@{{viemVersion}} @tanstack/react-query +``` +::: + +- [Viem](https://viem.sh) is a TypeScript interface for Ethereum that performs blockchain operations. +- [TanStack Query](https://tanstack.com/query/v5) is an async state manager that handles requests, caching, and more. +- [TypeScript](/react/typescript) is optional, but highly recommended. Learn more about [TypeScript support](/react/typescript). + +### Create Config + +Create and export a new Wagmi config using `createConfig`. + +::: code-group +<<< @/snippets/react/config.ts[config.ts] +::: + +In this example, Wagmi is configured to use the Mainnet and Sepolia chains, and `injected` connector. Check out the [`createConfig` docs](/react/api/createConfig) for more configuration options. + + +::: details TypeScript Tip +If you are using TypeScript, you can "register" the Wagmi config or use the hook `config` property to get strong type-safety across React Context in places that wouldn't normally have type info. + +::: code-group +```ts twoslash [register config] +// @errors: 2322 +import { type Config } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare const config: Config +// ---cut--- +import { useBlockNumber } from 'wagmi' + +useBlockNumber({ chainId: 123 }) + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} +``` + +```ts twoslash [hook config property] +// @errors: 2322 +import { type Config } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare const config: Config +// ---cut--- +import { useBlockNumber } from 'wagmi' + +useBlockNumber({ chainId: 123, config }) +``` + +By registering or using the hook `config` property, `useBlockNumber`'s `chainId` is strongly typed to only allow Mainnet and Sepolia IDs. Learn more by reading the [TypeScript docs](/react/typescript#config-types). +::: + +### Wrap App in Context Provider + +Wrap your app in the `WagmiProvider` React Context Provider and pass the `config` you created earlier to the `config` property. + +::: code-group +```tsx [app.tsx] +import { WagmiProvider } from 'wagmi' // [!code focus] +import { config } from './config' // [!code focus] + +function App() { + return ( + // [!code focus] + {/** ... */} // [!code focus] + // [!code focus] + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +Check out the [`WagmiProvider` docs](/react/api/WagmiProvider) to learn more about React Context in Wagmi. + +### Setup TanStack Query + +Inside the `WagmiProvider`, wrap your app in a TanStack Query React Context Provider, e.g. `QueryClientProvider`, and pass a new `QueryClient` instance to the `client` property. + +::: code-group +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' // [!code focus] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +const queryClient = new QueryClient() // [!code focus] + +function App() { + return ( + + // [!code focus] + {/** ... */} // [!code focus] + // [!code focus] + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +Check out the [TanStack Query docs](https://tanstack.com/query/latest/docs/framework/react) to learn about the library, APIs, and more. + +### Use Wagmi + +Now that everything is set up, every component inside the Wagmi and TanStack Query Providers can use Wagmi React Hooks. + +::: code-group +```tsx [profile.tsx] +import { useAccount, useEnsName } from 'wagmi' + +export function Profile() { + const { address } = useAccount() + const { data, error, status } = useEnsName({ address }) + if (status === 'pending') return
Loading ENS name
+ if (status === 'error') + return
Error fetching ENS name: {error.message}
+ return
ENS name: {data}
+} +``` + +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' +import { config } from './config' +import { Profile } from './profile' + +const queryClient = new QueryClient() + +function App() { + return ( + + + + + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Next Steps + +For more information on what to do next, check out the following topics. + +- [**TypeScript**](/react/typescript) Learn how to get the most out of Wagmi's type-safety and inference for an enlightened developer experience. +- [**Connect Wallet**](/react/guides/connect-wallet) Learn how to enable wallets to connect to and disconnect from your apps and display information about connected accounts. +- [**React Hooks**](/react/api/hooks) Browse the collection of React Hooks and learn how to use them. +- [**Viem**](/react/guides/viem) Learn about Viem and how it works with Wagmi. diff --git a/site/react/guides/chain-properties.md b/site/react/guides/chain-properties.md new file mode 100644 index 0000000000..9e4667a6e3 --- /dev/null +++ b/site/react/guides/chain-properties.md @@ -0,0 +1,97 @@ +# Chain Properties + +Some chains support additional properties related to blocks and transactions. This is powered by Viem's [formatters](https://viem.sh/docs/chains/formatters) and [serializers](https://viem.sh/docs/chains/serializers). For example, Celo, ZkSync, OP Stack chains all support additional properties. In order to use these properties in a type-safe way, there are a few things you should be aware of. + +
+ +::: tip +Make sure you follow the TypeScript guide's [Config Types](/react/typescript#config-types) section before moving on. The easiest way to do this is to use [Declaration Merging](/react/typescript#declaration-merging) to "register" your `config` globally with TypeScript. + +<<< @/snippets/react/config-chain-properties.ts[config.ts] +::: + +## Narrowing Parameters + +Once your Config is registered with TypeScript, you are ready to access chain-specific properties! For example, Celo's `feeCurrency` is available. + +::: code-group +```ts [index.tsx] +import { parseEther } from 'viem' +import { useSimulateContract } from 'wagmi' + +const result = useSimulateContract({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x…', // [!code focus] +}) +``` +<<< @/snippets/react/config-chain-properties.ts[config.ts] +::: + +This is great, but if you have multiple chains that support additional properties, your autocomplete could be overwhelmed with all of them. By setting the `chainId` property to a specific value (e.g. `celo.id`), you can narrow parameters to a single chain. + +::: code-group +```ts [index.tsx] +import { parseEther } from 'viem' +import { useSimulateContract } from 'wagmi' +import { celo } from 'wagmi/chains' + +const result = useSimulateContract({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + chainId: celo.id, // [!code focus] + feeCurrency: '0x…', // [!code focus] + // ^? (property) feeCurrency?: `0x${string}` | undefined // [!code focus] +}) +``` +<<< @/snippets/react/config-chain-properties.ts[config.ts] +::: + +## Narrowing Return Types + +Return types can also have chain-specific properties attached to them. There are a couple approaches for extracting these properties. + +### `chainId` Parameter + +Not only can you use the `chainId` parameter to [narrow parameters](#narrowing-parameters), you can also use it to narrow the return type. + +::: code-group +```ts [index.tsx] +import { useWaitForTransactionReceipt } from 'wagmi' +import { zkSync } from 'wagmi/chains' + +const { data } = useWaitForTransactionReceipt({ + chainId: zkSync.id, + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +data?.logs +// ^? (property) logs: ZkSyncLog[] | undefined +``` +<<< @/snippets/react/config-chain-properties.ts[config.ts] +::: + +### `chainId` Data Property + +Wagmi internally will set a `chainId` property on return types that you can use to narrow results. The `chainId` is determined from the `chainId` parameter or global state (e.g. connector). You can use this property to help TypeScript narrow the type. + +::: code-group +```ts [index.tsx] +import { useWaitForTransactionReceipt } from 'wagmi' +import { zkSync } from 'wagmi/chains' + +const { data } = useWaitForTransactionReceipt({ + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +if (data?.chainId === zkSync.id) { + data?.logs + // ^? (property) logs: ZkSyncLog[] | undefined +} +``` +<<< @/snippets/react/config-chain-properties.ts[config.ts] +::: + +## Troubleshooting + +If chain properties aren't working, make sure [TypeScript](/react/guides/faq#type-inference-doesn-t-work) is configured correctly. Not all chains have additional properties, to check which ones do, see the [Viem repo](https://github.com/wevm/viem/tree/main/src/chains) (chains that have a top-level directory under [`src/chains`](https://github.com/wevm/viem/tree/main/src/chains) support additional properties). diff --git a/site/react/guides/connect-wallet.md b/site/react/guides/connect-wallet.md new file mode 100644 index 0000000000..34a7e7bea3 --- /dev/null +++ b/site/react/guides/connect-wallet.md @@ -0,0 +1,421 @@ +# Connect Wallet + +The ability for a user to connect their wallet is a core function for any Dapp. It allows users to perform tasks such as: writing to contracts, signing messages, or sending transactions. + +Wagmi contains everything you need to get started with building a Connect Wallet module. To get started, you can either use a [third-party library](#third-party-libraries) or [build your own](#build-your-own). + +## Third-party Libraries + +You can use a pre-built Connect Wallet module from a third-party library such as: + +- [ConnectKit](https://docs.family.co/connectkit) - [Guide](https://docs.family.co/connectkit/getting-started) +- [AppKit](https://reown.com/appkit) - [Guide](https://docs.reown.com/appkit/next/core/installation) +- [RainbowKit](https://www.rainbowkit.com/) - [Guide](https://www.rainbowkit.com/docs/installation) +- [Dynamic](https://www.dynamic.xyz/) - [Guide](https://docs.dynamic.xyz/quickstart) +- [Privy](https://privy.io) - [Guide](https://docs.privy.io/guide/react/wallets/usage/wagmi) + +The above libraries are all built on top of Wagmi, handle all the edge cases around wallet connection, and provide a seamless Connect Wallet UX that you can use in your Dapp. + +## Build Your Own + +Wagmi provides you with the Hooks to get started building your own Connect Wallet module. + +It takes less than five minutes to get up and running with Browser Wallets, WalletConnect, and Coinbase Wallet. + +### 1. Configure Wagmi + +Before we get started with building the functionality of the Connect Wallet module, we will need to set up the Wagmi configuration. + +Let's create a `config.ts` file and export a `config` object. + +::: code-group + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +In the above configuration, we want to set up connectors for Injected (browser), WalletConnect (browser + mobile), MetaMask, and Safe wallets. This configuration uses the **Mainnet** and **Base** chains, but you can use whatever you want. + +::: warning + +Make sure to replace the `projectId` with your own WalletConnect Project ID, if you wish to use WalletConnect! + +[Get your Project ID](https://cloud.reown.com/) + +::: + +### 2. Wrap App in Context Provider + +Next, we will need to wrap our React App with Context so that our application is aware of Wagmi & React Query's reactive state and in-memory caching. + +::: code-group + +```tsx [app.tsx] + // 1. Import modules +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +// 2. Set up a React Query client. +const queryClient = new QueryClient() + +function App() { + // 3. Wrap app with Wagmi and React Query context. + return ( + + + {/** ... */} + + + ) +} +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 3. Display Wallet Options + +After that, we will create a `WalletOptions` component that will display our connectors. This will allow users to select a wallet and connect. + +Below, we are rendering a list of `connectors` retrieved from `useConnect`. When the user clicks on a connector, the `connect` function will connect the users' wallet. + +::: code-group + +```tsx [wallet-options.tsx] +import * as React from 'react' +import { Connector, useConnect } from 'wagmi' + +export function WalletOptions() { + const { connectors, connect } = useConnect() + + return connectors.map((connector) => ( + + )) +} +``` + +```tsx [app.tsx] + // 1. Import modules +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +// 2. Set up a React Query client. +const queryClient = new QueryClient() + +function App() { + // 3. Wrap app with Wagmi and React Query context. + return ( + + + {/* ... */} + + + ) +} +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 4. Display Connected Account + +Lastly, if an account is connected, we want to show some basic information, like the connected address and ENS name and avatar. + +Below, we are using hooks like `useAccount`, `useEnsAvatar` and `useEnsName` to extract this information. + +We are also utilizing `useDisconnect` to show a "Disconnect" button so a user can disconnect their wallet. + +::: code-group + +```tsx [account.tsx] +import { useAccount, useDisconnect, useEnsAvatar, useEnsName } from 'wagmi' + +export function Account() { + const { address } = useAccount() + const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ address }) + const { data: ensAvatar } = useEnsAvatar({ name: ensName! }) + + return ( +
+ {ensAvatar && ENS Avatar} + {address &&
{ensName ? `${ensName} (${address})` : address}
} + +
+ ) +} +``` + +```tsx [wallet-options.tsx] +import * as React from 'react' +import { Connector, useConnect } from 'wagmi' + +export function WalletOptions() { + const { connectors, connect } = useConnect() + + return connectors.map((connector) => ( + connect({ connector })} + /> + )) +} + +function WalletOption({ + connector, + onClick, +}: { + connector: Connector + onClick: () => void +}) { + const [ready, setReady] = React.useState(false) + + React.useEffect(() => { + ;(async () => { + const provider = await connector.getProvider() + setReady(!!provider) + })() + }, [connector]) + + return ( + + ) +} +``` + +```tsx [app.tsx] + // 1. Import modules +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +// 2. Set up a React Query client. +const queryClient = new QueryClient() + +function App() { + // 3. Wrap app with Wagmi and React Query context. + return ( + + + {/* ... */} + + + ) +} +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 5. Wire it up! + +Finally, we can wire up our Wallet Options and Account components to our application's entrypoint. + +::: code-group + +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider, useAccount } from 'wagmi' +import { config } from './config' +import { Account } from './account' // [!code ++] +import { WalletOptions } from './wallet-options' // [!code ++] + +const queryClient = new QueryClient() + +function ConnectWallet() { // [!code ++] + const { isConnected } = useAccount() // [!code ++] + if (isConnected) return // [!code ++] + return // [!code ++] +} // [!code ++] + +function App() { + return ( + + + // [!code ++] + + + ) +} +``` + +```tsx [account.tsx] +import { useAccount, useDisconnect, useEnsAvatar, useEnsName } from 'wagmi' + +export function Account() { + const { address } = useAccount() + const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ address }) + const { data: ensAvatar } = useEnsAvatar({ name: ensName! }) + + return ( +
+ {ensAvatar && ENS Avatar} + {address &&
{ensName ? `${ensName} (${address})` : address}
} + +
+ ) +} +``` + +```tsx [wallet-options.tsx] +import * as React from 'react' +import { Connector, useConnect } from 'wagmi' + +export function WalletOptions() { + const { connectors, connect } = useConnect() + + return connectors.map((connector) => ( + connect({ connector })} + /> + )) +} + +function WalletOption({ + connector, + onClick, +}: { + connector: Connector + onClick: () => void +}) { + const [ready, setReady] = React.useState(false) + + React.useEffect(() => { + ;(async () => { + const provider = await connector.getProvider() + setReady(!!provider) + })() + }, [connector]) + + return ( + + ) +} +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### Playground + +Want to see the above steps all wired up together in an end-to-end example? Check out the below StackBlitz playground. + +
+ + diff --git a/site/react/guides/error-handling.md b/site/react/guides/error-handling.md new file mode 100644 index 0000000000..62dae72b52 --- /dev/null +++ b/site/react/guides/error-handling.md @@ -0,0 +1,42 @@ +# Error Handling + +The `error` property in Wagmi Hooks is strongly typed with it's corresponding error type. This enables you to have granular precision with handling errors in your application. + +You can discriminate the error type by using the `name` property on the error object. + +::: code-group +```tsx twoslash [index.tsx] +// @noErrors +import { useBlockNumber } from 'wagmi' + +function App() { + const { data, error } = useBlockNumber() + // ^? + + error?.name + // ^? + + + + + + + if (error?.name === 'HttpRequestError') { + const { status } = error + // ^? + + + return
A HTTP error occurred. Status: {status}
+ } + if (error?.name === 'LimitExceededRpcError') { + const { code } = error + // ^? + + + return
Rate limit exceeded. Code: {code}
+ } + // ... +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: diff --git a/site/react/guides/ethers.md b/site/react/guides/ethers.md new file mode 100644 index 0000000000..fe1b2ad597 --- /dev/null +++ b/site/react/guides/ethers.md @@ -0,0 +1,285 @@ +# Ethers.js Adapters + +It is recommended for projects to migrate to [Viem](https://viem.sh) when using Wagmi, but there are some cases where you might still need to use [Ethers.js](https://ethers.org) in your project: + +- You may want to **incrementally migrate** Ethers.js usage to Viem +- Some **third-party libraries & SDKs** may only support Ethers.js +- Personal preference + +We have provided reference implementations for Viem → Ethers.js adapters that you can copy + paste in your project. + +## Client → Provider + +### Reference Implementation + +Copy the following reference implementation into a file of your choice: + +::: code-group + +```ts [Ethers v5] +import { providers } from 'ethers' +import { useMemo } from 'react' +import type { Chain, Client, Transport } from 'viem' +import { Config, useClient } from 'wagmi' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') + return new providers.FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), + ) + return new providers.JsonRpcProvider(transport.url, network) +} + +/** Hook to convert a viem Client to an ethers.js Provider. */ +export function useEthersProvider({ + chainId, +}: { chainId?: number | undefined } = {}) { + const client = useClient({ chainId }) + return useMemo(() => (client ? clientToProvider(client) : undefined), [client]) +} +``` + +```ts [Ethers v6] +import { FallbackProvider, JsonRpcProvider } from 'ethers' +import { useMemo } from 'react' +import type { Chain, Client, Transport } from 'viem' +import { type Config, useClient } from 'wagmi' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Client to an ethers.js Provider. */ +export function useEthersProvider({ chainId }: { chainId?: number } = {}) { + const client = useClient({ chainId }) + return useMemo(() => (client ? clientToProvider(client) : undefined), [client]) +} +``` + +::: + +### Usage + +Now you can use the `useEthersProvider` function in your components: + +::: code-group + +```ts [example.ts] +import { useEthersProvider } from './ethers' + +function Example() { + const provider = useEthersProvider() + ... +} +``` + +```ts [ethers.ts (Ethers v5)] +import { providers } from 'ethers' +import { useMemo } from 'react' +import type { Chain, Client, Transport } from 'viem' +import { Config, useClient } from 'wagmi' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') + return new providers.FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), + ) + return new providers.JsonRpcProvider(transport.url, network) +} + +/** Hook to convert a viem Client to an ethers.js Provider. */ +export function useEthersProvider({ + chainId, +}: { chainId?: number | undefined } = {}) { + const client = useClient({ chainId }) + return useMemo(() => (client ? clientToProvider(client) : undefined), [client]) +} +``` + +```ts [ethers.ts (Ethers v6)] +import { FallbackProvider, JsonRpcProvider } from 'ethers' +import { useMemo } from 'react' +import type { Chain, Client, Transport } from 'viem' +import { type Config, useClient } from 'wagmi' + +export function clientToProvider(client: Client) { + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) +} + +/** Action to convert a viem Client to an ethers.js Provider. */ +export function useEthersProvider({ chainId }: { chainId?: number } = {}) { + const client = useClient({ chainId }) + return useMemo(() => (client ? clientToProvider(client) : undefined), [client]) +} +``` + +::: + +## Connector Client → Signer + +### Reference Implementation + +Copy the following reference implementation into a file of your choice: + +::: code-group + +```ts [Ethers v5] +import { providers } from 'ethers' +import { useMemo } from 'react' +import type { Account, Chain, Client, Transport } from 'viem' +import { Config, useConnectorClient } from 'wagmi' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new providers.Web3Provider(transport, network) + const signer = provider.getSigner(account.address) + return signer +} + +/** Hook to convert a Viem Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: client } = useConnectorClient({ chainId }) + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]) +} +``` + +```ts [Ethers v6] +import { BrowserProvider, JsonRpcSigner } from 'ethers' +import { useMemo } from 'react' +import type { Account, Chain, Client, Transport } from 'viem' +import { type Config, useConnectorClient } from 'wagmi' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new BrowserProvider(transport, network) + const signer = new JsonRpcSigner(provider, account.address) + return signer +} + +/** Hook to convert a viem Wallet Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: client } = useConnectorClient({ chainId }) + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]) +} +``` + +::: + +### Usage + +Now you can use the `useEthersSigner` function in your components: + +::: code-group + +```ts [example.ts] +import { useEthersSigner } from './ethers' + +function example() { + const signer = useEthersSigner() + ... +} +``` + +```ts [ethers.ts (Ethers v5)] +import { providers } from 'ethers' +import { useMemo } from 'react' +import type { Account, Chain, Client, Transport } from 'viem' +import { Config, useConnectorClient } from 'wagmi' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new providers.Web3Provider(transport, network) + const signer = provider.getSigner(account.address) + return signer +} + +/** Action to convert a Viem Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: client } = useConnectorClient({ chainId }) + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]) +} +``` + +```ts [ethers.ts (Ethers v6)] +import { BrowserProvider, JsonRpcSigner } from 'ethers' +import { useMemo } from 'react' +import type { Account, Chain, Client, Transport } from 'viem' +import { type Config, useConnectorClient } from 'wagmi' + +export function clientToSigner(client: Client) { + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new BrowserProvider(transport, network) + const signer = new JsonRpcSigner(provider, account.address) + return signer +} + +/** Hook to convert a viem Wallet Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: client } = useConnectorClient({ chainId }) + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]) +} +``` + +::: diff --git a/site/react/guides/faq.md b/site/react/guides/faq.md new file mode 100644 index 0000000000..8628c5b1b3 --- /dev/null +++ b/site/react/guides/faq.md @@ -0,0 +1,24 @@ + + +# FAQ / Troubleshooting + +Collection of frequently asked questions with ideas on how to troubleshoot and resolve them. + + + +## How does Wagmi work? + +Until there's a more in-depth write-up about Wagmi internals, here is the gist: + +- Wagmi is essentially a wrapper around [Viem](https://viem.sh) and TanStack Query that adds connector and multichain support. +- [Connectors](/react/api/connectors) allow Wagmi and Ethereum accounts to communicate with each other. +- The Wagmi [`Config`](/react/api/createConfig#config) manages connections established between Wagmi and Connectors, as well as some global state. [Connections](/react/api/createConfig#connection) come with one or more addresses and a chain ID. + - If there are connections, the Wagmi `Config` listens for connection changes and updates the [`chainId`](/react/api/createConfig#chainid) based on the ["current" connection](/react/api/createConfig#current). (The Wagmi `Config` can have [many connections established](/react/api/createConfig#connections) at once, but only one connection can be the "current" connection. Usually this is the connection from the last connector that is connected, but can change based on event emitted from other connectors or through the [`useSwitchAccount`](/react/api/hooks/useSwitchAccount) hook and [`switchAccount`](/core/api/actions/switchAccount) action.) + - If there are no connections, the Wagmi `Config` defaults the global state `chainId` to the first chain it was created with or last established connection. + - The global `chainId` can be changed directly using the [`useSwitchChain`](/react/api/hooks/useSwitchChain) hook and [`switchChain`](/core/api/actions/switchChain) action. This works when there are no connections as well as for most connectors (not all connectors support chain switching). +- Wagmi uses the global `chainId` (from the "current" connection or global state) to internally create Viem Client's, which are used by hooks and actions. +- Hooks are constructed by TanStack Query options helpers, exported by the `'@wagmi/core/query'` entrypoint, and some additional code to wire up type parameters, hook into React Context, etc. +- There are three types of hooks: query hooks, mutation hooks, and config hooks. Query hooks, like [`useCall`](/react/api/hooks/useCall), generally read blockchain state and mutation hooks, like [`useSendTransaction`](/react/api/hooks/useSendTransaction), usually change state through sending transactions via the "current" connection. Config hooks are for getting data from and managing the Wagmi `Config` instance, e.g. [`useChainId`](/react/api/hooks/useChainId) and `useSwitchAccount`. Query and mutation hooks usually have corresponding [Viem actions.](https://viem.sh) + diff --git a/site/react/guides/migrate-from-v1-to-v2.md b/site/react/guides/migrate-from-v1-to-v2.md new file mode 100644 index 0000000000..1374fd6eef --- /dev/null +++ b/site/react/guides/migrate-from-v1-to-v2.md @@ -0,0 +1,658 @@ +--- +title: Migrate from v1 to v2 +description: Guide for migrating from Wagmi v1 to v2. +--- + + + +# Migrate from v1 to v2 + +## Overview + +Wagmi v2 redesigns the core APIs to mesh better with [Viem](https://viem.sh) and [TanStack Query](https://tanstack.com/query/v5/docs/react). This major version transforms Wagmi into a light wrapper around these libraries, sprinkling in multichain support and account management. As such, there are some breaking changes and deprecations to be aware of outlined in this guide. + +To get started, install the latest version of Wagmi and it's required peer dependencies. + +::: code-group +```bash-vue [pnpm] +pnpm add wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [npm] +npm install wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [yarn] +yarn add wagmi viem@{{viemVersion}} @tanstack/react-query +``` + +```bash-vue [bun] +bun add wagmi viem@{{viemVersion}} @tanstack/react-query +``` +::: + +::: info Wagmi v2 should be the last major version that will have this many actionable breaking changes. +Moving forward after Wagmi v2, new functionality will be opt-in with old functionality being deprecated alongside the new features. This means upgrading to the latest major versions will not require immediate changes. +::: + +::: info Not ready to migrate yet? +The Wagmi v1 docs are still available at [1.x.wagmi.sh/react](https://1.x.wagmi.sh/react). +::: + +## Dependencies + +### Moved TanStack Query to peer dependencies + +Wagmi uses [TanStack Query](https://tanstack.com/query/v5/docs/react) to manage async state, handling requests, caching, and more. With Wagmi v1, TanStack Query was an internal implementation detail. With Wagmi v2, TanStack Query is a peer dependency. A lot of Wagmi users also use TanStack Query in their apps so making it a peer dependency gives them more control and removes some custom Wagmi code internally. + +If you don't normally use TanStack Query, all you need to do is set it up and mostly forget about it (we'll provide guidance around version updates). + +::: code-group +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' // [!code ++] +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +const queryClient = new QueryClient() // [!code ++] + +function App() { + return ( + + // [!code ++] + {/** ... */} + // [!code ++] + + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +For more information on setting up TanStack Query for Wagmi, follow the [Getting Started docs](/react/getting-started#setup-tanstack-query). If you want to set up persistence for your query cache (default behavior before Wagmi v2), check out the [TanStack Query docs](https://tanstack.com/query/v5/docs/react/plugins/persistQueryClient#usage-with-react). + +### Dropped CommonJS support + +Wagmi v2 no longer publishes a separate `cjs` tag since very few people use this tag and ESM is the future. See [Sindre Sorhus' guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) for more info about switching to ESM. + +## Hooks + +### Removed mutation setup arguments + +Mutation hooks are hooks that change network or application state, sign data, or perform write operations through mutation functions. With Wagmi v1, you could pass arguments directly to these hooks instead of using them with their mutation functions. For example: + +```ts{3} +// Wagmi v1 +const { signMessage } = useSignMessage({ + message: 'foo bar baz', +}) +``` + +With Wagmi v2, you must pass arguments to the mutation function instead. This follows the same behavior as [TanStack Query](https://tanstack.com/query/v5/docs/react/guides/mutations) mutations and improves type-safety. + +```tsx +import { useSignMessage } from 'wagmi' + +const { signMessage } = useSignMessage({ message: 'foo bar baz' }) // [!code --] +const { signMessage } = useSignMessage() // [!code ++] + + +``` + +### Moved TanStack Query parameters to `query` property + +Previously, you could pass TanStack Query parameters, like `enabled` and `staleTime`, directly to hooks. In Wagmi v2, TanStack Query parameters are now moved to the `query` property. This allows Wagmi to better support TanStack Query type inference, control for future breaking changes since [TanStack Query is now a peer dependency](#moved-tanstack-query-to-peer-dependencies), and expose Wagmi-related hook property at the top-level of editor features, like autocomplete. + +```tsx +useReadContract({ + enabled: false, // [!code --] + staleTime: 1_000, // [!code --] + query: { // [!code ++] + enabled: false, // [!code ++] + staleTime: 1_000, // [!code ++] + }, // [!code ++] +}) +``` + +### Removed watch property + +The `watch` property was removed from all hooks besides [`useBlock`](/react/api/hooks/useBlock) and [`useBlockNumber`](/react/api/hooks/useBlockNumber). This property allowed hooks to internally listen for block changes and automatically refresh their data. In Wagmi v2, you can compose `useBlock` or `useBlockNumber` along with [`React.useEffect`](https://react.dev/reference/react/useEffect) to achieve the same behavior. Two different approaches are outlined for `useBalance` below. + +::: code-group +```ts [invalidateQueries] +import { useQueryClient } from '@tanstack/react-query' // [!code ++] +import { useEffect } from 'react' // [!code ++] +import { useBlockNumber, useBalance } from 'wagmi' // [!code ++] + +const queryClient = useQueryClient() // [!code ++] +const { data: blockNumber } = useBlockNumber({ watch: true }) // [!code ++] +const { data: balance, queryKey } = useBalance({ // [!code ++] + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + watch: true, // [!code --] +}) + +useEffect(() => { // [!code ++] + queryClient.invalidateQueries({ queryKey }) // [!code ++] +}, [blockNumber, queryClient]) // [!code ++] +``` +```ts [refetch] +import { useEffect } from 'react' // [!code ++] +import { useBlockNumber, useBalance } from 'wagmi' // [!code ++] + +const { data: blockNumber } = useBlockNumber({ watch: true }) // [!code ++] +const { data: balance, refetch } = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + watch: true, // [!code --] +}) + +useEffect(() => { // [!code ++] + refetch() // [!code ++] +}, [blockNumber]) // [!code ++] +``` +::: + +This is a bit more code, but removes a lot of internal code from hooks that can slow down your app when not used and gives you more control. For example, you can easily refresh data every five blocks instead of every block. + +```ts +const { data: blockNumber } = useBlockNumber({ watch: true }) +const { data: balance, queryKey } = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', +}) + +useEffect(() => { + if (blockNumber % 5 === 0) // [!code focus] + queryClient.invalidateQueries({ queryKey }) // [!code focus] +}, [blockNumber, queryClient]) +``` + +### Removed suspense property + +Wagmi used to support an experimental `suspense` property via TanStack Query. Since TanStack Query [removed `suspense`](https://tanstack.com/query/v5/docs/react/guides/migrating-to-v5#new-hooks-for-suspense) from its `useQuery` hook, it is no longer supported by Wagmi Hooks. + +Instead, you can use `useSuspenseQuery` along with TanStack Query-related exports from the `'wagmi/query'` entrypoint. + +```ts +import { useSuspenseQuery } from '@tanstack/react-query' // [!code ++] +import { useConfig } from 'wagmi' // [!code ++] +import { getBalanceQueryOptions } from 'wagmi/query' // [!code ++] +import { useBalance } from 'wagmi' // [!code --] + +const config = useConfig() // [!code ++] +const options = getBalanceQueryOptions(config, { address: '0x…' }) // [!code ++] +const result = useSuspenseQuery(options) // [!code ++] +const result = useBalance({ // [!code --] + address: '0x…', // [!code --] + suspense: true, // [!code --] +}) // [!code --] +``` + +### Removed prepare hooks + +`usePrepareContractWrite` and `usePrepareSendTransaction` were removed and replaced with idiomatic Viem alternatives. For `usePrepareContractWrite`, use [`useSimulateContract`](/react/api/hooks/useSimulateContract). Similar to `usePrepareContractWrite`, `useSimulateContract` composes well with `useWriteContract` + +```tsx +import { usePrepareContractWrite, useWriteContract } from 'wagmi' // [!code --] +import { useSimulateContract, useWriteContract } from 'wagmi' // [!code ++] + +const { config } = usePrepareContractWrite({ // [!code --] +const { data } = useSimulateContract({ // [!code ++] + address: '0x', + abi: [{ + type: 'function', + name: 'transferFrom', + stateMutability: 'nonpayable', + inputs: [ + { name: 'sender', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ type: 'bool' }], + }], + functionName: 'transferFrom', + args: ['0x', '0x', 123n], +}) +const { write } = useWriteContract(config) // [!code --] +const { writeContract } = useWriteContract() // [!code ++] + + +``` + +Instead of `usePrepareSendTransaction`, use [`useEstimateGas`](/react/api/hooks/useEstimateGas). You can pass `useEstimateGas` `data` to `useSendTransaction` to compose the two hooks. + +```tsx +import { usePrepareSendTransaction, useSendTransaction } from 'wagmi' // [!code --] +import { useEstimateGas, useSendTransaction } from 'wagmi' // [!code ++] +import { parseEther } from 'viem' + +const { config } = usePrepareSendTransaction({ // [!code --] +const { data } = useEstimateGas({ // [!code ++] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), +}) +const { sendTransaction } = useSendTransaction(config) // [!code --] +const { sendTransaction } = useSendTransaction() // [!code ++] + + +``` + +This might seem like more work, but it gives you more control and is more accurate representation of what is happening under the hood. + +### Removed `useNetwork` hook + +The `useNetwork` hook was removed since the connected chain is typically based on the connected account. Use [`useAccount`](/react/api/hooks/useAccount) to get the connected `chain`. + +```ts +import { useNetwork } from 'wagmi' // [!code --] +import { useAccount } from 'wagmi' // [!code ++] + +const { chain } = useNetwork() // [!code --] +const { chain } = useAccount() // [!code ++] +``` + +Use `useConfig` for the list of `chains` set up with the Wagmi [`Config`](/react/api/createConfig#chains). + +```ts +import { useNetwork } from 'wagmi' // [!code --] +import { useConfig } from 'wagmi' // [!code ++] + +const { chains } = useNetwork() // [!code --] +const { chains } = useConfig() // [!code ++] +``` + +### Removed `onConnect` and `onDisconnect` callbacks from `useAccount` + +The `onConnect` and `onDisconnect` callbacks were removed from the `useAccount` hook since it is frequently used without these callbacks so it made sense to extract these into a new API, [`useAccountEffect`](/react/api/hooks/useAccountEffect), rather than clutter the `useAccount` hook. + +```ts +import { useAccount } from 'wagmi' // [!code --] +import { useAccountEffect } from 'wagmi' // [!code ++] + +useAccount({ // [!code --] +useAccountEffect({ // [!code ++] + onConnect(data) { + console.log('connected', data) + }, + onDisconnect() { + console.log('disconnected') + }, +}) +``` + +### Removed `useWebSocketPublicClient` + +The Wagmi [`Config`](/react/api/createConfig) does not separate transport types anymore. Simply use Viem's [`webSocket`](https://viem.sh/docs/clients/transports/websocket.html) transport instead when setting up your Wagmi `Config`. You can get Viem `Client` instance with this transport attached by using [`useClient`](/react/api/hooks/useClient) or [`usePublicClient`](/react/api/hooks/usePublicClient). + +### Removed `useInfiniteReadContracts` `paginatedIndexesConfig` + +In the spirit of removing unnecessary abstractions, `paginatedIndexesConfig` was removed. Use `useInfiniteReadContracts`'s `initialPageParam` and `getNextPageParam` parameters along with `fetchNextPage`/`fetchPreviousPage` from the result instead or copy `paginatedIndexesConfig`'s implementation to your codebase. + +See the [TanStack Query docs](https://tanstack.com/query/v5/docs/react/guides/infinite-queries) for more information on infinite queries. + +### Updated `useSendTransaction` and `useWriteContract` return type + +Updated [`useSendTransaction`](/react/api/hooks/useSendTransaction) and [`useWriteContract`](/react/api/hooks/useWriteContract) return type from `` { hash: `0x${string}` } `` to `` `0x${string}` ``. + +```ts +const result = useSendTransaction() +result.data?.hash // [!code --] +result.data // [!code ++] +``` + +### Updated `useConnect` return type + +Updated [`useConnect`](/react/api/hooks/useConnect) return type from `` { account: Address; chain: { id: number; unsupported?: boolean }; connector: Connector } `` to `` { accounts: readonly Address[]; chainId: number } ``. This better reflects the ability to have multiple accounts per connector. + +### Renamed parameters and return types + +All hook parameters and return types follow the naming pattern of `[PascalCaseHookName]Parameters` and `[PascalCaseHookName]ReturnType`. For example, `UseAccountParameters` and `UseAccountReturnType`. + +```ts +import { UseAccountConfig, UseAccountResult } from 'wagmi' // [!code --] +import { UseAccountParameters, UseAccountReturnType } from 'wagmi' // [!code ++] +``` + +## Connectors + +### Updated connector API + +In order to maximize type-safety and ease of creating connectors, the connector API changed. Follow the [Creating Connectors guide](/dev/creating-connectors) for more info on creating new connectors and converting Wagmi v1 connectors. + +### Removed individual entrypoints + +Previously, each connector had it's own entrypoint to optimize tree-shaking. Since all connectors now have [`package.json#sideEffects`](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) enabled, this is no longer necessary and the entrypoint is unified. Use the `'wagmi/connectors'` entrypoint instead. + +```ts +import { InjectedConnector } from 'wagmi/connectors/injected' // [!code --] +import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet' // [!code --] +import { coinbaseWallet, injected } from 'wagmi/connectors' // [!code ++] +``` + +### Removed `MetaMaskConnector` + +The `MetaMaskConnector` was removed since it was nearly the same thing as the `InjectedConnector`. Use the [`injected`](/react/api/connectors/injected) connector instead, along with the [`target`](/react/api/connectors/injected#target) parameter set to `'metaMask'`, for the same behavior. + +```ts +import { MetaMaskConnector } from 'wagmi/connectors/metaMask' // [!code --] +import { injected } from 'wagmi/connectors' // [!code ++] + +const connector = new MetaMaskConnector() // [!code --] +const connector = injected({ target: 'metaMask' }) // [!code ++] +``` +### Renamed connectors + +In Wagmi v1, connectors were classes you needed to instantiate. In Wagmi v2, connectors are functions. As a result, the API has changed. Connectors have the following new names: + +- `CoinbaseWalletConnector` is now [`coinbaseWallet`](/react/api/connectors/coinbaseWallet). +- `InjectedConnector` is now [`injected`](/react/api/connectors/injected). +- `SafeConnector` is now [`safe`](/react/api/connectors/safe). +- `WalletConnectConnector` is now [`walletConnect`](/react/api/connectors/walletConnect). + +To create a connector, you now call the connector function with parameters. + +```ts +import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' // [!code --] +import { walletConnect } from 'wagmi/connectors' // [!code ++] + +const connector = new WalletConnectConnector({ // [!code --] +const connector = walletConnect({ // [!code ++] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +### Removed `WalletConnectLegacyConnector` + +WalletConnect v1 was sunset June 28, 2023. Use the [`walletConnect`](/react/api/connectors/walletConnect) connector instead. + +```ts +import { WalletConnectLegacyConnector } from 'wagmi/connectors/walletConnectLegacy' // [!code --] +import { walletConnect } from 'wagmi/connectors' // [!code ++] + +const connector = new WalletConnectLegacyConnector({ // [!code --] +const connector = walletConnect({ // [!code ++] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +## Chains + +### Updated `'wagmi/chains'` entrypoint + +Chains now live in the [Viem repository](https://github.com/wevm/viem). As a result, the `'wagmi/chains'` entrypoint now proxies all chains from `'viem/chains'` directly. + +### Removed `mainnet` and `sepolia` from main entrypoint + +Since the `'wagmi/chains'` entrypoint now proxies `'viem/chains'`, `mainnet` and `sepolia` were removed from the main entrypoint. Use the `'wagmi/chains'` entrypoint instead. + +```ts +import { mainnet, sepolia } from 'wagmi' // [!code --] +import { mainnet, sepolia } from 'wagmi/chains' // [!code ++] +``` + +## Errors + +A number of errors were renamed to better reflect their functionality or replaced by Viem errors. + +## Miscellaneous + +### Removed internal ENS name normalization + +Before v2, Wagmi handled ENS name normalization internally for `useEnsAddress`, `useEnsAvatar`, and `useEnsResolver`, using Viem's [`normalize`](https://viem.sh/docs/ens/utilities/normalize.html) function. This added extra bundle size as full normalization is quite heavy. For v2, you must normalize ENS names yourself before passing them to these hooks. You can use Viem's `normalize` function or any other function that performs [UTS-46 normalization](https://unicode.org/reports/tr46). + +```ts +import { useEnsAddress } from 'wagmi' +import { normalize } from 'viem/ens' // [!code ++] + +const result = useEnsAddress({ + name: 'wevm.eth', // [!code --] + name: normalize('wevm.eth'), // [!code ++] +}) +``` + +By inverting control, Wagmi let's you choose how much normalization to do. For example, maybe your project only allows ENS names that are numeric so no normalization is not needed. Check out the [ENS documentation](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) for more information on normalizing names. + +### Removed `configureChains` + +The Wagmi v2 `Config` now has native multichain support using the [`chains`](/react/api/createConfig) parameter so the `configureChains` function is no longer required. + +```ts +import { configureChains, createConfig } from 'wagmi' // [!code --] +import { http, createConfig } from 'wagmi' // [!code ++] +import { mainnet, sepolia } from 'wagmi/chains' + +const { chains, publicClient } = configureChains( // [!code --] + [mainnet, sepolia], // [!code --] + [publicProvider(), publicProvider()], // [!code --] +) // [!code --] + +export const config = createConfig({ + publicClient, // [!code --] + chains: [mainnet, sepolia], // [!code ++] + transports: { // [!code ++] + [mainnet.id]: http(), // [!code ++] + [sepolia.id]: http(), // [!code ++] + }, // [!code ++] +}) +``` + +### Removed ABI exports + +Import from Viem instead. + +```ts +import { erc20ABI } from 'wagmi' // [!code --] +import { erc20Abi } from 'viem' // [!code ++] +``` + +### Removed `'wagmi/providers/*` entrypoints + +It never made sense that we would have provider URLs hardcoded in the Wagmi codebase. Use [Viem transports](https://viem.sh/docs/clients/intro.html#transports) along with RPC provider URLs instead. + +```ts +import { alchemyProvider } from 'wagmi/providers/alchemy' // [!code --] +import { http } from 'viem' // [!code ++] + +const transport = http('https://mainnet.example.com') +``` + +### Updated `createConfig` parameters + +- Removed `autoConnect`. The reconnecting behavior is now managed by React and not related to the Wagmi `Config`. Use `WagmiProvider` [`reconnectOnMount`](/react/api/WagmiProvider#reconnectonmount) or [`useReconnect`](/react/api/hooks/useReconnect) hook instead. +- Removed `publicClient` and `webSocketPublicClient`. Use [`transports`](/react/api/createConfig#transports) or [`client`](/react/api/createConfig#client) instead. +- Removed `logger`. Wagmi no longer logs debug information to console. + +### Updated `Config` object + +- Removed `config.connector`. Use `config.state.connections.get(config.state.current)?.connector` instead. +- Removed `config.data`. Use `config.state.connections.get(config.state.current)` instead. +- Removed `config.error`. Was unused and not needed. +- Removed `config.lastUsedChainId`. Use `config.state.connections.get(config.state.current)?.chainId` instead. +- Removed `config.publicClient`. Use [`config.getClient()`](/react/api/createConfig#getclient) or [`getPublicClient`](/core/api/actions/getPublicClient) instead. +- Removed `config.status`. Use [`config.state.status`](/react/api/createConfig#status) instead. +- Removed `config.webSocketClient`. Use [`config.getClient()`](/react/api/createConfig#getclient) or [`getPublicClient`](/core/api/actions/getPublicClient) instead. +- Removed `config.clearState`. Was unused and not needed. +- Removed `config.autoConnect()`. Use [`reconnect`](/core/api/actions/reconnect) action instead. +- Renamed `config.setConnectors`. Use `config._internal.setConnectors` instead. +- Removed `config.setLastUsedConnector`. Use `config.storage?.setItem('recentConnectorId', connectorId)` instead. +- Removed `getConfig`. `config` should be passed explicitly to actions instead of using global `config`. + +## Deprecations + +### Renamed `WagmiConfig` + +`WagmiConfig` was renamed to [`WagmiProvider`](/react/api/WagmiProvider) to reduce confusion with the Wagmi [`Config`](/react/api/createConfig) type. React Context Providers usually follow the naming schema `*Provider` so this is a more idiomatic name. Now that Wagmi no longer uses Ethers.js (since Wagmi v1), the term "Provider" is less overloaded. + +::: code-group +```tsx [app.tsx] +import { WagmiConfig } from 'wagmi' // [!code --] +import { WagmiProvider } from 'wagmi' // [!code ++] +import { config } from './config' + +function App() { + return ( + // [!code --] + // [!code ++] + {/** ... */} + // [!code ++] + // [!code --] + ) +} +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### Deprecated `useBalance` `token` parameter + +Moving forward, `useBalance` will only work for native currencies, thus the `token` parameter is no longer supported. Use [`useReadContracts`](/react/api/hooks/useReadContracts) instead. + +```ts +import { useBalance } from 'wagmi' // [!code --] +import { useReadContracts } from 'wagmi' // [!code ++] +import { erc20Abi } from 'viem' // [!code ++] + +const result = useBalance({ // [!code --] + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', // [!code --] + token: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code --] +}) // [!code --] +const result = useReadContracts({ // [!code ++] + allowFailure: false, // [!code ++] + contracts: [ // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'balanceOf', // [!code ++] + args: ['0x4557B18E779944BFE9d78A672452331C186a9f48'], // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'decimals', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'symbol', // [!code ++] + }, // [!code ++] + ] // [!code ++] +}) // [!code ++] +``` + +### Deprecated `useBalance` `unit` parameter and `formatted` return value + +Moving forward, `useBalance` will not accept the `unit` parameter or return a `formatted` value. Instead you can call `formatUnits` from Viem directly or use another number formatting library, like [dnum](https://github.com/bpierre/dnum) instead. + +```ts +import { formatUnits } from 'viem' // [!code ++] +import { useBalance } from 'wagmi' + +const result = useBalance({ + address: '0x4557B18E779944BFE9d78A672452331C186a9f48', + unit: 'ether', // [!code --] +}) +result.data!.formatted // [!code --] +formatUnits(result.data!.value, result.data!.decimals) // [!code ++] +``` + +### Deprecated `useToken` + +Moving forward, `useToken` is no longer supported. Use [`useReadContracts`](/react/api/hooks/useReadContracts) instead. + +```ts +import { useToken } from 'wagmi' // [!code --] +import { useReadContracts } from 'wagmi' // [!code ++] +import { erc20Abi } from 'viem' // [!code ++] + +const result = useToken({ // [!code --] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code --] +}) // [!code --] +const result = useReadContracts({ // [!code ++] + allowFailure: false, // [!code ++] + contracts: [ // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'decimals', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'name', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'symbol', // [!code ++] + }, // [!code ++] + { // [!code ++] + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // [!code ++] + abi: erc20Abi, // [!code ++] + functionName: 'totalSupply', // [!code ++] + }, // [!code ++] + ] // [!code ++] +}) // [!code ++] +``` + +### Deprecated `formatUnits` parameters and return values + +The `formatUnits` parameter and related return values (e.g. `result.formatted`) are deprecated for the following hooks: + +- [`useEstimateFeesPerGas`](/react/api/hooks/useEstimateFeesPerGas) +- [`useToken`](/react/api/hooks/useToken) + +Instead you can call `formatUnits` from Viem directly or use another number formatting library, like [dnum](https://github.com/bpierre/dnum) instead. + +```ts +import { formatUnits } from 'viem' // [!code ++] + +const result = useToken({ + address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + formatUnits: 'ether', +}) +result.data!.totalSupply.formatted // [!code --] +formatUnits(result.data!.totalSupply.value, 18) // [!code ++] +``` + +This allows us to invert control to users so they can handle number formatting however they want, taking into account precision, localization, and more. + +### Renamed hooks + +The following hooks were renamed to better reflect their functionality and underlying [Viem](https://viem.sh) actions: + +- `useContractRead` is now [`useReadContract`](/react/api/hooks/useReadContract) +- `useContractReads` is now [`useReadContracts`](/react/api/hooks/useReadContracts) +- `useContractWrite` is now [`useWriteContract`](/react/api/hooks/useWriteContract) +- `useContractEvent` is now [`useWatchContractEvent`](/react/api/hooks/useWatchContractEvent) +- `useContractInfiniteReads` is now [`useInfiniteReadContracts`](/react/api/hooks/useInfiniteReadContracts) +- `useFeeData` is now [`useEstimateFeesPerGas`](/react/api/hooks/useEstimateFeesPerGas) +- `useSwitchNetwork` is now [`useSwitchChain`](/react/api/hooks/useSwitchChain) +- `useWaitForTransaction` is now [`useWaitForTransactionReceipt`](/react/api/hooks/useWaitForTransactionReceipt) + +### Miscellaneous + +- `WagmiConfigProps` renamed to [`WagmiProviderProps`](/react/api/WagmiProvider#parameters). +- `Context` renamed to [`WagmiContext`](/react/api/WagmiProvider#context). diff --git a/site/react/guides/read-from-contract.md b/site/react/guides/read-from-contract.md new file mode 100644 index 0000000000..b41f5c569d --- /dev/null +++ b/site/react/guides/read-from-contract.md @@ -0,0 +1,202 @@ +# Read from Contract + +The [`useReadContract` Hook](/react/api/hooks/useReadContract) allows you to read data on a smart contract, from a `view` or `pure` (read-only) function. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas. + +The component below shows how to retrieve the token balance of an address from the [Wagmi Example](https://etherscan.io/token/0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2) contract + +:::code-group +```tsx [read-contract.tsx] +import { useReadContract } from 'wagmi' +import { wagmiContractConfig } from './contracts' + +function ReadContract() { + const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + return ( +
Balance: {balance?.toString()}
+ ) +} +``` +```ts [contracts.ts] +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, + ], +} as const +``` +::: + +If `useReadContract` depends on another value (`address` in the example below), you can use the [`query.enabled`](/react/api/hooks/useReadContract#enabled) option to prevent the query from running until the dependency is ready. + +```tsx +const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: [address], + query: { // [!code focus] + enabled: !!address, // [!code focus] + }, // [!code focus] +}) +``` + +## Loading & Error States + +The [`useReadContract` Hook](/react/api/hooks/useReadContract) also returns loading & error states, which can be used to display a loading indicator while the data is being fetched, or an error message if contract execution reverts. + +:::code-group + +```tsx [read-contract.tsx] +import { type BaseError, useReadContract } from 'wagmi' + +function ReadContract() { + const { + data: balance, + error, // [!code ++] + isPending // [!code ++] + } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + if (isPending) return
Loading...
// [!code ++] + + if (error) // [!code ++] + return ( // [!code ++] +
// [!code ++] + Error: {(error as BaseError).shortMessage || error.message} // [!code ++] +
// [!code ++] + ) // [!code ++] + + return ( +
Balance: {balance?.toString()}
+ ) +} +``` + +## Refetching On Blocks + +The [`useBlockNumber` Hook](/react/api/hooks/useBlockNumber) can be utilized to refetch or [invalidate](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) the contract data on a specific block interval. + +:::code-group +```tsx [read-contract.tsx (refetch)] +import { useEffect } from 'react' +import { useBlockNumber, useReadContract } from 'wagmi' + +function ReadContract() { + const { data: balance, refetch } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + const { data: blockNumber } = useBlockNumber({ watch: true }) + + useEffect(() => { + // want to refetch every `n` block instead? use the modulo operator! + // if (blockNumber % 5 === 0) refetch() // refetch every 5 blocks + refetch() + }, [blockNumber]) + + return ( +
Balance: {balance?.toString()}
+ ) +} +``` +```tsx [read-contract.tsx (invalidate)] +import { useQueryClient } from '@tanstack/react-query' +import { useEffect } from 'react' +import { useBlockNumber, useReadContract } from 'wagmi' + +function ReadContract() { + const queryClient = useQueryClient() + const { data: balance, refetch } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + const { data: blockNumber } = useBlockNumber({ watch: true }) + + useEffect(() => { + // if `useReadContract` is in a different hook/component, + // you can import `readContractQueryKey` from `'wagmi/query'` and + // construct a one-off query key to use for invalidation + queryClient.invalidateQueries({ queryKey }) + }, [blockNumber, queryClient]) + + return ( +
Balance: {balance?.toString()}
+ ) +} +``` +::: + +## Calling Multiple Functions + +We can use the [`useReadContract` Hook](/react/api/hooks/useReadContract) multiple times in a single component to call multiple functions on the same contract, but this ends up being hard to manage as the number of functions increases, especially when we also want to deal with loading & error states. + +Luckily, to make this easier, we can use the [`useReadContracts` Hook](/react/api/hooks/useReadContracts) to call multiple functions in a single call. + +:::code-group + +```tsx [read-contract.tsx] +import { type BaseError, useReadContracts } from 'wagmi' + +function ReadContract() { + const { + data, + error, + isPending + } = useReadContracts({ + contracts: [{ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }, { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69n], + }, { + ...wagmiContractConfig, + functionName: 'totalSupply', + }] + }) + const [balance, ownerOf, totalSupply] = data || [] + + if (isPending) return
Loading...
+ + if (error) + return ( +
+ Error: {(error as BaseError).shortMessage || error.message} +
+ ) + + return ( + <> +
Balance: {balance?.toString()}
+
Owner of Token 69: {ownerOf?.toString()}
+
Total Supply: {totalSupply?.toString()}
+ + ) +} +``` + +::: diff --git a/site/react/guides/send-transaction.md b/site/react/guides/send-transaction.md new file mode 100644 index 0000000000..a6c5c1887a --- /dev/null +++ b/site/react/guides/send-transaction.md @@ -0,0 +1,362 @@ +# Send Transaction + +The following guide teaches you how to send transactions in Wagmi. The example below builds on the [Connect Wallet guide](/react/guides/connect-wallet) and uses the [useSendTransaction](/react/api/hooks/useSendTransaction) & [useWaitForTransaction](/react/api/hooks/useWaitForTransactionReceipt) hooks. + +## Example + +Feel free to check out the example before moving on: + + + +## Steps + +### 1. Connect Wallet + +Follow the [Connect Wallet guide](/react/guides/connect-wallet) guide to get this set up. + +### 2. Create a new component + +Create your `SendTransaction` component that will contain the send transaction logic. + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' + +export function SendTransaction() { + return ( +
+ + + +
+ ) +} +``` + +::: + +### 3. Add a form handler + +Next, we will need to add a handler to the form that will send the transaction when the user hits "Send". This will be a basic handler in this step. + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' + +export function SendTransaction() { + async function submit(e: React.FormEvent) { // [!code ++] + e.preventDefault() // [!code ++] + const formData = new FormData(e.target as HTMLFormElement) // [!code ++] + const to = formData.get('address') as `0x${string}` // [!code ++] + const value = formData.get('value') as string // [!code ++] + } // [!code ++] + + return ( +
// [!code --] + // [!code ++] + + + +
+ ) +} +``` + +::: + +### 4. Hook up the `useSendTransaction` Hook + +Now that we have the form handler, we can hook up the [`useSendTransaction` Hook](/react/api/hooks/useSendTransaction) to send the transaction. + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' +import { useSendTransaction } from 'wagmi' // [!code ++] +import { parseEther } from 'viem' // [!code ++] + +export function SendTransaction() { + const { data: hash, sendTransaction } = useSendTransaction() // [!code ++] + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as `0x${string}` + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) // [!code ++] + } + + return ( +
+ + + + {hash &&
Transaction Hash: {hash}
} // [!code ++] +
+ ) +} +``` + +::: + +### 5. Add loading state (optional) + +We can optionally add a loading state to the "Send" button while we are waiting confirmation from the user's wallet. + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' +import { useSendTransaction } from 'wagmi' +import { parseEther } from 'viem' + +export function SendTransaction() { + const { + data: hash, + isPending, // [!code ++] + sendTransaction + } = useSendTransaction() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as `0x${string}` + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + return ( +
+ + + + {hash &&
Transaction Hash: {hash}
} +
+ ) +} +``` + +::: + +### 6. Wait for transaction receipt (optional) + +We can also display the transaction confirmation status to the user by using the [`useWaitForTransactionReceipt` Hook](/react/api/hooks/useWaitForTransactionReceipt). + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' +import { + useSendTransaction, + useWaitForTransactionReceipt // [!code ++] +} from 'wagmi' +import { parseEther } from 'viem' + +export function SendTransaction() { + const { + data: hash, + isPending, + sendTransaction + } = useSendTransaction() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as `0x${string}` + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = // [!code ++] + useWaitForTransactionReceipt({ // [!code ++] + hash, // [!code ++] + }) // [!code ++] + + return ( +
+ + + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} // [!code ++] + {isConfirmed &&
Transaction confirmed.
} // [!code ++] +
+ ) +} +``` + +::: + +### 7. Handle errors (optional) + +If the user rejects the transaction, or the user does not have enough funds to cover the transaction, we can display an error message to the user. + +::: code-group + +```tsx [send-transaction.tsx] +import * as React from 'react' +import { + type BaseError, // [!code ++] + useSendTransaction, + useWaitForTransactionReceipt +} from 'wagmi' +import { parseEther } from 'viem' + +export function SendTransaction() { + const { + data: hash, + error, // [!code ++] + isPending, + sendTransaction + } = useSendTransaction() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as `0x${string}` + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+ + + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
Transaction confirmed.
} + {error && ( // [!code ++] +
Error: {(error as BaseError).shortMessage || error.message}
// [!code ++] + )} // [!code ++] +
+ ) +} +``` + +::: + +### 8. Wire it up! + +Finally, we can wire up our Send Transaction component to our application's entrypoint. + +::: code-group + +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider, useAccount } from 'wagmi' +import { config } from './config' +import { SendTransaction } from './send-transaction' // [!code ++] + +const queryClient = new QueryClient() + +function App() { + return ( + + + // [!code ++] + + + ) +} +``` + +```tsx [send-transaction.tsx] +import * as React from 'react' +import { + type BaseError, + useSendTransaction, + useWaitForTransactionReceipt +} from 'wagmi' +import { parseEther } from 'viem' + +export function SendTransaction() { + const { + data: hash, + error, + isPending, + sendTransaction + } = useSendTransaction() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as `0x${string}` + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+ + + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
Transaction confirmed.
} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +[See the Example.](#example) diff --git a/site/react/guides/ssr.md b/site/react/guides/ssr.md new file mode 100644 index 0000000000..131be1a2b7 --- /dev/null +++ b/site/react/guides/ssr.md @@ -0,0 +1,168 @@ +--- +outline: deep +--- + +# SSR + +Wagmi uses client-only external stores (such as `localStorage` and `mipd`) to show the user the most relevant data as quickly as possible on first render. + +However, the caveat of using these external client stores is that frameworks which incorporate SSR (such as Next.js) will throw hydration warnings on the client when it identifies mismatches between the server-rendered HTML and the client-rendered HTML. + +To stop this from happening, you can toggle on the [`ssr`](/react/api/createConfig#ssr) property in the Wagmi Config. + +```tsx +import { createConfig, http } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +const config = createConfig({ // [!code focus:99] + chains: [mainnet, sepolia], + ssr: true, // [!code ++] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +Turning on the `ssr` property means that content from the external stores will be hydrated on the client after the initial mount. + +## Persistence using Cookies + +As a result of turning on the `ssr` property, external persistent stores like `localStorage` will be hydrated on the client **after the initial mount**. + +This means that you will still see a flash of "empty" data on the client (e.g. a `"disconnected"` account instead of a `"reconnecting"` account, or an empty address instead of the last connected address) until after the first mount, when the store hydrates. + +In order to persist data between the server and the client, you can use cookies. + +### 1. Set up cookie storage + +First, we will set up cookie storage in the Wagmi Config. + +```tsx +import { + createConfig, + http, + cookieStorage, // [!code ++] + createStorage // [!code ++] +} from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +export function getConfig() { + return createConfig({ + chains: [mainnet, sepolia], + ssr: true, + storage: createStorage({ // [!code ++] + storage: cookieStorage, // [!code ++] + }), // [!code ++] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, + }) +} +``` + +### 2. Hydrate the cookie + +Next, we will need to add some mechanisms to hydrate the stored cookie in Wagmi. + +#### Next.js App Directory + +In our `app/layout.tsx` file (a [Server Component](https://nextjs.org/docs/app/building-your-application/rendering/server-components)), we will need to extract the cookie from the `headers` function and pass it to [`cookieToInitialState`](/react/api/utilities/cookieToInitialState). + +We will need to pass this result to the [`initialState` property](/react/api/WagmiProvider#initialstate) of the `WagmiProvider`. The `WagmiProvider` **must** be in a Client Component tagged with `"use client"` (see `app/providers.tsx` tab). + +::: code-group +```tsx [app/layout.tsx] +import { type ReactNode } from 'react' +import { headers } from 'next/headers' // [!code ++] +import { cookieToInitialState } from 'wagmi' // [!code ++] + +import { getConfig } from './config' +import { Providers } from './providers' + +export default async function Layout({ children }: { children: ReactNode }) { + const initialState = cookieToInitialState( // [!code ++] + getConfig(), // [!code ++] + (await headers()).get('cookie') // [!code ++] + ) // [!code ++] + return ( + + + // [!code --] + // [!code ++] + {children} + + + + ) +} + +``` + +```tsx [app/providers.tsx] +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { type ReactNode, useState } from 'react' +import { type State, WagmiProvider } from 'wagmi' + +import { getConfig } from './config' + +type Props = { + children: ReactNode, + initialState: State | undefined, // [!code ++] +} + +export function Providers({ children }: Props) { // [!code --] +export function Providers({ children, initialState }: Props) { // [!code ++] + const [config] = useState(() => getConfig()) + const [queryClient] = useState(() => new QueryClient()) + + return ( + // [!code --] + // [!code ++] + + {children} + + + ) +} + +``` + +```tsx [app/config.ts] +import { + createConfig, + http, + cookieStorage, + createStorage +} from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +export function getConfig() { + return createConfig({ + chains: [mainnet, sepolia], + ssr: true, + storage: createStorage({ // [!code ++] + storage: cookieStorage, // [!code ++] + }), // [!code ++] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, + }) +} +``` +::: + +#### Next.js Pages Directory + +Would you like to contribute this content? Feel free to [open a Pull Request](https://github.com/wevm/wagmi/pulls)! + + +#### Vanilla SSR + +Would you like to contribute this content? Feel free to [open a Pull Request](https://github.com/wevm/wagmi/pulls)! + + diff --git a/site/react/guides/tanstack-query.md b/site/react/guides/tanstack-query.md new file mode 100644 index 0000000000..e491695d71 --- /dev/null +++ b/site/react/guides/tanstack-query.md @@ -0,0 +1,403 @@ +# TanStack Query + +Wagmi Hooks are not only a wrapper around the core [Wagmi Actions](/core/api/actions), but they also utilize [TanStack Query](https://tanstack.com/query/v5) to enable trivial and intuitive fetching, caching, synchronizing, and updating of asynchronous data in your React applications. + +Without an asynchronous data fetching abstraction, you would need to handle all the negative side-effects that comes as a result, such as: representing finite states (loading, error, success), handling race conditions, caching against a deterministic identifier, etc. + +## Queries & Mutations + +Wagmi Hooks represent either a **Query** or a **Mutation**. + +**Queries** are used for fetching data (e.g. fetching a block number, reading from a contract, etc), and are typically invoked on mount by default. All queries are coupled to a unique [Query Key](#query-keys), and can be used for further operations such as refetching, prefetching, or modifying the cached data. + +**Mutations** are used for mutating data (e.g. connecting/disconnecting accounts, writing to a contract, switching chains, etc), and are typically invoked in response to a user interaction. Unlike **Queries**, they are not coupled with a query key. + +## Terms + +- **Query**: An asynchronous data fetching (e.g. read data) operation that is tied against a unique Query Key. +- **Mutation**: An asynchronous mutating (e.g. create/update/delete data or side-effect) operation. +- **Query Key**: A unique identifier that is used to deterministically identify a query. It is typically a tuple of the query name and the query arguments. +- **Stale Data**: Data that is unused or inactive after a certain period of time. +- **Query Fetching**: The process of invoking an async query function. +- **Query Refetching**: The process of refetching **rendered** queries. +- **[Query Invalidation](https://tanstack.com/query/v5/docs/react/guides/query-invalidation)**: The process of marking query data as stale (e.g. inactive/unused), and refetching **rendered** queries. +- **[Query Prefetching](https://tanstack.com/query/v5/docs/react/guides/prefetching)**: The process of prefetching queries and seeding the cache. + +## Persistence via External Stores + +By default, TanStack Query persists all query data in-memory. This means that if you refresh the page, all in-memory query data will be lost. + +If you want to persist query data to an external storage, you can utilize TanStack Query's [`createSyncStoragePersister`](https://tanstack.com/query/v5/docs/react/plugins/createSyncStoragePersister) or [`createAsyncStoragePersister`](https://tanstack.com/query/v5/docs/react/plugins/createAsyncStoragePersister) to plug external storage like `localStorage`, `sessionStorage`, [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) or [`AsyncStorage`](https://reactnative.dev/docs/asyncstorage) (React Native). + +### Sync Storage + +Below is an example of how to set up Wagmi + TanStack Query with sync external storage like `localStorage` or `sessionStorage`. + +#### Install + +::: code-group +```bash [pnpm] +pnpm i @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client +``` + +```bash [npm] +npm i @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client +``` + +```bash [yarn] +yarn add @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client +``` + +```bash [bun] +bun i @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client +``` +::: + +#### Usage + +```tsx +// 1. Import modules. // [!code hl] +import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' // [!code hl] +import { QueryClient } from '@tanstack/react-query' // [!code hl] +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' // [!code hl] +import { WagmiProvider, deserialize, serialize } from 'wagmi' // [!code hl] + +// 2. Create a new Query Client with a default `gcTime`. // [!code hl] +const queryClient = new QueryClient({ // [!code hl] + defaultOptions: { // [!code hl] + queries: { // [!code hl] + gcTime: 1_000 * 60 * 60 * 24, // 24 hours // [!code hl] + }, // [!code hl] + }, // [!code hl] +}) // [!code hl] + +// 3. Set up the persister. // [!code hl] +const persister = createSyncStoragePersister({ // [!code hl] + serialize, // [!code hl] + storage: window.localStorage, // [!code hl] + deserialize, // [!code hl] +}) // [!code hl] + +function App() { + return ( + + {/* 4. Wrap app in PersistQueryClientProvider */} // [!code hl] + // [!code hl] + {/* ... */} + // [!code hl] + + ) +} +``` + +Read more about [Sync Storage Persistence](https://tanstack.com/query/v5/docs/react/plugins/createSyncStoragePersister). + +### Async Storage + +Below is an example of how to set up Wagmi + TanStack Query with async external storage like [`IndexedDB`](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) or [`AsyncStorage`](https://reactnative.dev/docs/asyncstorage). + +#### Install + +::: code-group +```bash [pnpm] +pnpm i @tanstack/query-async-storage-persister @tanstack/react-query-persist-client +``` + +```bash [npm] +npm i @tanstack/query-async-storage-persister @tanstack/react-query-persist-client +``` + +```bash [yarn] +yarn add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client +``` + +```bash [bun] +bun i @tanstack/query-async-storage-persister @tanstack/react-query-persist-client +``` +::: + +#### Usage + +```tsx +// 1. Import modules. // [!code hl] +import AsyncStorage from '@react-native-async-storage/async-storage' // [!code hl] +import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister' // [!code hl] +import { QueryClient } from '@tanstack/react-query' // [!code hl] +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' // [!code hl] +import { WagmiProvider, deserialize, serialize } from 'wagmi' // [!code hl] + +// 2. Create a new Query Client with a default `gcTime`. // [!code hl] +const queryClient = new QueryClient({ // [!code hl] + defaultOptions: { // [!code hl] + queries: { // [!code hl] + gcTime: 1_000 * 60 * 60 * 24, // 24 hours // [!code hl] + }, // [!code hl] + }, // [!code hl] +}) // [!code hl] + +// 3. Set up the persister. // [!code hl] +const persister = createAsyncStoragePersister({ // [!code hl] + serialize, // [!code hl] + storage: AsyncStorage, // [!code hl] + deserialize, // [!code hl] +}) // [!code hl] + +function App() { + return ( + + {/* 4. Wrap app in PersistQueryClientProvider */} // [!code hl] + // [!code hl] + {/* ... */} + // [!code hl] + + ) +} +``` + +Read more about [Async Storage Persistence](https://tanstack.com/query/v5/docs/react/plugins/createAsyncStoragePersister). + +## Query Keys + +Query Keys are typically used to perform advanced operations on the query such as: invalidation, refetching, prefetching, etc. + +Wagmi exports Query Keys for every Hook, and they can be retrieved via the [Hook (React)](#hook-react) or via an [Import (Vanilla JS)](#import-vanilla-js). + +Read more about **Query Keys** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/react/guides/query-keys) + +### Hook (React) + +Each Hook returns a `queryKey` value. You would use this approach when you want to utilize the query key in a React component as it handles reactivity for you, unlike the [Import](#import-vanilla-js) method below. + +```ts +import { useBlock } from 'wagmi' // [!code hl] + +function App() { + const { queryKey } = useBlock() // [!code hl] +} +``` + +### Import (Vanilla JS) + +Each Hook has a corresponding `getQueryOptions` function that returns a query key. You would use this method when you want to utilize the query key outside of a React component in a Vanilla JS context, like in a utility function. + +```ts +import { getBlockQueryOptions } from 'wagmi/query' // [!code hl] +import { config } from './config' + +function perform() { + const { queryKey } = getBlockQueryOptions(config, { // [!code hl] + chainId: config.state.chainId // [!code hl] + }) // [!code hl] +} +``` + +::: warning + +The caveat of this method is that it does not handle reactivity for you (e.g. active account/chain changes, argument changes, etc). You would need to handle this yourself by explicitly passing through the arguments to `getQueryOptions`. + +::: + +## Invalidating Queries + +Invalidating a query is the process of marking the query data as stale (e.g. inactive/unused), and refetching the queries that are already rendered. + +Read more about **Invalidating Queries** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/react/guides/query-invalidation) + +#### Example: Watching a Users' Balance + +You may want to "watch" a users' balance, and invalidate the balance after each incoming block. We can invoke `invalidateQueries` inside a `useEffect` with the block number as it's only dependency – this will refetch all rendered balance queries when the `blockNumber` changes. + +```tsx +import { useQueryClient } from '@tanstack/react-query' +import { useEffect } from 'react' +import { useBlockNumber, useBalance } from 'wagmi' + +function App() { + const queryClient = useQueryClient() + const { data: blockNumber } = useBlockNumber({ watch: true }) // [!code hl] + const { data: balance, queryKey } = useBalance() // [!code hl] + + useEffect(() => { // [!code hl] + queryClient.invalidateQueries({ queryKey }) // [!code hl] + }, [blockNumber]) // [!code hl] + + return
{balance}
+} +``` + +#### Example: After User Interaction + +Maybe you want to invalidate a users' balance after some interaction. This would mark the balance as stale, and consequently refetch all rendered balance queries. + +```tsx +import { useBalance } from 'wagmi' + +function App() { + // 1. Extract `queryKey` from the useBalance Hook. // [!code hl] + const { queryKey } = useBalance() // [!code hl] + + return ( + + ) +} + +function Example() { + // 3. Other `useBalance` Hooks in your rendered React tree will be refetched! // [!code hl] + const { data: balance } = useBalance() // [!code hl] + + return
{balance}
+} +``` + +## Fetching Queries + +Fetching a query is the process of invoking the query function to retrieve data. If the query exists and the data is not invalidated or older than a given `staleTime`, then the data from the cache will be returned. Otherwise, the query will fetch for the latest data. + +::: code-group +```tsx [example.tsx] +import { getBlockQueryOptions } from 'wagmi' +import { queryClient } from './app' +import { config } from './config' + +export async function fetchBlockData() { + return queryClient.fetchQuery( // [!code hl] + getBlockQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + } // [!code hl] + )) // [!code hl] +} +``` +<<< @/snippets/react/app.tsx[app.tsx] +<<< @/snippets/react/config.ts[config.ts] +::: + +## Retrieving & Updating Query Data + +You can retrieve and update query data imperatively with `getQueryData` and `setQueryData`. This is useful for scenarios where you want to retrieve or update a query outside of a React component. + +Note that these functions do not invalidate or refetch queries. + +::: code-group +```tsx [example.tsx] +import { getBlockQueryOptions } from 'wagmi' +import type { Block } from 'viem' +import { queryClient } from './app' +import { config } from './config' + +export function getPendingBlockData() { + return queryClient.getQueryData( // [!code hl] + getBlockQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + tag: 'pending' // [!code hl] + } // [!code hl] + )) // [!code hl] +} + +export function setPendingBlockData(data: Block) { + return queryClient.setQueryData( // [!code hl] + getBlockQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + tag: 'pending' // [!code hl] + }, // [!code hl] + data // [!code hl] + )) // [!code hl] +} +``` +<<< @/snippets/react/app.tsx[app.tsx] +<<< @/snippets/react/config.ts[config.ts] +::: + +## Prefetching Queries + +Prefetching a query is the process of fetching the data ahead of time and seeding the cache with the returned data. This is useful for scenarios where you want to fetch data before the user navigates to a page, or fetching data on the server to be reused on client hydration. + +Read more about **Prefetching Queries** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/react/guides/prefetching) + +#### Example: Prefetching in Event Handler + +```tsx +import { Link } from 'next/link' +import { getBlockQueryOptions } from 'wagmi' + +function App() { + const config = useConfig() + const chainId = useChainId() + + // 1. Set up a function to prefetch the block data. // [!code hl] + const prefetch = () => // [!code hl] + queryClient.prefetchQuery(getBlockQueryOptions(config, { chainId })) // [!code hl] + + + return ( + + Block details + + ) +} +``` + +## SSR + +It is possible to utilize TanStack Query's SSR strategies with Wagmi Hooks & Query Keys. Check out the [Server Rendering & Hydration](https://tanstack.com/query/v5/docs/react/guides/ssr) & [Advanced Server Rendering](https://tanstack.com/query/v5/docs/react/guides/advanced-ssr) guides. + +## Devtools + +TanStack Query includes dedicated [Devtools](https://tanstack.com/query/latest/docs/framework/react/devtools) that assist in visualizing and debugging your queries, their cache states, and much more. You will have to pass a custom `queryKeyFn` to your `QueryClient` for Devtools to correctly serialize BigInt values for display. Alternatively, You can use the `hashFn` from `@wagmi/core/query`, which already handles this serialization. + +#### Install + +::: code-group +```bash [pnpm] +pnpm i @tanstack/react-query-devtools +``` + +```bash [npm] +npm i @tanstack/react-query-devtools +``` + +```bash [yarn] +yarn add @tanstack/react-query-devtools +``` + +```bash [bun] +bun i @tanstack/react-query-devtools +``` +::: + +#### Usage + +```tsx +import { + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; // [!code hl] +import { hashFn } from "@wagmi/core/query"; // [!code hl] + +const queryClient = new QueryClient({ + defaultOptions: { // [!code hl] + queries: { // [!code hl] + queryKeyHashFn: hashFn, // [!code hl] + }, // [!code hl] + }, // [!code hl] +}); +``` diff --git a/site/react/guides/testing.md b/site/react/guides/testing.md new file mode 100644 index 0000000000..fd198fa058 --- /dev/null +++ b/site/react/guides/testing.md @@ -0,0 +1,2 @@ +# Testing + diff --git a/site/react/guides/viem.md b/site/react/guides/viem.md new file mode 100644 index 0000000000..6539b3fe61 --- /dev/null +++ b/site/react/guides/viem.md @@ -0,0 +1,150 @@ +# Viem + +[Viem](https://viem.sh) is a low-level TypeScript Interface for Ethereum that enables developers to interact with the Ethereum blockchain, including: JSON-RPC API abstractions, Smart Contract interaction, wallet & signing implementations, coding/parsing utilities and more. + +**Wagmi Core** is essentially a wrapper over **Viem** that provides multi-chain functionality via [Wagmi Config](/react/api/createConfig) and automatic account management via [Connectors](/react/api/connectors). + +## Leveraging Viem Actions + +All of the core [Wagmi Hooks](/react/api/actions) are friendly wrappers around [Viem Actions](https://viem.sh/docs/actions/public/introduction.html) that inject a multi-chain and connector aware [Wagmi Config](/react/api/createConfig). + +There may be cases where you might want to dig deeper and utilize Viem Actions directly (maybe a Hook doesn't exist in Wagmi yet). In these cases, you can create your own custom Wagmi Hook by importing Viem Actions directly via `viem/actions` and plugging in a Viem Client returned by the [`useClient` Hook](/react/api/hooks/useClient). + +The example below demonstrates two different ways to utilize Viem Actions: + +1. **Tree-shakable Actions (recommended):** Uses `useClient` (for public actions) and `useConnectorClient` (for wallet actions). +2. **Client Actions:** Uses `usePublicClient` (for public actions) and `useWalletClient` (for wallet actions). + +::: tip + +It is highly recommended to use the **tree-shakable** method to ensure that you are only pulling modules you use, and keep your bundle size low. + +::: + +::: code-group + +```tsx [Tree-shakable Actions] +// 1. Import modules. +import { useMutation, useQuery } from '@tanstack/react-query' +import { http, createConfig, useClient, useConnectorClient } from 'wagmi' +import { base, mainnet, optimism, zora } from 'wagmi/chains' +import { getLogs, watchAsset } from 'viem/actions' + +// 2. Set up a Wagmi Config +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +function Example() { + // 3. Extract a Viem Client for the current active chain. // [!code hl] + const publicClient = useClient({ config }) // [!code hl] + + // 4. Create a "custom" Query Hook that utilizes the Client. // [!code hl] + const { data: logs } = useQuery({ // [!code hl] + queryKey: ['logs', publicClient.uid], // [!code hl] + queryFn: () => getLogs(publicClient, /* ... */) // [!code hl] + }) // [!code hl] + + // 5. Extract a Viem Client for the current active chain & account. // [!code hl] + const { data: walletClient } = useConnectorClient({ config }) // [!code hl] + + // 6. Create a "custom" Mutation Hook that utilizes the Client. // [!code hl] + const { mutate } = useMutation({ // [!code hl] + mutationFn: (asset) => watchAsset(walletClient, asset) // [!code hl] + }) // [!code hl] + + return ( +
+ {/* ... */} +
+ ) +} +``` + +```tsx [Client Actions] +// 1. Import modules. +import { useMutation, useQuery } from '@tanstack/react-query' +import { http, createConfig, useClient, useConnectorClient } from 'wagmi' +import { base, mainnet, optimism, zora } from 'wagmi/chains' + +// 2. Set up a Wagmi Config +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +function Example() { + // 3. Extract a Viem Client for the current active chain. // [!code hl] + const publicClient = useClient({ config }) // [!code hl] + + // 4. Create a "custom" Query Hook that utilizes the Client. // [!code hl] + const { data: logs } = useQuery({ // [!code hl] + queryKey: ['logs', publicClient.uid], // [!code hl] + queryFn: () => publicClient.getLogs(/* ... */) // [!code hl] + }) // [!code hl] + + // 5. Extract a Viem Client for the current active chain & account. // [!code hl] + const { data: walletClient } = useConnectorClient({ config }) // [!code hl] + + // 6. Create a "custom" Mutation Hook that utilizes the Client. // [!code hl] + const { mutate } = useMutation({ // [!code hl] + mutationFn: (asset) => walletClient.watchAsset(asset) // [!code hl] + }) // [!code hl] + + return ( +
+ {/* ... */} +
+ ) +} +``` + +::: + +## Private Key & Mnemonic Accounts + +It is possible to utilize Viem's [Private Key & Mnemonic Accounts](https://viem.sh/docs/accounts/local.html) with Wagmi by explicitly passing through the account via the `account` argument on Wagmi Actions. + +```tsx +import { http, createConfig, useSendTransaction } from 'wagmi' +import { base, mainnet, optimism, zora } from 'wagmi/chains' +import { parseEther } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +export const config = createConfig({ + chains: [base, mainnet, optimism, zora], + transports: { + [base.id]: http(), + [mainnet.id]: http(), + [optimism.id]: http(), + [zora.id]: http(), + }, +}) + +const account = privateKeyToAccount('0x...') // [!code hl] + +function Example() { + const { data: hash } = useSendTransaction({ + account, // [!code hl] + to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + value: parseEther('0.001') + }) +} +``` + +::: info + +Wagmi currently does not support hoisting Private Key & Mnemonic Accounts to the top-level Wagmi Config – meaning you have to explicitly pass through the account to every Action. If you feel like this is a feature that should be added, please [open an discussion](https://github.com/wevm/wagmi/discussions/new?category=ideas). + +::: diff --git a/site/react/guides/write-to-contract.md b/site/react/guides/write-to-contract.md new file mode 100644 index 0000000000..65914ae981 --- /dev/null +++ b/site/react/guides/write-to-contract.md @@ -0,0 +1,438 @@ +# Write to Contract + +The [`useWriteContract` Hook](/react/api/hooks/useWriteContract) allows you to mutate data on a smart contract, from a `payable` or `nonpayable` (write) function. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state. + +In the guide below, we will teach you how to implement a "Mint NFT" form that takes in a dynamic argument (token ID) using Wagmi. The example below builds on the [Connect Wallet guide](/react/guides/connect-wallet) and uses the [useWriteContract](/react/api/hooks/useWriteContract) & [useWaitForTransaction](/react/api/hooks/useWaitForTransactionReceipt) hooks. + +If you have already completed the [Sending Transactions guide](/react/guides/send-transaction), this guide will look very similar! That's because writing to a contract internally broadcasts & sends a transaction. + +## Example + +Feel free to check out the example before moving on: + + + +## Steps + +### 1. Connect Wallet + +Follow the [Connect Wallet guide](/react/guides/connect-wallet) guide to get this set up. + +### 2. Create a new component + +Create your `MintNFT` component that will contain the Mint NFT logic. + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' + +export function MintNFT() { + return ( +
+ + +
+ ) +} +``` + +::: + +### 3. Add a form handler + +Next, we will need to add a handler to the form that will send the transaction when the user hits "Mint". This will be a basic handler in this step. + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' + +export function MintNFT() { + async function submit(e: React.FormEvent) { // [!code ++] + e.preventDefault() // [!code ++] + const formData = new FormData(e.target as HTMLFormElement) // [!code ++] + const tokenId = formData.get('tokenId') as string // [!code ++] + } // [!code ++] + + return ( +
// [!code --] + // [!code ++] + + +
+ ) +} +``` + +::: + +### 4. Hook up the `useWriteContract` Hook + +Now that we have the form handler, we can hook up the [`useWriteContract` Hook](/react/api/hooks/useWriteContract) to send the transaction. + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' +import { useWriteContract } from 'wagmi' // [!code ++] +import { abi } from './abi' // [!code ++] + +export function MintNFT() { + const { data: hash, writeContract } = useWriteContract() // [!code ++] + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ // [!code ++] + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code ++] + abi, // [!code ++] + functionName: 'mint', // [!code ++] + args: [BigInt(tokenId)], // [!code ++] + }) // [!code ++] + } + + return ( +
+ + + {hash &&
Transaction Hash: {hash}
} // [!code ++] +
+ ) +} +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 5. Add loading state (optional) + +We can optionally add a loading state to the "Mint" button while we are waiting confirmation from the user's wallet. + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' +import { useWriteContract } from 'wagmi' +import { abi } from './abi' + +export function MintNFT() { + const { + data: hash, + isPending, // [!code ++] + writeContract + } = useWriteContract() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + return ( +
+ + + {hash &&
Transaction Hash: {hash}
} +
+ ) +} +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 6. Wait for transaction receipt (optional) + +We can also display the transaction confirmation status to the user by using the [`useWaitForTransactionReceipt` Hook](/react/api/hooks/useWaitForTransactionReceipt). + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' +import { + useWaitForTransactionReceipt, // [!code ++] + useWriteContract +} from 'wagmi' +import { abi } from './abi' + +export function MintNFT() { + const { + data: hash, + isPending, + writeContract + } = useWriteContract() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = // [!code ++] + useWaitForTransactionReceipt({ // [!code ++] + hash, // [!code ++] + }) // [!code ++] + + return ( +
+ + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} // [!code ++] + {isConfirmed &&
Transaction confirmed.
} // [!code ++] +
+ ) +} +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 7. Handle errors (optional) + +If the user rejects the transaction, or the contract reverts, we can display an error message to the user. + +::: code-group + +```tsx [mint-nft.tsx] +import * as React from 'react' +import { + type BaseError, // [!code ++] + useWaitForTransactionReceipt, + useWriteContract +} from 'wagmi' +import { abi } from './abi' + +export function MintNFT() { + const { + data: hash, + error, // [!code ++] + isPending, + writeContract + } = useWriteContract() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+ + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
Transaction confirmed.
} + {error && ( // [!code ++] +
Error: {(error as BaseError).shortMessage || error.message}
// [!code ++] + )} // [!code ++] +
+ ) +} +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 8. Wire it up! + +Finally, we can wire up our Mint NFT component to our application's entrypoint. + +::: code-group + +```tsx [app.tsx] +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider, useAccount } from 'wagmi' +import { config } from './config' +import { MintNft } from './mint-nft' // [!code ++] + +const queryClient = new QueryClient() + +function App() { + return ( + + + // [!code ++] + + + ) +} +``` + +```tsx [mint-nft.tsx] +import * as React from 'react' +import { + type BaseError, + useWaitForTransactionReceipt, + useWriteContract +} from 'wagmi' +import { abi } from './abi' + +export function MintNFT() { + const { + data: hash, + error, + isPending, + writeContract + } = useWriteContract() + + async function submit(e: React.FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+ + + {hash &&
Transaction Hash: {hash}
} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
Transaction confirmed.
} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +```tsx [config.ts] +import { http, createConfig } from 'wagmi' +import { base, mainnet, optimism } from 'wagmi/chains' +import { injected, metaMask, safe, walletConnect } from 'wagmi/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +[See the Example.](#example) diff --git a/site/react/installation.md b/site/react/installation.md new file mode 100644 index 0000000000..7dd8306796 --- /dev/null +++ b/site/react/installation.md @@ -0,0 +1,56 @@ + + +# Installation + +Install Wagmi via your package manager, a ` +``` + +Check out the React docs for info on how to use [React without JSX](https://react.dev/reference/react/createElement#creating-an-element-without-jsx). + + diff --git a/site/react/typescript.md b/site/react/typescript.md new file mode 100644 index 0000000000..1ed9d4246e --- /dev/null +++ b/site/react/typescript.md @@ -0,0 +1,302 @@ + + +# TypeScript + +## Requirements + +Wagmi is designed to be as type-safe as possible! Things to keep in mind: + +- Types currently require using TypeScript {{typescriptVersion}}. +- [TypeScript doesn't follow semver](https://www.learningtypescript.com/articles/why-typescript-doesnt-follow-strict-semantic-versioning) and often introduces breaking changes in minor releases. +- Changes to types in this repository are considered non-breaking and are usually released as patch changes (otherwise every type enhancement would be a major version!). +- It is highly recommended that you lock your `wagmi` and `typescript` versions to specific patch releases and upgrade with the expectation that types may be fixed or upgraded between any release. +- The non-type-related public API of Wagmi still follows semver very strictly. + +To ensure everything works correctly, make sure your `tsconfig.json` has [`strict`](https://www.typescriptlang.org/tsconfig#strict) mode set to `true`. + +::: code-group +```json [tsconfig.json] +{ + "compilerOptions": { + "strict": true + } +} +``` +::: + +## Config Types + +By default React Context does not work well with type inference. To support strong type-safety across the React Context boundary, there are two options available: + +- Declaration merging to "register" your `config` globally with TypeScript. +- `config` property to pass your `config` directly to hooks. + +### Declaration Merging + +[Declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) allows you to "register" your `config` globally with TypeScript. The `Register` type enables Wagmi to infer types in places that wouldn't normally have access to type info via React Context alone. + +To set this up, add the following declaration to your project. Below, we co-locate the declaration merging and the `config` set up. + +```ts +import { createConfig, http } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module 'wagmi' { // [!code focus] + interface Register { // [!code focus] + config: typeof config // [!code focus] + } // [!code focus] +} // [!code focus] + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +Since the `Register` type is global, you only need to add it once in your project. Once set up, you will get strong type-safety across your entire project. For example, query hooks will type `chainId` based on your `config`'s `chains`. + +```ts twoslash +// @errors: 2322 +import { type Config } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module 'wagmi' { + interface Register { + config: Config + } +} +// ---cut--- +import { useBlockNumber } from 'wagmi' + +useBlockNumber({ chainId: 123 }) +``` + +You just saved yourself a runtime error and you didn't even need to pass your `config`. 🎉 + +### Hook `config` Property + +For cases where you have more than one Wagmi `config` or don't want to use the declaration merging approach, you can pass a specific `config` directly to hooks via the `config` property. + +```ts +import { createConfig, http } from 'wagmi' +import { mainnet, optimism } from 'wagmi/chains' + +export const configA = createConfig({ // [!code focus] + chains: [mainnet], // [!code focus] + transports: { // [!code focus] + [mainnet.id]: http(), // [!code focus] + }, // [!code focus] +}) // [!code focus] + +export const configB = createConfig({ // [!code focus] + chains: [optimism], // [!code focus] + transports: { // [!code focus] + [optimism.id]: http(), // [!code focus] + }, // [!code focus] +}) // [!code focus] +``` + +As you expect, `chainId` is inferred correctly for each `config`. + +```ts twoslash +// @errors: 2322 +import { type Config } from 'wagmi' +import { mainnet, optimism } from 'wagmi/chains' + +declare const configA: Config +declare const configB: Config +// ---cut--- +import { useBlockNumber } from 'wagmi' + +useBlockNumber({ chainId: 123, config: configA }) +useBlockNumber({ chainId: 123, config: configB }) +``` + +This approach is more explicit, but works well for advanced use-cases, if you don't want to use React Context or declaration merging, etc. + +## Const-Assert ABIs & Typed Data + +Wagmi can infer types based on [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html#json) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data definitions, powered by [Viem](https://viem.sh) and [ABIType](https://github.com/wevm/abitype). This achieves full end-to-end type-safety from your contracts to your frontend and enlightened developer experience by autocompleting ABI item names, catching misspellings, inferring argument and return types (including overloads), and more. + +For this to work, you must either [const-assert](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) ABIs and Typed Data (more info below) or define them inline. For example, `useReadContract`'s `abi` configuration parameter: + +```ts +const { data } = useReadContract({ + abi: […], // <--- defined inline // [!code focus] +}) +``` + +```ts +const abi = […] as const // <--- const assertion // [!code focus] +const { data } = useReadContract({ abi }) +``` + +If type inference isn't working, it's likely you forgot to add a `const` assertion or define the configuration parameter inline. Also, make sure your ABIs, Typed Data definitions, and [TypeScript configuration](#requirements) are valid and set up correctly. + +::: tip +Unfortunately [TypeScript doesn't support importing JSON `as const` yet](https://github.com/microsoft/TypeScript/issues/32063). Check out the [Wagmi CLI](/cli/getting-started) to help with this! It can automatically fetch ABIs from Etherscan and other block explorers, resolve ABIs from your Foundry/Hardhat projects, generate React Hooks, and more. +::: + +Anywhere you see the `abi` or `types` configuration property, you can likely use const-asserted or inline ABIs and Typed Data to get type-safety and inference. These properties are also called out in the docs. + +Here's what [`useReadContract`](/react/api/hooks/useReadContract) looks like with and without a const-asserted `abi` property. + +::: code-group +```ts twoslash [Const-Asserted] +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { useReadContract } from 'wagmi' + +const { data } = useReadContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +data +// ^? +``` +```ts twoslash [Not Const-Asserted] +declare const erc721Abi: { + name: string; + type: string; + stateMutability: string; + inputs: { + type: string; + name: string; + }[]; + outputs: { + type: string; + }[]; +}[] +// ---cut--- +import { useReadContract } from 'wagmi' + +const { data } = useReadContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +data +// ^? +``` +::: + +
+
+ +You can prevent runtime errors and be more productive by making sure your ABIs and Typed Data definitions are set up appropriately. 🎉 + +```ts twoslash +// @errors: 2820 +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { useReadContract } from 'wagmi' + +useReadContract({ + abi: erc721Abi, + functionName: 'balanecOf', +}) +``` + +## Configure Internal Types + +For advanced use-cases, you may want to configure Wagmi's internal types. Most of Wagmi's types relating to ABIs and EIP-712 Typed Data are powered by [ABIType](https://github.com/wevm/abitype). See the [ABIType docs](https://abitype.dev) for more info on how to configure types. diff --git a/site/react/why.md b/site/react/why.md new file mode 100644 index 0000000000..3b4bf2b89b --- /dev/null +++ b/site/react/why.md @@ -0,0 +1,46 @@ +# Why Wagmi + +## The Problems + +Building Ethereum applications is hard. Apps need to support connecting wallets, multiple chains, signing messages and data, sending transactions, listening for events and state changes, refreshing stale blockchain data, and much more. This is all on top of solving for app-specific use-cases and providing polished user experiences. + +The ecosystem is also continuously evolving, meaning you need to adapt to new improvements or get left behind. App developers should not need to worry about connecting tens of different wallets, the intricacies of multi-chain support, typos accidentally sending an order of magnitude more ETH or calling a misspelled contract function, or accidentally spamming their RPC provider, costing thousands in compute units. + +Wagmi solves all these problems and more — allowing app developers to focus on building high-quality and performant experiences for Ethereum — by focusing on **developer experience**, **performance**, **feature coverage**, and **stability.** + +## Developer Experience + +Wagmi delivers a great developer experience through modular and composable APIs, automatic type safety and inference, and comprehensive documentation. + +It provides developers with intuitive building blocks to build their Ethereum apps. While Wagmi's APIs might seem more verbose at first, it makes Wagmi's modular building blocks extremely flexible. Easy to move around, change, and remove. It also allows developers to better understand Ethereum concepts as well as understand _what_ and _why_ certain properties are being passed through. Learning how to use Wagmi is a great way to learn how to interact with Ethereum in general. + +Wagmi also provides [strongly typed APIs](/react/typescript), allowing consumers to get the best possible experience through [autocomplete](https://twitter.com/awkweb/status/1555678944770367493), [type inference](https://twitter.com/jakemoxey/status/1570244174502588417?s=20), as well as static validation. You often just need to provide an ABI and Wagmi can help you autocomplete your way to success, identify type errors before your users do, drill into blockchain errors [at compile and runtimes](/react/guides/error-handling) with surgical precision, and much more. + +The API documentation is comprehensive and contains usage info for _every_ module in Wagmi. The core team uses a [documentation](https://gist.github.com/zsup/9434452) and [test driven](https://en.wikipedia.org/wiki/Test-driven_development#:~:text=Test%2Ddriven%20development%20(TDD),software%20against%20all%20test%20cases.) development approach to building modules, which leads to predictable and stable APIs. + +## Performance + +Performance is critical for applications on all sizes. Slow page load and interactions can cause users to stop using applications. Wagmi uses and is built by the same team behind [Viem](https://viem.sh), the most performant production-ready Ethereum library. + +End users should not be required to download a module of over 100kB in order to interact with Ethereum. Wagmi is optimized for tree-shaking and dead-code elimination, allowing apps to minimize bundle size for fast page load times. + +Data layer performance is also critical. Slow, unnecessary, and manual data fetching can make apps unusable and cost thousands in RPC compute units. Wagmi supports caching, deduplication, persistence, and much more through [TanStack Query](/react/guides/tanstack-query). + +## Feature Coverage + +Wagmi supports the most popular and commonly-used Ethereum features out of the box with 40+ React Hooks for accounts, wallets, contracts, transactions, signing, ENS, and more. Wagmi also supports just about any wallet out there through official [connectors](/react/api/connectors), [EIP-6963 support](/react/api/createConfig#multiinjectedproviderdiscovery), and [extensible API](/dev/creating-connectors). + +If you need lower-level control, you can always drop down to [Wagmi Core](/core/getting-started) or [Viem](https://viem.sh), which Wagmi uses internally to perform blockchain operations. Wagmi also manages multi-chain support automatically so developers can focus on their applications instead of adding custom code. + +Finally, Wagmi has a [CLI](/cli/getting-started) to manage ABIs as well as a robust ecosystem of third-party libraries, like [ConnectKit](https://docs.family.co/connectkit), [RainbowKit](https://www.rainbowkit.com), [AppKit](https://walletconnect.com/appkit), [Dynamic](https://www.dynamic.xyz), [Privy](https://privy.io), and many more, so you can get started quickly without needing to build everything from scratch. + +## Stability + +Stability is a fundamental principle for Wagmi. Many organizations, large and small, rely heavily on Wagmi and expect it to be entirely stable for their users and applications. + +Wagmi's test suite runs against forked Ethereum nodes to make sure functions work across chains. The test suite also runs type tests against many different versions of peer dependencies, like TypeScript, to ensure compatibility with the latest releases of other popular software. + +Wagmi follows semver so developers can upgrade between versions with confidence. Starting with Wagmi v2, new functionality will be opt-in with old functionality being deprecated alongside the new features. This means upgrading to the latest major versions will not require immediate changes. + +Lastly, the core team works full-time on Wagmi and [related projects](https://github.com/wevm), and is constantly improving Wagmi and keeping it up-to-date with industry trends and changes. + diff --git a/site/shared/connectors/coinbaseWallet.md b/site/shared/connectors/coinbaseWallet.md new file mode 100644 index 0000000000..de70807aef --- /dev/null +++ b/site/shared/connectors/coinbaseWallet.md @@ -0,0 +1,160 @@ + + +# coinbaseWallet + +Connector for the [Coinbase Wallet SDK](https://github.com/coinbase/coinbase-wallet-sdk). + +## Import + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { coinbaseWallet } from '{{connectorsPackageName}}' // [!code hl] + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [coinbaseWallet()], // [!code hl] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +:::warning +Before going to production, it is highly recommended to set an [`appName`](#appname) and [`appLogoUrl`](#applogourl) for your application that can be displayed upon connection to the wallet. +::: + +## Parameters + +```ts-vue +import { type CoinbaseWalletParameters } from '{{connectorsPackageName}}' +``` + +Check out the [Coinbase Wallet SDK docs](https://github.com/coinbase/coinbase-wallet-sdk) for more info. + +### appName + +`string` + +Application name. + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', // [!code focus] +}) +``` + +### appLogoUrl + +`string | null | undefined` + +Application logo image URL; favicon is used if unspecified. + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', + appLogoUrl: 'https://example.com/myLogoUrl.png', // [!code focus] +}) +``` + +### headlessMode + +`boolean | undefined` + +- Whether or not onboarding overlay popup should be displayed. +- `headlessMode` will be removed in the next major version. Upgrade to [`version: '4'`](#version). + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', + headlessMode: false, // [!code focus] +}) +``` + +### preference + +`"all" | "eoaOnly" | "smartWalletOnly"` + +Preference for the type of wallet to display. + +- `'eoaOnly'`: Uses EOA Browser Extension or Mobile Coinbase Wallet. +- `'smartWalletOnly'`: Displays Smart Wallet popup. +- `'all'` (default): Supports both `'eoaOnly'` and `'smartWalletOnly'` based on context. + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', + preference: 'smartWalletOnly', // [!code focus] +}) +``` + +::: warning +Passing `preference` as a string is deprecated and will be removed in the next major version. Instead you should use [`preference#options`](#options). +::: + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', + preference: { // [!code focus] + options: 'smartWalletOnly' // [!code focus] + }, // [!code focus] +}) +``` + +#### attribution + +`` { auto?: boolean | undefined; dataSuffix?: `0x${string}` | undefined } `` + +This option only applies to Coinbase Smart Wallet. When a valid data suffix is supplied, it is appended to the `initCode` and `executeBatch` calldata. Coinbase Smart Wallet expects a 16 byte hex string. If the data suffix is not a 16 byte hex string, the Smart Wallet will ignore the property. If auto is true, the Smart Wallet will generate a 16 byte hex string from the apps origin. + +#### keysUrl + +`string` + +- The URL for the keys popup. +- By default, `https://keys.coinbase.com/connect` is used for production. Use `https://keys-dev.coinbase.com/connect` for development environments. + +#### options + +`"all" | "eoaOnly" | "smartWalletOnly"` + +Preference for the type of wallet to display. + +- `'eoaOnly'`: Uses EOA Browser Extension or Mobile Coinbase Wallet. +- `'smartWalletOnly'`: Displays Smart Wallet popup. +- `'all'` (default): Supports both `'eoaOnly'` and `'smartWalletOnly'` based on context. + +### version + +- Coinbase Wallet SDK version +- Defaults to `'4'`. If [`headlessMode: true`](#headlessmode), defaults to `'3'`. + +```ts-vue +import { coinbaseWallet } from '{{connectorsPackageName}}' + +const connector = coinbaseWallet({ + appName: 'My Wagmi App', + version: '4', // [!code focus] +}) +``` + diff --git a/site/shared/connectors/injected.md b/site/shared/connectors/injected.md new file mode 100644 index 0000000000..36583b4d83 --- /dev/null +++ b/site/shared/connectors/injected.md @@ -0,0 +1,89 @@ + + +# injected + +Connector for [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Providers. + +## Import + +```ts-vue +import { injected } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue{3,7} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { injected } from '{{connectorsPackageName}}' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [injected()], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +## Parameters + +```ts-vue +import { type InjectedParameters } from '{{connectorsPackageName}}' +``` + +### shimDisconnect + +`boolean | undefined` + +- MetaMask and other injected providers do not support programmatic disconnect. +- This flag simulates the disconnect behavior by keeping track of connection status in storage. See [GitHub issue](https://github.com/MetaMask/metamask-extension/issues/10353) for more info. +- Defaults to `true`. + +```ts-vue +import { injected } from '{{connectorsPackageName}}' + +const connector = injected({ + shimDisconnect: false, // [!code focus] +}) +``` + +### target + +`TargetId | (TargetMap[TargetId] & { id: string }) | (() => (TargetMap[TargetId] & { id: string }) | undefined) | undefined` + +- [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) Ethereum Provider to target. +- [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) supported via `createConfig`'s `multiInjectedProviderDiscovery` property. + +```ts-vue +import { injected } from '{{connectorsPackageName}}' + +const connector = injected({ + target() { // [!code focus] + return { // [!code focus] + id: 'windowProvider', // [!code focus] + name: 'Window Provider', // [!code focus] + provider: window.ethereum, // [!code focus] + } // [!code focus] + }, // [!code focus] +}) +``` + +### unstable_shimAsyncInject + +`boolean | number | undefined` + +Watches for async provider injection via the `ethereum#initialized` event. When `true`, defaults to `1_000` milliseconds. Otherwise, uses a provided value of milliseconds. + +```ts-vue +import { injected } from '{{connectorsPackageName}}' + +const connector = injected({ + unstable_shimAsyncInject: 2_000, // [!code focus] +}) +``` diff --git a/site/shared/connectors/metaMask.md b/site/shared/connectors/metaMask.md new file mode 100644 index 0000000000..08a6f4f9cd --- /dev/null +++ b/site/shared/connectors/metaMask.md @@ -0,0 +1,124 @@ + + +# metaMask + +Connector for [MetaMask SDK](https://github.com/MetaMask/metamask-sdk). + +Check out the [MetaMask SDK docs](https://docs.metamask.io/wallet/connect/metamask-sdk/javascript) for more information. + +## Import + +```ts-vue +import { metaMask } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue{3,7} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { metaMask } from '{{connectorsPackageName}}' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [metaMask()], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +## Parameters + +```ts-vue +import { type MetaMaskParameters } from '{{connectorsPackageName}}' +``` + +Check out the [MetaMask SDK docs](https://docs.metamask.io/wallet/connect/3rd-party-libraries/wagmi/) for more info. A few options are omitted that Wagmi manages internally. + +### dappMetadata + +`DappMetadata | undefined` + +Metadata is used to fill details for the UX on confirmation screens in MetaMask, including the following fields: + +- `name`: `string` - The name of the dapp. +- `url`: `string` - URL of the dapp (defaults to `window.location.origin`). +- `iconUrl`: `string` - URL to the dapp's favicon or icon. + +```ts-vue +import { metaMask } from '{{connectorsPackageName}}' + +const connector = metaMask({ + dappMetadata: { // [!code focus] + name: 'My Wagmi App', // [!code focus] + url: 'https://example.com', // [!code focus] + iconUrl: 'https://example.com/favicon.ico', // [!code focus] + } +}) +``` + +### logging + +`SDKLoggingOptions | undefined` + +Enables SDK-side logging to provide visibility into: + +- RPC methods being called. +- Events received for syncing the chain or active account. +- Raw RPC responses. + +In this context, this is especially useful to observe what calls are made through Wagmi hooks. + +Relevant options: + +```ts +{ + developerMode: boolean, // Enables developer mode logs + sdk: boolean // Enables SDK-specific logs +} +``` + +```ts +import { metaMask } from '{{connectorsPackageName}}' + +const connector = metaMask({ + logging: { developerMode: true, sdk: true } // [!code focus] +}) +``` + +### headless + +`boolean | undefined` + +- Enables headless mode, disabling MetaMask's built-in modal. +- Allows developers to create their own modal, such as for displaying a QR code. + +This is particularly relevant for web-only setups using Wagmi, where developers want complete control over the UI. + +To get the deeplink to display in the QR code, listen to the `display_uri` event. + +The default is `false`. + +```ts-vue +import { metaMask } from '{{connectorsPackageName}}' + +const connector = metaMask({ + headless: true // [!code focus] +}) +``` + +## Advanced + +By default, if the EIP-6963 MetaMask injected provider is detected, this connector will replace it. + +EIP-6963 defines a standard way for dapps to interact with multiple wallets simultaneously by injecting providers into the browser. Wallets that implement this standard can make their presence known to dapps in a consistent and predictable manner. + +When MetaMask SDK detects an EIP-6963-compliant provider (such as MetaMask itself), the connector will automatically replace the default injected provider (like `window.ethereum`) with the one provided by MetaMask SDK. + +See the [`rdns` property](https://wagmi.sh/dev/creating-connectors#properties) for more information. diff --git a/site/shared/connectors/mock.md b/site/shared/connectors/mock.md new file mode 100644 index 0000000000..90d87b0b3e --- /dev/null +++ b/site/shared/connectors/mock.md @@ -0,0 +1,128 @@ + + +# mock + +Connector for mocking Wagmi functionality. + +## Import + +```ts-vue +import { mock } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue{3,8-14} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { mock } from '{{connectorsPackageName}}' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [ + mock({ + accounts: [ + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', + ], + }), + ], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +## Parameters + +```ts-vue +import { type MockParameters } from '{{connectorsPackageName}}' +``` + +### accounts + +`readonly [Address, ...Address[]]` + +Accounts to use with the connector. + +```ts-vue +import { mock } from '{{connectorsPackageName}}' + +const connector = mock({ + accounts: [ // [!code focus] + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus] + '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // [!code focus] + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', // [!code focus] + '0x90F79bf6EB2c4f870365E785982E1f101E93b906', // [!code focus] + '0x15d34aaf54267db7d7c367839aaf71a00a2c6a65', // [!code focus] + '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', // [!code focus] + '0x976EA74026E726554dB657fA54763abd0C3a0aa9', // [!code focus] + '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // [!code focus] + '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', // [!code focus] + '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720', // [!code focus] + ], // [!code focus] +}) +``` + +### features + +`{ connectError?: boolean | Error | undefined; reconnect?: boolean | undefined; signMessageError?: boolean | Error | undefined; signTypedDataError?: boolean | Error | undefined; switchChainError?: boolean | Error | undefined; } | undefined` + +Feature flags that change behavior of Wagmi internals. + +```ts-vue +import { mock } from '{{connectorsPackageName}}' +import { UserRejectedRequestError } from 'viem' + +const connector = mock({ + accounts: [ + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', + ], + features: { // [!code focus] + connectError: new UserRejectedRequestError(new Error('Failed to connect.')), // [!code focus] + reconnect: false, // [!code focus] + }, // [!code focus] +}) +``` +#### defaultConnected + +`boolean | undefined` + +Whether the connector is connected by default. + +#### connectError + +`boolean | Error | undefined` + +Whether to throw an error when `connector.connect` is called. + +#### reconnect + +`boolean | undefined` + +Enables reconnecting to connector. + +#### signMessageError + +`boolean | Error | undefined` + +Whether to throw an error when `'personal_sign'` is called. + +#### signTypedDataError + +`boolean | Error | undefined` + +Whether to throw an error when `'eth_signTypedData_v4'` is called. + +#### switchChainError + +`boolean | Error | undefined` + +Whether to throw an error when `connector.switchChain` is called. diff --git a/site/shared/connectors/safe.md b/site/shared/connectors/safe.md new file mode 100644 index 0000000000..171010214b --- /dev/null +++ b/site/shared/connectors/safe.md @@ -0,0 +1,77 @@ + + +# safe + +Connector for [Safe Apps SDK](https://github.com/safe-global/safe-apps-sdk). + +## Import + +```ts-vue +import { safe } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue{3,7} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { safe } from '{{connectorsPackageName}}' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [safe()], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +## Parameters + +```ts-vue +import { type SafeParameters } from '{{connectorsPackageName}}' +``` + +Check out the [Safe docs](https://github.com/safe-global/safe-apps-sdk/tree/main/packages/safe-apps-sdk) for more info. +### allowedDomains + +`RegExp[] | undefined` + +```ts-vue +import { safe } from '{{connectorsPackageName}}' + +const connector = safe({ + allowedDomains: [/app.safe.global$/], // [!code focus] +}) +``` + +### debug + +`boolean | undefined` + +```ts-vue +import { safe } from '{{connectorsPackageName}}' + +const connector = safe({ + debug: true, // [!code focus] +}) +``` + +### shimDisconnect + +`boolean | undefined` + +- This flag simulates disconnect behavior by keeping track of connection status in storage. +- Defaults to `false`. + +```ts-vue +import { safe } from '{{connectorsPackageName}}' + +const connector = safe({ + shimDisconnect: true, // [!code focus] +}) +``` diff --git a/site/shared/connectors/walletConnect.md b/site/shared/connectors/walletConnect.md new file mode 100644 index 0000000000..48e7fcb950 --- /dev/null +++ b/site/shared/connectors/walletConnect.md @@ -0,0 +1,215 @@ + + +# walletConnect + +Connector for [WalletConnect](https://walletconnect.com). + +## Import + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' +``` + +## Usage + +```ts-vue{3,8-10} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { walletConnect } from '{{connectorsPackageName}}' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [ + walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + }), + ], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +## Parameters + +```ts-vue +import { type WalletConnectParameters } from '{{connectorsPackageName}}' +``` + +Check out the [WalletConnect docs](https://github.com/WalletConnect/walletconnect-monorepo/tree/v2.0/providers/ethereum-provider) for more info. A few options are omitted that Wagmi manages internally. + +### customStoragePrefix + +`string | undefined` + +Custom storage prefix for persisting provider state. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + customStoragePrefix: 'wagmi', // [!code focus] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +### disableProviderPing + +`boolean | undefined` + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + disableProviderPing: false, // [!code focus] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +### isNewChainsStale + +`boolean | undefined` + +- If a new chain is added to a previously existing configured connector `chains`, this flag +will determine if that chain should be considered as stale. A stale chain is a chain that +WalletConnect has yet to establish a relationship with (e.g. the user has not approved or +rejected the chain). +- Defaults to `true`. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + isNewChainsStale: true, // [!code focus] + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', +}) +``` + +::: details More info +Preface: Whereas WalletConnect v1 supported dynamic chain switching, WalletConnect v2 requires +the user to pre-approve a set of chains up-front. This comes with consequent UX nuances (see below) when +a user tries to switch to a chain that they have not approved. + +This flag mainly affects the behavior when a wallet does not support dynamic chain authorization +with WalletConnect v2. + +If `true` (default), the new chain will be treated as a stale chain. If the user +has yet to establish a relationship (approved/rejected) with this chain in their WalletConnect +session, the connector will disconnect upon the dapp auto-connecting, and the user will have to +reconnect to the dapp (revalidate the chain) in order to approve the newly added chain. +This is the default behavior to avoid an unexpected error upon switching chains which may +be a confusing user experience (e.g. the user will not know they have to reconnect +unless the dapp handles these types of errors). + +If `false`, the new chain will be treated as a validated chain. This means that if the user +has yet to establish a relationship with the chain in their WalletConnect session, wagmi will successfully +auto-connect the user. This comes with the trade-off that the connector will throw an error +when attempting to switch to the unapproved chain. This may be useful in cases where a dapp constantly +modifies their configured chains, and they do not want to disconnect the user upon +auto-connecting. If the user decides to switch to the unapproved chain, it is important that the +dapp handles this error and prompts the user to reconnect to the dapp in order to approve +the newly added chain. +::: + +### metadata + +`CoreTypes.Metadata | undefined` + +Metadata related to the app requesting the connection. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + metadata: { // [!code focus] + name: 'Example', // [!code focus] + description: 'Example website', // [!code focus] + url: 'https://example.com', // [!code focus] + }, // [!code focus] +}) +``` + +### projectId + +`string` + +WalletConnect Cloud project identifier. You can find your `projectId` on your [WalletConnect dashboard](https://cloud.reown.com/sign-in). + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', // [!code focus] +}) +``` + +### qrModalOptions + +`QrModalOptions | undefined` + +Options for rendering QR modal. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + qrModalOptions: { // [!code focus] + themeMode: 'dark', // [!code focus] + }, // [!code focus] +}) +``` + +### relayUrl + +`string | undefined` + +- WalletConnect relay URL to use. +- Defaults to `'wss://relay.walletconnect.com'`. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + relayUrl: 'wss://relay.walletconnect.org', // [!code focus] +}) +``` + +### storageOptions + +`KeyValueStorageOptions | undefined` + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + storageOptions: {}, // [!code focus] +}) +``` + +### showQrModal + +`boolean | undefined` + +- Whether to show the QR code modal upon calling `connector.connect()`. +- Defaults to `true`. + +```ts-vue +import { walletConnect } from '{{connectorsPackageName}}' + +const connector = walletConnect({ + projectId: '3fcc6bba6f1de962d911bb5b5c3dba68', + showQrModal: true, // [!code focus] +}) +``` + +::: tip +This can be disabled and you can listen for a `'message'` event with payload `{ type: 'display_uri'; data: string }` if you want to render your own QR code. +::: diff --git a/site/shared/create-chain.md b/site/shared/create-chain.md new file mode 100644 index 0000000000..da2a7194cd --- /dev/null +++ b/site/shared/create-chain.md @@ -0,0 +1,93 @@ +## Create Chain + +Import the `Chain` type from Viem and create a new object that is asserted `as const` and `satisfies` the type. You can also use the `defineChain` function from Viem. + +::: code-group +```ts twoslash [as const satisfies Chain] +// @errors: 1360 +import { type Chain } from 'viem' + +export const mainnet = {} as const satisfies Chain +``` +```ts twoslash [defineChain] +// @errors: 2345 +import { defineChain } from 'viem' + +export const mainnet = defineChain({}) +``` +::: + +Now, add the missing required properties to the object until the error goes away. + +::: code-group +```ts twoslash [as const satisfies Chain] +import { type Chain } from 'viem' + +export const mainnet = { + id: 1, + name: 'Ethereum', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + default: { http: ['https://eth.merkle.io'] }, + }, + blockExplorers: { + default: { name: 'Etherscan', url: 'https://etherscan.io' }, + }, + contracts: { + ensRegistry: { + address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, + ensUniversalResolver: { + address: '0xE4Acdd618deED4e6d2f03b9bf62dc6118FC9A4da', + blockCreated: 16773775, + }, + multicall3: { + address: '0xca11bde05977b3631167028862be2a173976ca11', + blockCreated: 14353601, + }, + }, +} as const satisfies Chain +``` +```ts twoslash [defineChain] +import { defineChain } from 'viem' + +export const mainnet = defineChain({ + id: 1, + name: 'Ethereum', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + default: { http: ['https://eth.merkle.io'] }, + }, + blockExplorers: { + default: { name: 'Etherscan', url: 'https://etherscan.io' }, + }, + contracts: { + ensRegistry: { + address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, + ensUniversalResolver: { + address: '0xE4Acdd618deED4e6d2f03b9bf62dc6118FC9A4da', + blockCreated: 16773775, + }, + multicall3: { + address: '0xca11bde05977b3631167028862be2a173976ca11', + blockCreated: 14353601, + }, + }, +}) +``` +::: + +The more properties you add, the better the chain will be to use with Wagmi. Most of these attributes exist within the [`ethereum-lists/chains` repository](https://github.com/ethereum-lists/chains/tree/3fbd4eeac7ce116579634bd042b84e2b1d89886a/_data/chains). + +- `id`: The chain ID for the network. This can be found by typing the network name into [ChainList](https://chainlist.org). Example: "Ethereum Mainnet" has a Chain ID of `1`. +- `name`: Human-readable name for the chain. Example: "Ethereum Mainnet" +- `nativeCurrency`: The native currency of the chain. Found from [`ethereum-lists/chains`](https://github.com/ethereum-lists/chains/blob/3fbd4eeac7ce116579634bd042b84e2b1d89886a/_data/chains/eip155-56.json#L20-L24). +- `rpcUrls`: At least one public, credible RPC URL. Found from [`ethereum-lists/chains`](https://github.com/ethereum-lists/chains/blob/3fbd4eeac7ce116579634bd042b84e2b1d89886a/_data/chains/eip155-56.json#L4-L18). +- `blockExplorers`: A set of block explorers for the chain. Found from [`ethereum-lists/chains`](https://github.com/ethereum-lists/chains/blob/3fbd4eeac7ce116579634bd042b84e2b1d89886a/_data/chains/eip155-56.json#L30-L36). +- `contracts`: A set of deployed contracts for the chain. If you are deploying one of the following contracts yourself, make sure it is verified. + - `multicall3` is optional, but it's address is most likely `0xca11bde05977b3631167028862be2a173976ca11` – you can find the deployed block number on the block explorer. Check out [`mds1/multicall`](https://github.com/mds1/multicall#multicall3-contract-addresses) for more info. + - `ensRegistry` is optional – not all Chains have a ENS Registry. See [ENS Deployments](https://docs.ens.domains/ens-deployments) for more info. + - `ensUniversalResolver` is optional – not all Chains have a ENS Universal Resolver. +- `sourceId`: Source Chain ID (e.g. the L1 chain). +- `testnet`: Whether or not the chain is a testnet. \ No newline at end of file diff --git a/site/shared/createConfig.md b/site/shared/createConfig.md new file mode 100644 index 0000000000..bde2601794 --- /dev/null +++ b/site/shared/createConfig.md @@ -0,0 +1,485 @@ + + +# createConfig + +Creates new [`Config`](#config) object. + +## Import + +```ts-vue +import { createConfig } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +::: tip Integrating a Viem Client + +Instead of using [`transports`](#transports), it's possible to provide a function that returns a Viem [`Client`](https://viem.sh/docs/clients/custom.html) via the [`client`](#client) property for more fine-grained control over Wagmi's internal `Client` creation. + +```ts-vue {3,7-9} +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { createClient } from 'viem' + +const config = createConfig({ + chains: [mainnet, sepolia], + client({ chain }) { + return createClient({ chain, transport: http() }) + }, +}) +``` +::: + +## Parameters + +```ts-vue +import { type CreateConfigParameters } from '{{packageName}}' +``` + +### chains + +`readonly [Chain, ...Chain[]]` + +- Chains used by the `Config`. +- See Chains for more details about built-in chains and the `Chain` type. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### connectors + +`CreateConnectorFn[] | undefined` + +Connectors used by the `Config`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' +import { injected } from '{{connectorsPackageName}}' // [!code focus] + +const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [injected()], // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### multiInjectedProviderDiscovery + +`boolean | undefined` + +- Enables discovery of injected providers via [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) using the [`mipd`](https://github.com/wevm/mipd) library and converting to injected connectors. +- Defaults to `true`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + multiInjectedProviderDiscovery: false, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### ssr + +`boolean | undefined` + +Flag to indicate if the config is being used in a server-side rendering environment. Defaults to `false`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' // [!code focus] +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + ssr: true, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### storage + +`Storage | null | undefined` + +- `Storage` used by the config. Persists `Config`'s [`State`](#state-1) between sessions. +- Defaults to `createStorage({ storage: typeof window !== 'undefined' && window.localStorage ? window.localStorage : noopStorage })`. + +```ts-vue +import { createConfig, createStorage, http } from '{{packageName}}' // [!code focus] +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + storage: createStorage({ storage: window.localStorage }), // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### syncConnectedChain + +`boolean | undefined` + +- Keep the [`State['chainId']`](#chainid) in sync with the current connection. +- Defaults to `true`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + syncConnectedChain: false, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +--- + +### batch + +`{ multicall?: boolean | { batchSize?: number | undefined; wait?: number | undefined } | undefined } | { [_ in chains[number]["id"]]?: { multicall?: boolean | { batchSize?: number | undefined; wait?: number | undefined } | undefined } | undefined } | undefined` + +- Batch settings. See [Viem docs](https://viem.sh/docs/clients/custom.html#batch-optional) for more info. +- Defaults to `{ multicall: true }`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + batch: { multicall: true }, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### cacheTime + +`number | { [_ in chains[number]['id']]?: number | undefined } | undefined` + +- Frequency in milliseconds for polling enabled features. See [Viem docs](https://viem.sh/docs/clients/public.html#cachetime-optional) for more info. +- Defaults to [`pollingInterval`](#pollinginterval) or `4_000`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + cacheTime: 4_000, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### pollingInterval + +`number | { [_ in chains[number]['id']]?: number | undefined } | undefined` + +- Frequency in milliseconds for polling enabled features. See [Viem docs](https://viem.sh/docs/clients/custom.html#pollinginterval-optional) for more info. +- Defaults to `4_000`. + +```ts-vue +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + pollingInterval: 4_000, // [!code focus] + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +### transports + +`Record` + +Mapping of [chain IDs](#chains) to `Transport`s. This mapping is used internally when creating chain-aware Viem [`Client`](https://viem.sh/docs/clients/custom.html) objects. See the Transport docs for more info. + +```ts-vue +import { createConfig, fallback, http } from '{{packageName}}' // [!code focus] +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { // [!code focus] + [mainnet.id]: fallback([ // [!code focus] + http('https://...'), // [!code focus] + http('https://...'), // [!code focus] + ]), // [!code focus] + [sepolia.id]: http('https://...'), // [!code focus] + }, // [!code focus] +}) +``` + +--- + +### client + +`(parameters: { chain: chains[number] }) => Client` + +Function for creating new Viem [`Client`](https://viem.sh/docs/clients/custom.html) to be used internally. Exposes more control over the internal `Client` creation logic versus using the [`transports`](#transports) property. + +```ts-vue +import { createClient, http } from 'viem' // [!code focus] +import { createConfig } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + client({ chain }) { // [!code focus] + return createClient({ chain, transport: http('https://...') }) // [!code focus] + }, // [!code focus] +}) +``` + +::: warning +When using this option, you likely want to pass `parameters.chain` straight through to [`createClient`](https://viem.sh/docs/clients/custom.html#createclient) to ensure the Viem `Client` is in sync with any active connections. +::: + +## Return Type + +```ts-vue +import { type Config } from '{{packageName}}' +``` + +## Config + +Object responsible for managing Wagmi state and internals. + +```ts-vue +import { type Config } from '{{packageName}}' +``` + +### chains + +`readonly [Chain, ...Chain[]]` + +[`chains`](#chains) passed to `createConfig`. + +### connectors + +`readonly Connector[]` + +Connectors set up from passing [`connectors`](#connectors) and [`multiInjectedProviderDiscovery`](#multiinjectedproviderdiscovery) to `createConfig`. + +### state + +`State` + +The `Config` object's internal state. See [`State`](#state-1) for more info. + +### storage + +`Storage | null` + +[`storage`](#storage) passed to `createConfig`. + +### getClient + +`(parameters?: { chainId?: chainId | chains[number]['id'] | undefined }): Client>` + +Creates new Viem [`Client`](https://viem.sh/docs/clients/custom.html) object. + +::: code-group +```ts-vue [index.ts] +import { config } from './config' + +const client = config.getClient({ chainId: 1 }) +``` + +```ts-vue [config.ts] +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +::: + +### setState + +`(value: State | ((state: State) => State)) => void` + +Updates the `Config` object's internal state. See [`State`](#state-1) for more info. + +::: code-group +```ts-vue [index.ts] +import { mainnet } from '{{packageName}}/chains' +import { config } from './config' + +config.setState((x) => ({ + ...x, + chainId: x.current ? x.chainId : mainnet.id, +})) +``` + +```ts-vue [config.ts] +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +::: + +::: warning +Exercise caution when using this method. It is intended for internal and advanced use-cases only. Manually setting state can cause unexpected behavior. +::: + +### subscribe + +`(selector: (state: State) => state, listener: (selectedState: state, previousSelectedState: state) => void, options?: { emitImmediately?: boolean | undefined; equalityFn?: ((a: state, b: state) => boolean) | undefined } | undefined) => (() => void)` + +Listens for state changes matching the `selector` function. Returns a function that can be called to unsubscribe the listener. + +::: code-group +```ts-vue [index.ts] +import { config } from './config' + +const unsubscribe = config.subscribe( + (state) => state.chainId, + (chainId) => console.log(`Chain ID changed to ${chainId}`), +) +unsubscribe() +``` + +```ts-vue [config.ts] +import { createConfig, http } from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http('https://mainnet.example.com'), + [sepolia.id]: http('https://sepolia.example.com'), + }, +}) +``` + +::: + +## State + +```ts-vue +import { type State } from '{{packageName}}' +``` + +### chainId + +`chains[number]['id']` + +Current chain ID. When `syncConnectedChain` is `true`, `chainId` is kept in sync with the current connection. Defaults to first chain in [`chains`](#chains). + +### connections + +`Map` + +Mapping of unique connector identifier to [`Connection`](#connection) object. + +### current + +`string | undefined` + +Unique identifier of the current connection. + +### status + +`'connected' | 'connecting' | 'disconnected' | 'reconnecting'` + +Current connection status. + +- `'connecting'` attempting to establish connection. +- `'reconnecting'` attempting to re-establish connection to one or more connectors. +- `'connected'` at least one connector is connected. +- `'disconnected'` no connection to any connector. + +## Connection + +```ts-vue +import { type Connection } from '{{packageName}}' +``` + +### accounts + +`readonly [Address, ...Address[]]` + +Array of addresses associated with the connection. + +### chainId + +`number` + +Chain ID associated with the connection. + +### connector + +`Connector` + +Connector associated with the connection. diff --git a/site/shared/createStorage.md b/site/shared/createStorage.md new file mode 100644 index 0000000000..fe758569d2 --- /dev/null +++ b/site/shared/createStorage.md @@ -0,0 +1,161 @@ + + +# createStorage + +Creates new [`Storage`](#storage) object. + +## Import + +```ts-vue +import { createStorage } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { createStorage } from '{{packageName}}' + +const storage = createStorage({ storage: localStorage }) +``` + +## Parameters + +```ts-vue +import { type CreateStorageParameters } from '{{packageName}}' +``` + +### deserialize + +`((value: string) => T) | undefined` + +- Function to deserialize data from storage. +- Defaults to `deserialize`. + +```ts-vue +import { createStorage, deserialize } from '{{packageName}}' // [!code focus] + +const storage = createStorage({ + deserialize, // [!code focus] + storage: localStorage, +}) +``` + +::: warning +If you use a custom `deserialize` function, make sure it can handle `bigint` and `Map` values. +::: + +### key + +`string | undefined` + +- Key prefix to use when persisting data. +- Defaults to `'wagmi'`. + +```ts-vue +import { createStorage } from '{{packageName}}' + +const storage = createStorage({ + key: 'my-app', // [!code focus] + storage: localStorage, +}) +``` + +### serialize + +`((value: T) => string) | undefined` + +- Function to serialize data for storage. +- Defaults to `serialize`. + +```ts-vue +import { createStorage, serialize } from '{{packageName}}' // [!code focus] + +const storage = createStorage({ + serialize, // [!code focus] + storage: localStorage, +}) +``` + +::: warning +If you use a custom `serialize` function, make sure it can handle `bigint` and `Map` values. +::: + +### storage + +`{ getItem(key: string): string | null | undefined | Promise; setItem(key: string, value: string): void | Promise; removeItem(key: string): void | Promise; }` + +- Storage interface to use for persisting data. +- Defaults to `localStorage`. +- Supports synchronous and asynchronous storage methods. + +```ts-vue +import { createStorage } from '{{packageName}}' +// Using IndexedDB via https://github.com/jakearchibald/idb-keyval // [!code focus] +import { del, get, set } from 'idb-keyval' // [!code focus] + +const storage = createStorage({ + storage: { // [!code focus] + async getItem(name) { // [!code focus] + return get(name)// [!code focus] + }, // [!code focus] + async setItem(name, value) { // [!code focus] + await set(name, value) // [!code focus] + }, // [!code focus] + async removeItem(name) { // [!code focus] + await del(name) // [!code focus] + }, // [!code focus] + }, // [!code focus] +}) +``` + +## Return Type + +```ts-vue +import { type Storage } from '{{packageName}}' +``` + +## Storage + +Object responsible for persisting Wagmi `State` and other data. + +```ts-vue +import { type Storage } from '{{packageName}}' +``` + +### getItem + +`getItem(key: string, defaultValue?: value | null | undefined): value | null | Promise` + +```ts-vue +import { createStorage } from '{{packageName}}' + +const storage = createStorage({ storage: localStorage }) +const recentConnectorId = storage.getItem('recentConnectorId') // [!code focus] +``` + +### setItem + +`setItem(key: string, value: any): void | Promise` + +```ts-vue +import { createStorage } from '{{packageName}}' + +const storage = createStorage({ storage: localStorage }) +storage.setItem('recentConnectorId', 'foo') // [!code focus] +``` + +### removeItem + +`removeItem(key: string): void | Promise` + +```ts-vue +import { createStorage } from '{{packageName}}' + +const storage = createStorage({ storage: localStorage }) +storage.removeItem('recentConnectorId') // [!code focus] +``` \ No newline at end of file diff --git a/site/shared/errors.md b/site/shared/errors.md new file mode 100644 index 0000000000..c518172fc7 --- /dev/null +++ b/site/shared/errors.md @@ -0,0 +1,90 @@ + + +## BaseError + +Error class extended by all errors. + +```ts-vue +import { BaseError } from '{{packageName}}' +``` + +## Config + +### ConnectorAccountNotFoundError + +When an account does not exist on the connector or is unable to be used. + +```ts-vue +import { ConnectorAccountNotFoundError } from '{{packageName}}' +``` + +### ConnectorAlreadyConnectedError + +When a connector is already connected. + +```ts-vue +import { ConnectorAlreadyConnectedError } from '{{packageName}}' +``` + +### ConnectorChainMismatchError + +When the Wagmi Config is out-of-sync with the connector's active chain ID. This is rare and likely an upstream wallet issue. + +```ts-vue +import { ConnectorChainMismatchError } from '{{packageName}}' +``` + +### ChainNotConfiguredError + +When a chain is not configured. You likely need to add the chain to `Config['chains']`. + +```ts-vue +import { ChainNotConfiguredError } from '{{packageName}}' +``` + +### ConnectorNotConnectedError + +When a connector is not connected. + +```ts-vue +import { ConnectorNotConnectedError } from '{{packageName}}' +``` + +### ConnectorNotFoundError + +When a connector is not found or able to be used. + +```ts-vue +import { ConnectorNotFoundError } from '{{packageName}}' +``` + +### ConnectorUnavailableReconnectingError + +During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uuid`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. + +```ts-vue +import { ConnectorUnavailableReconnectingError } from '{{packageName}}' +``` + +## Connector + +### ProviderNotFoundError + +When a connector's provider is not found or able to be used. + +```ts-vue +import { ProviderNotFoundError } from '{{packageName}}' +``` + +### SwitchChainNotSupportedError + +When switching chains is not supported by connectors. + +```ts-vue +import { SwitchChainNotSupportedError } from '{{packageName}}' +``` diff --git a/site/shared/faq.md b/site/shared/faq.md new file mode 100644 index 0000000000..03e17693ee --- /dev/null +++ b/site/shared/faq.md @@ -0,0 +1,81 @@ + + +## Type inference doesn't work + +- Check that you set up TypeScript correctly with `"strict": true` in your `tsconfig.json` (TypeScript docs) +- Check that you const-asserted any ABIs or Typed Data you are using. +- Restart your language server or IDE, and check for type errors in your code. + +## My wallet doesn't work + +If you run into issues with a specific wallet, try another before opening up an issue. There are many different wallets and it's likely that the issue is with the wallet itself, not Wagmi. For example, if you are using Wallet X and sending a transaction doesn't work, try Wallet Y and see if it works. + +## `BigInt` Serialization + +Using native `BigInt` with `JSON.stringify` will raise a `TypeError` as +[`BigInt` values are not serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json). There are two techniques to mitigate this: + +#### Lossless serialization + +Lossless serialization means that `BigInt` will be converted to a format that can be deserialized later (e.g. `69420n` → `"#bigint.69420"`). The trade-off is that these values are not human-readable and are not intended to be displayed to the user. + +Lossless serialization can be achieved with wagmi's `serialize` and `deserialize` utilities. + +```tsx +import { serialize, deserialize } from 'wagmi' + +const serialized = serialize({ value: 69420n }) +// '{"value":"#bigint.69420"}' + +const deserialized = deserialize(serialized) +// { value: 69420n } +``` + +#### Lossy serialization + +Lossy serialization means that the `BigInt` will be converted to a normal display string (e.g. `69420n` → `'69420'`). +The trade-off is that you will not be able to deserialize the `BigInt` with `JSON.parse` as it can not distinguish between a normal string and a `BigInt`. + +This method can be achieved by modifying `JSON.stringify` to include a BigInt `replacer`: + +```tsx +const replacer = (key, value) => + typeof value === 'bigint' ? value.toString() : value + +JSON.stringify({ value: 69420n }, replacer) +// '{"value":"69420"}' +``` + +## How do I support the project? + +Wagmi is an open source software project and free to use. If you enjoy using Wagmi or would like to support Wagmi development, you can: + +- [Become a sponsor on GitHub](https://github.com/sponsors/wevm) +- Send us crypto + - Mainnet: 0x4557B18E779944BFE9d78A672452331C186a9f48 + - Multichain: 0xd2135CfB216b74109775236E36d4b433F1DF507B +- [Become a supporter on Drips](https://www.drips.network/app/projects/github/wevm/wagmi) + +If you use Wagmi at work, consider asking your company to sponsor Wagmi. This may not be easy, but **business sponsorships typically make a much larger impact on the sustainability of OSS projects** than individual donations, so you will help us much more if you succeed. + +## Is Wagmi production ready? + +Yes. Wagmi is very stable and is used in production by thousands of organizations, like [Stripe](https://stripe.com), [Shopify](https://shopify.com), [Coinbase](https://coinbase.com), [Uniswap](https://uniswap.org), [ENS](https://ens.domains), [Optimism](https://optimism.com). + +## Is Wagmi strict with semver? + +Yes, Wagmi is very strict with [semantic versioning](https://semver.org) and we will never introduce breaking changes to the runtime API in a minor version bump. + +For exported types, we try our best to not introduce breaking changes in non-major versions, however, [TypeScript doesn't follow semver](https://www.learningtypescript.com/articles/why-typescript-doesnt-follow-strict-semantic-versioning) and often introduces breaking changes in minor releases that can cause Wagmi type issues. See the TypeScript docs for more information. + +## How can I contribute to Wagmi? + +The Wagmi team accepts all sorts of contributions. Check out the [Contributing](/dev/contributing) guide to get started. If you are interested in adding a new connector to Wagmi, check out the [Creating Connectors](/dev/creating-connectors) guide. + +## Anything else you want to know? + +Please create a new [GitHub Discussion thread](https://github.com/wevm/wagmi). You're also free to suggest changes to this or any other page on the site using the "Suggest changes to this page" button at the bottom of the page. diff --git a/site/shared/getAccount-return-type.md b/site/shared/getAccount-return-type.md new file mode 100644 index 0000000000..315728261b --- /dev/null +++ b/site/shared/getAccount-return-type.md @@ -0,0 +1,107 @@ + + +### address + +`Address | undefined` + +- Connected address from connector. +- Defaults to first address in [`addresses`](#addresses). + +### addresses + +`readonly Address[] | undefined` + +Connected addresses from connector. + +### chain + +`Chain | undefined` + +Connected chain from connector. If chain is not configured by config, it will be `undefined`. + +### chainId + +`number | undefined` + +Connected chain id from connector. + +### connector + +`Connector | undefined` + +Connected connector. + +### isConnecting / isReconnecting / isConnected / isDisconnected + +`boolean` + +Boolean variables derived from [`status`](#status). + +### status + +`'connecting' | 'reconnecting' | 'connected' | 'disconnected'` + +- `'connecting'` attempting to establish connection. +- `'reconnecting'` attempting to re-establish connection to one or more connectors. +- `'connected'` at least one connector is connected. +- `'disconnected'` no connection to any connector. + +::: info You can use `status` to narrow the return type. +For example, when `status` is `'connected'` properties like `address` are guaranteed to be defined. + +```ts twoslash +import { type GetAccountReturnType } from '@wagmi/core' +const account = {} as GetAccountReturnType +// ---cut--- +if (account.status === 'connected') { + account + // ^? + + + + + + + + + + + + + + + +} +``` + +Or when status is `'disconnected'` properties like `address` are guaranteed to be `undefined`: + +```ts twoslash +import { type GetAccountReturnType } from '@wagmi/core' +const account = {} as GetAccountReturnType +// ---cut--- +if (account.status === 'disconnected') { + account + // ^? + + + + + + + + + + + + + + + +} +``` +::: diff --git a/site/shared/installation.md b/site/shared/installation.md new file mode 100644 index 0000000000..c96c14def0 --- /dev/null +++ b/site/shared/installation.md @@ -0,0 +1,62 @@ + + +## Requirements + +Wagmi is optimized for modern browsers. It is compatible with the latest versions of the following browsers. + + + +::: tip +Depending on your environment, you might need to add polyfills. See [Viem Platform Compatibility](https://viem.sh/docs/compatibility.html) for more info. +::: + +## Using Unreleased Commits + +If you can't wait for a new release to test the latest features, you can either install from the `canary` tag (tracks the [`main`](https://github.com/wevm/wagmi/tree/main) branch). + +::: code-group +```bash-vue [pnpm] +pnpm add {{packageName}}@canary +``` + +```bash-vue [npm] +npm install {{packageName}}@canary +``` + +```bash-vue [yarn] +yarn add {{packageName}}@canary +``` + +```bash-vue [bun] +bun add {{packageName}}@canary +``` +::: + +Or clone the [Wagmi repo](https://github.com/wevm/wagmi) to your local machine, build, and link it yourself. + +```bash-vue +gh repo clone wevm/wagmi +cd wagmi +pnpm install +pnpm build +cd packages/{{packageDir}} +pnpm link --global +``` + +Then go to the project where you are using Wagmi and run `pnpm link --global {{packageName}}` (or the package manager that you used to link Wagmi globally). Make sure you installed any [required peer dependencies](#package-manager) and their versions are correct. + +## Security + +Ethereum-related projects are often targeted in attacks to steal users' assets. Make sure you follow security best-practices for your project. Some quick things to get started. + +- Pin package versions, upgrade mindfully, and inspect lockfile changes to minimize the risk of [supply-chain attacks](https://nodejs.org/en/guides/security/#supply-chain-attacks). +- Install the [Socket Security](https://socket.dev) [GitHub App](https://github.com/apps/socket-security) to help detect and block supply-chain attacks. +- Add a [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) to defend against external scripts running in your app. +- Pin [GitHub Action](https://x.com/paulmillr/status/1900948425325031448) versions to commits instead of tags. diff --git a/site/shared/mutation-imports.md b/site/shared/mutation-imports.md new file mode 100644 index 0000000000..b538dcfab6 --- /dev/null +++ b/site/shared/mutation-imports.md @@ -0,0 +1,19 @@ + + +## TanStack Query + +```ts-vue +import { + type {{typeName}}Data, + type {{typeName}}Variables, + type {{typeName}}Mutate, + type {{typeName}}MutateAsync, + {{actionName}}MutationOptions, +} from '{{packageName}}/query' +``` \ No newline at end of file diff --git a/site/shared/mutation-options.md b/site/shared/mutation-options.md new file mode 100644 index 0000000000..b1eae45250 --- /dev/null +++ b/site/shared/mutation-options.md @@ -0,0 +1,89 @@ + + +
+ +--- + +### mutation + +TanStack Query parameters. See the [TanStack Query mutation docs](https://tanstack.com/query/v5/docs/react/reference/useMutation) for more info. + +::: info Wagmi does not support passing all TanStack Query parameters +TanStack Query parameters, like `mutationFn` and `mutationKey`, are used internally to make Wagmi work and you cannot override them. Check out the [source](https://github.com/wevm/wagmi/blob/main/packages/react/src/utils/query.ts#L30) to see what parameters are not supported. All parameters listed below are supported. +::: + +#### gcTime + +`number | Infinity | undefined` + +- The time in milliseconds that unused/inactive cache data remains in memory. When a mutation's cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different cache times are specified, the longest one will be used. +- If set to `Infinity`, will disable garbage collection + +#### meta + +`Record | undefined` + +If set, stores additional information on the mutation cache entry that can be used as needed. It will be accessible wherever [`{{mutate}}`](#mutate) is available (e.g. [`onError`](#onerror), [`onSuccess`](#onsuccess) functions). + +#### networkMode + +`'online' | 'always' | 'offlineFirst' | undefined` + +- defaults to `'online'` +- see [Network Mode](https://tanstack.com/query/v5/docs/react/guides/network-mode) for more information. + +#### onError + +`((error: {{TError}}, variables: {{TVariables}}, context?: context | undefined) => Promise | unknown) | undefined` + +This function will fire if the mutation encounters an error and will be passed the error. + +#### onMutate + +`((variables: {{TVariables}}) => Promise | context | void) | undefined` + +- This function will fire before the mutation function is fired and is passed the same variables the mutation function would receive +- Useful to perform optimistic updates to a resource in hopes that the mutation succeeds +- The value returned from this function will be passed to both the `onError` and `onSettled` functions in the event of a mutation failure and can be useful for rolling back optimistic updates. + +#### onSuccess + +`((data: {{TData}}, variables: {{TVariables}}, context?: context | undefined) => Promise | unknown) | undefined` + +This function will fire when the mutation is successful and will be passed the mutation's result. + +#### onSettled + +`((data: {{TData}}, error: {{TError}}, variables: {{TVariables}}, context?: context | undefined) => Promise | unknown) | undefined` + +This function will fire when the mutation is either successfully fetched or encounters an error and be passed either the data or error + +#### queryClient + +`QueryClient` + +Use this to use a custom `QueryClient`. Otherwise, the one from the nearest context will be used. + +#### retry + +`boolean | number | ((failureCount: number, error: {{TError}}) => boolean) | undefined` + +- Defaults to `0`. +- If `false`, failed mutations will not retry. +- If `true`, failed mutations will retry infinitely. +- If set to an `number`, e.g. `3`, failed mutations will retry until the failed mutations count meets that number. + +#### retryDelay + +`number | ((retryAttempt: number, error: {{TError}}) => number) | undefined` + +- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds. +- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff. +- A function like `attempt => attempt * 1000` applies linear backoff. diff --git a/site/shared/mutation-result.md b/site/shared/mutation-result.md new file mode 100644 index 0000000000..3dfaa2d709 --- /dev/null +++ b/site/shared/mutation-result.md @@ -0,0 +1,122 @@ + + +
+ +--- + +[TanStack Query mutation docs](https://tanstack.com/query/v5/docs/react/reference/useMutation) + +### {{mutate}} + +`(variables: {{TVariables}}, { onSuccess, onSettled, onError }) => void` + +The mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options. + +- #### variables + + `{{TVariables}}` + + The variables object to pass to the `{{mutate}}` action. + +- #### onSuccess + + `(data: {{TData}}, variables: {{TVariables}}, context: TContext) => void` + + This function will fire when the mutation is successful and will be passed the mutation's result. + +- #### onError + + `(error: {{TError}}, variables: {{TVariables}}, context: TContext | undefined) => void` + + This function will fire if the mutation encounters an error and will be passed the error. + +- #### onSettled + + `(data: {{TData}} | undefined, error: {{TError}} | null, variables: {{TVariables}}, context: TContext | undefined) => void` + + - This function will fire when the mutation is either successfully fetched or encounters an error and be passed either the data or error + - If you make multiple requests, `onSuccess` will fire only after the latest call you've made. + +### {{mutate}}Async + +`(variables: {{TVariables}}, { onSuccess, onSettled, onError }) => Promise<{{TData}}>` + +Similar to [`{{mutate}}`](#mutate) but returns a promise which can be awaited. + +### data + +`{{TData}} | undefined` + +- `{{mutate}}` return type +- Defaults to `undefined` +- The last successfully resolved data for the mutation. + +### error + +`{{TError}} | null` + +The error object for the mutation, if an error was encountered. + +### failureCount + +`number` + +- The failure count for the mutation. +- Incremented every time the mutation fails. +- Reset to `0` when the mutation succeeds. + +### failureReason + +`{{TError}} | null` + +- The failure reason for the mutation retry. +- Reset to `null` when the mutation succeeds. + +### isError / isIdle / isPending / isSuccess + +`boolean` + +Boolean variables derived from [`status`](#status). + +### isPaused + +`boolean` + +- will be `true` if the mutation has been `paused`. +- see [Network Mode](https://tanstack.com/query/v5/docs/react/guides/network-mode) for more information. + +### reset + +`() => void` + +A function to clean the mutation internal state (e.g. it resets the mutation to its initial state). + +### status + +`'idle' | 'pending' | 'error' | 'success'` + +- `'idle'` initial status prior to the mutation function executing. +- `'pending'` if the mutation is currently executing. +- `'error'` if the last mutation attempt resulted in an error. +- `'success'` if the last mutation attempt was successful. + +### submittedAt + +`number` + +- The timestamp for when the mutation was submitted. +- Defaults to `0`. + +### variables + +`{{TVariables}} | undefined` + +- The variables object passed to [`{{mutate}}`](#mutate). +- Defaults to `undefined`. diff --git a/site/shared/query-imports.md b/site/shared/query-imports.md new file mode 100644 index 0000000000..20eed55a01 --- /dev/null +++ b/site/shared/query-imports.md @@ -0,0 +1,20 @@ + + +## TanStack Query + +```ts-vue +import { + type {{typeName}}Data, + type {{typeName}}Options, + type {{typeName}}QueryFnData, + type {{typeName}}QueryKey, + {{actionName}}QueryKey, + {{actionName}}QueryOptions, +} from '{{packageName}}/query' +``` \ No newline at end of file diff --git a/site/shared/query-options.md b/site/shared/query-options.md new file mode 100644 index 0000000000..ac6f3f51f5 --- /dev/null +++ b/site/shared/query-options.md @@ -0,0 +1,208 @@ + + +
+ +--- + +### query + +TanStack Query parameters. See the [TanStack Query query docs](https://tanstack.com/query/v5/docs/react/reference/useQuery) for more info. + +::: info Wagmi does not support passing all TanStack Query parameters +TanStack Query parameters, like `queryFn` and `queryKey`, are used internally to make Wagmi work and you cannot override them. Check out the [source](https://github.com/wevm/wagmi/blob/main/packages/react/src/types/properties.ts#L27) to see what parameters are not supported. All parameters listed below are supported. +::: + +#### enabled + +`boolean | undefined` + +- Set this to `false` to disable this query from automatically running. +- Can be used for [Dependent Queries](https://tanstack.com/query/v5/docs/react/guides/dependent-queries). + +
+ +#### gcTime + +`number | Infinity | undefined` + +- Defaults to `5 * 60 * 1000` (5 minutes) or `Infinity` during SSR +- The time in milliseconds that unused/inactive cache data remains in memory. When a query's cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different garbage collection times are specified, the longest one will be used. +- If set to `Infinity`, will disable garbage collection + +
+ +#### initialData + +`{{TData}} | (() => {{TData}}) | undefined` + +- If set, this value will be used as the initial data for the query cache (as long as the query hasn't been created or cached yet) +- If set to a function, the function will be called **once** during the shared/root query initialization, and be expected to synchronously return the initialData +- Initial data is considered stale by default unless a `staleTime` has been set. +- `initialData` **is persisted** to the cache + +#### initialDataUpdatedAt + +`number | ((() => number | undefined)) | undefined` + +If set, this value will be used as the time (in milliseconds) of when the `initialData` itself was last updated. + +
+ +#### initialPageParam + +`{{TPageParam}}` + +The initial page parameter to be passed to the query function. + +#### getPreviousPageParam + +This function can be set to automatically get the previous cursor for infinite queries. +The result will also be used to determine the value of `hasPreviousPage`. + +`(firstPage: {{TData}}, allPages: {{TData}}[], firstPageParam: {{TPageParam}}, allPageParams: {{TPageParam}}[]) => {{TPageParam}} | undefined | null` + +#### getNextPageParam + +This function can be set to automatically get the previous cursor for infinite queries. +The result will also be used to determine the value of `hasPreviousPage`. + +`(lastPage: {{TData}}, allPages: {{TData}}[], lastPageParam: {{TPageParam}}, allPageParams: {{TPageParam}}[]) => {{TPageParam}} | undefined | null` + +
+ +#### meta + +`Record | undefined` + +If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn`. + +#### networkMode + +`online' | 'always' | 'offlineFirst' | undefined` + +- Defaults to `'online'` +- see [Network Mode](https://tanstack.com/query/v5/docs/react/guides/network-mode) for more information. + +#### notifyOnChangeProps + +`string[] | 'all' | (() => string[] | 'all') | undefined` + +- If set, the component will only re-render if any of the listed properties change. +- If set to `['data', 'error']` for example, the component will only re-render when the `data` or `error` properties change. +- If set to `'all'`, the component will opt-out of smart tracking and re-render whenever a query is updated. +- If set to a function, the function will be executed to compute the list of properties. +- By default, access to properties will be tracked, and the component will only re-render when one of the tracked properties change. + +#### placeholderData + +`{{TData}} | ((previousValue: {{TData}} | undefined; previousQuery: Query | undefined) => {{TData}}) | undefined` + +- If set, this value will be used as the placeholder data for this particular query observer while the query is still in the `pending` state. +- `placeholderData` is **not persisted** to the cache +- If you provide a function for `placeholderData`, as a first argument you will receive previously watched query data if available, and the second argument will be the complete previousQuery instance. + +#### queryClient + +`QueryClient | undefined` + +Use this to use a custom `QueryClient`. Otherwise, the one from the nearest context will be used. + +#### refetchInterval + +`number | false | ((data: {{TData}} | undefined, query: Query) => number | false | undefined) | undefined` + +- If set to a number, all queries will continuously refetch at this frequency in milliseconds +- If set to a function, the function will be executed with the latest data and query to compute a frequency + +#### refetchIntervalInBackground + +`boolean | undefined` + +If set to `true`, queries that are set to continuously refetch with a `refetchInterval` will continue to refetch while their tab/window is in the background + +#### refetchOnMount + +`boolean | 'always' | ((query: Query) => boolean | 'always') | undefined` + +- Defaults to `true` +- If set to `true`, the query will refetch on mount if the data is stale. +- If set to `false`, the query will not refetch on mount. +- If set to `'always'`, the query will always refetch on mount. +- If set to a function, the function will be executed with the query to compute the value + +#### refetchOnReconnect + +`boolean | 'always' | ((query: Query) => boolean | 'always') | undefined` + +- Defaults to `true` +- If set to `true`, the query will refetch on reconnect if the data is stale. +- If set to `false`, the query will not refetch on reconnect. +- If set to `'always'`, the query will always refetch on reconnect. +- If set to a function, the function will be executed with the query to compute the value + +#### refetchOnWindowFocus + +`boolean | 'always' | ((query: Query) => boolean | 'always') | undefined` + +- Defaults to `true` +- If set to `true`, the query will refetch on window focus if the data is stale. +- If set to `false`, the query will not refetch on window focus. +- If set to `'always'`, the query will always refetch on window focus. +- If set to a function, the function will be executed with the query to compute the value + +#### retry + +`boolean | number | ((failureCount: number, error: {{TError}}) => boolean) | undefined` + +- If `false`, failed queries will not retry by default. +- If `true`, failed queries will retry infinitely. +- If set to a `number`, e.g. `3`, failed queries will retry until the failed query count meets that number. +- Defaults to `3` on the client and `0` on the server + +#### retryDelay + +`number | ((retryAttempt: number, error: {{TError}}) => number) | undefined` + +- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds. +- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff. +- A function like `attempt => attempt * 1000` applies linear backoff. + +#### retryOnMount + +`boolean | undefined` + +If set to `false`, the query will not be retried on mount if it contains an error. Defaults to `true`. + +#### select + +`((data: {{TData}}) => unknown) | undefined` + +This option can be used to transform or select a part of the data returned by the query function. It affects the returned `data` value, but does not affect what gets stored in the query cache. + +
+ +#### staleTime + +`number | Infinity | undefined` + +- Defaults to `0` +- The time in milliseconds after data is considered stale. This value only applies to the hook it is defined on. +- If set to `Infinity`, the data will never be considered stale + +
+ +#### structuralSharing + +`boolean | (((oldData: {{TData}} | undefined, newData: {{TData}}) => {{TData}})) | undefined` + +- Defaults to `true` +- If set to `false`, structural sharing between query results will be disabled. +- If set to a function, the old and new data values will be passed through this function, which should combine them into resolved data for the query. This way, you can retain references from the old data to improve performance even when that data contains non-serializable values. diff --git a/site/shared/query-result.md b/site/shared/query-result.md new file mode 100644 index 0000000000..34e4b8e733 --- /dev/null +++ b/site/shared/query-result.md @@ -0,0 +1,193 @@ + + +
+ +--- + +[TanStack Query query docs](https://tanstack.com/query/v5/docs/react/reference/useQuery) + +### data + +`{{TData}}` + +- The last successfully resolved data for the query. +- Defaults to `undefined`. + +### dataUpdatedAt + +`number` + +The timestamp for when the query most recently returned the `status` as `'success'`. + +### error + +`null | {{TError}}` + +- The error object for the query, if an error was thrown. +- Defaults to `null` + +### errorUpdatedAt + +`number` + +The timestamp for when the query most recently returned the `status` as `'error'`. + +### errorUpdateCount + +`number` + +The sum of all errors. + +### failureCount + +`number` + +- The failure count for the query. +- Incremented every time the query fails. +- Reset to `0` when the query succeeds. + +### failureReason + +`null | {{TError}}` + +- The failure reason for the query retry. +- Reset to `null` when the query succeeds. + +
+ +### fetchNextPage + +`(options?: FetchNextPageOptions) => Promise>` + +This function allows you to fetch the next "page" of results. + +### fetchPreviousPage + +`(options?: FetchPreviousPageOptions) => Promise>` + +This function allows you to fetch the previous "page" of results. + +### hasNextPage + +`boolean` + +This will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option). + +### hasPreviousPage + +`boolean` + +This will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option). + +### isFetchingNextPage + +`boolean` + +Will be `true` while fetching the next page with `fetchNextPage`. + +### isFetchingPreviousPage + +`boolean` + +Will be `true` while fetching the previous page with `fetchPreviousPage`. + +
+ +### fetchStatus + +`'fetching' | 'idle' | 'paused'` + +- `fetching` Is `true` whenever the queryFn is executing, which includes initial `pending` as well as background refetches. +- `paused` The query wanted to fetch, but has been `paused`. +- `idle` The query is not fetching. +- See [Network Mode](https://tanstack.com/query/v5/docs/react/guides/network-mode) for more information. + +### isError / isPending / isSuccess + +`boolean` + +Boolean variables derived from [`status`](#status). + +### isFetched + +`boolean` + +Will be `true` if the query has been fetched. + +### isFetchedAfterMount + +`boolean` + +- Will be `true` if the query has been fetched after the component mounted. +- This property can be used to not show any previously cached data. + +### isFetching / isPaused + +`boolean` + +Boolean variables derived from [`fetchStatus`](#fetchstatus). + +### isLoading + +`boolean` + +- Is `true` whenever the first fetch for a query is in-flight +- Is the same as `isFetching && isPending` + +### isLoadingError + +`boolean` + +Will be `true` if the query failed while fetching for the first time. + +### isPlaceholderData + +`boolean` + +Will be `true` if the data shown is the placeholder data. + +### isRefetchError + +`boolean` + +Will be `true` if the query failed while refetching. + +### isRefetching + +`boolean` + +- Is `true` whenever a background refetch is in-flight, which _does not_ include initial `'pending'`. +- Is the same as `isFetching && !isPending` + +### isStale + +`boolean` + +Will be `true` if the data in the cache is invalidated or if the data is older than the given `staleTime`. + +### refetch + +`(options: { cancelRefetch?: boolean | undefined; throwOnError?: boolean | undefined }) => Promise>` + +- A function to manually refetch the query. +- `throwOnError` + - When set to `true`, an error will be thrown if the query fails. + - When set to `false`, an error will be logged if the query fails. +- `cancelRefetch` + - When set to `true`, a currently running request will be cancelled before a new request is made. + - When set to `false`, no refetch will be made if there is already a request running. + - Defaults to `true` + +### status + +`'error' | 'pending' | 'success'` + +- `pending` if there's no cached data and no query attempt was finished yet. +- `error` if the query attempt resulted in an error. The corresponding `error` property has the error received from the attempted fetch +- `success` if the query has received a response with no errors and is ready to display its data. The corresponding `data` property on the query is the data received from the successful fetch or if the query's `enabled` property is set to `false` and has not been fetched yet `data` is the first `initialData` supplied to the query on initialization. diff --git a/site/shared/transports/custom.md b/site/shared/transports/custom.md new file mode 100644 index 0000000000..dee391608a --- /dev/null +++ b/site/shared/transports/custom.md @@ -0,0 +1,110 @@ + + +# custom + +The `custom` Transport connects to a JSON-RPC API via custom. Wraps Viem's [`custom` Transport](https://viem.sh/docs/clients/transports/custom.html). + +## Import + +```ts-vue +import { custom } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { + createConfig, + custom // [!code hl] +} from '{{packageName}}' +import { mainnet } from '{{packageName}}/chains' +import { customRpc } from './rpc' + +export const config = createConfig({ + chains: [mainnet], + connectors: [injected()], + transports: { + [mainnet.id]: custom({ // [!code hl] + async request({ method, params }) { // [!code hl] + const response = await customRpc.request(method, params) // [!code hl] + return response // [!code hl] + } // [!code hl] + }) // [!code hl] + }, +}) +``` + +## Parameters + +### provider + +`{ request({ method: string, params: unknown[] }): Promise }` + +An [EIP-1193 `request` function](https://eips.ethereum.org/EIPS/eip-1193#request) function. + +```ts +import { customRpc } from './rpc' + +const transport = custom({ + async request({ method, params }) { // [!code focus:3] + const response = await customRpc.request(method, params) + return response + } +}) +``` + +### key (optional) + +`string` + +A key for the Transport. Defaults to `"custom"`. + +```ts +const transport = custom( + provider, + { + key: 'windowProvider', // [!code focus] + } +) +``` + +### name (optional) + +`string` + +A name for the Transport. Defaults to `"Ethereum Provider"`. + +```ts +const transport = custom( + provider, + { + name: 'Window Ethereum Provider', // [!code focus] + } +) +``` + +### retryCount (optional) + +`number` + +The max number of times to retry when a request fails. Defaults to `3`. + +```ts +const transport = custom(provider, { + retryCount: 5, // [!code focus] +}) +``` + +### retryDelay (optional) + +`number` + +The base delay (in ms) between retries. By default, the Transport will use [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) (`~~(1 << count) * retryDelay`), which means the time between retries is not constant. + +```ts +const transport = custom(provider, { + retryDelay: 100, // [!code focus] +}) +``` \ No newline at end of file diff --git a/site/shared/transports/fallback.md b/site/shared/transports/fallback.md new file mode 100644 index 0000000000..befe9395e3 --- /dev/null +++ b/site/shared/transports/fallback.md @@ -0,0 +1,36 @@ + + +# fallback + +The `fallback` Transport consumes **multiple** Transports. If a Transport request fails, it will fall back to the next one in the list. Wraps Viem's [`fallback` Transport](https://viem.sh/docs/clients/transports/fallback.html). + +## Import + +```ts-vue +import { fallback } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { + createConfig, + fallback, // [!code hl] + http, +} from '{{packageName}}' +import { mainnet } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet], + connectors: [injected()], + transports: { + [mainnet.id]: fallback([ // [!code hl] + http('https://foo-bar-baz.quiknode.pro/...'), // [!code hl] + http('https://mainnet.infura.io/v3/...'), // [!code hl] + ]) // [!code hl] + }, +}) +``` + diff --git a/site/shared/transports/http.md b/site/shared/transports/http.md new file mode 100644 index 0000000000..1a1d864063 --- /dev/null +++ b/site/shared/transports/http.md @@ -0,0 +1,178 @@ + + +# http + +The `http` Transport connects to a JSON-RPC API via HTTP. Wraps Viem's [`http` Transport](https://viem.sh/docs/clients/transports/http.html). + +## Import + +```ts-vue +import { http } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { + createConfig, + http // [!code hl] +} from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [injected()], + transports: { + [mainnet.id]: http('https://foo-bar-baz.quiknode.pro/...'), // [!code hl] + [sepolia.id]: http('https://foo-bar-sep.quiknode.pro/...'), // [!code hl] + }, +}) +``` + +::: warning +If no URL is provided, then the transport will fall back to a public RPC URL on the chain. It is highly recommended to provide an authenticated RPC URL to prevent rate-limiting. +::: + +### Batch JSON-RPC + +The `http` Transport supports Batch JSON-RPC. This means that multiple JSON-RPC requests can be sent in a single HTTP request. + +The Transport will batch up Actions over a given period and execute them in a single Batch JSON-RPC HTTP request. By default, this period is a [zero delay](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop#zero_delays) meaning that the batch request will be executed at the end of the current [JavaScript message queue](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop#queue). Consumers can specify a custom time period `wait` (in ms). + +You can enable Batch JSON-RPC by setting the `batch` flag to `true`: + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + batch: true // [!code hl] +}) +``` + +## Parameters + +### url + +`string` + +URL of the JSON-RPC API. Defaults to `chain.rpcUrls.default.http[0]`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...') +``` + +### batch + +`boolean | BatchOptions` + +Toggle to enable Batch JSON-RPC. Defaults to `false` + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + batch: true // [!code focus] +}) +``` + +### batch.batchSize + +`number` + +The maximum number of JSON-RPC requests to send in a batch. Defaults to `1_000`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + batch: { + batchSize: 2_000 // [!code focus] + } +}) +``` + +### batch.wait + +`number` + +The maximum number of milliseconds to wait before sending a batch. Defaults to `0` ([zero delay](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop#zero_delays)). + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + batch: { + wait: 16 // [!code focus] + } +}) +``` + +### fetchOptions + +[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) + +[Fetch options](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to pass to the internal `fetch` function. Useful for passing auth headers or cache options. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + fetchOptions: { // [!code focus:5] + headers: { + 'Authorization': 'Bearer ...' + } + } +}) +``` + +### key + +`string` + +A key for the Transport. Defaults to `"http"`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + key: 'alchemy', // [!code focus] +}) +``` + +### name + +`string` + +A name for the Transport. Defaults to `"HTTP JSON-RPC"`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + name: 'Alchemy HTTP Provider', // [!code focus] +}) +``` + +### retryCount + +`number` + +The max number of times to retry when a request fails. Defaults to `3`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + retryCount: 5, // [!code focus] +}) +``` + +### retryDelay + +`number` + +The base delay (in ms) between retries. By default, the Transport will use [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) (`~~(1 << count) * retryDelay`), which means the time between retries is not constant. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + retryDelay: 100, // [!code focus] +}) +``` + +### timeout + +`number` + +The timeout for requests. Defaults to `10_000`. + +```ts +const transport = http('https://foo-bar-baz.quiknode.pro/...', { + timeout: 60_000, // [!code focus] +}) +``` diff --git a/site/shared/transports/unstable_connector.md b/site/shared/transports/unstable_connector.md new file mode 100644 index 0000000000..f858421c90 --- /dev/null +++ b/site/shared/transports/unstable_connector.md @@ -0,0 +1,123 @@ + + +# unstable_connector + +The `unstable_connector` Transport connects to a JSON-RPC API via the provided Connector. + +For example, if the provided Connector is `injected` and the end-user uses MetaMask, then outgoing JSON-RPC requests will be sent via the MetaMask EIP-1193 Provider (`window.ethereum`). + +## Import + +```ts-vue +import { unstable_connector } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { + createConfig, + fallback, + unstable_connector, // [!code hl] +} from '{{packageName}}' +import { mainnet } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet], + connectors: [injected()], + transports: { + [mainnet.id]: fallback([ + unstable_connector(injected), // [!code hl] + http('https://foo-bar-baz.quiknode.pro/...') + ]) + }, +}) +``` + +::: warning +It is **highly recommended** to use the `unstable_connector` Transport inside of a `fallback` Transport. This ensures that if the Connector request fails, the Transport will fall back to a different Transport in the fallback set. + +Some common cases for a Connector request to fail are: + +- Chain ID mismatches, +- Connector RPC not supporting the requested method and/or only supporting a subset of methods for connected accounts, +- Rate-limiting of Connector RPC. +::: + +## Parameters + +### connector + +`Connector` + +The Connector to use for the Transport. + +```ts +import { unstable_connector } from 'wagmi' +import { safe } from 'wagmi/connectors' + +const transport = unstable_connector(safe) // [!code focus] +``` + +### key (optional) + +`string` + +A key for the Transport. Defaults to `"connector"`. + +```ts +import { unstable_connector } from 'wagmi' +import { injected } from 'wagmi/connectors' + +const transport = unstable_connector(injected, { + key: 'injected', // [!code focus] +}) +``` + +### name (optional) + +`string` + +A name for the Transport. Defaults to `"Connector"`. + +```ts +import { unstable_connector } from 'wagmi' +import { injected } from 'wagmi/connectors' + +const transport = unstable_connector(injected, { + name: 'Injected', // [!code focus] +}) +``` + +### retryCount (optional) + +`number` + +The max number of times to retry when a request fails. Defaults to `3`. + +```ts +import { unstable_connector } from 'wagmi' +import { injected } from 'wagmi/connectors' + +const transport = unstable_connector(injected, { + retryCount: 5, // [!code focus] +}) +``` + +### retryDelay (optional) + +`number` + +The base delay (in ms) between retries. By default, the Transport will use [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) (`~~(1 << count) * retryDelay`), which means the time between retries is not constant. + +```ts +import { unstable_connector } from 'wagmi' +import { injected } from 'wagmi/connectors' + +const transport = unstable_connector(injected, { + retryDelay: 100, // [!code focus] +}) +``` diff --git a/site/shared/transports/webSocket.md b/site/shared/transports/webSocket.md new file mode 100644 index 0000000000..0b79117400 --- /dev/null +++ b/site/shared/transports/webSocket.md @@ -0,0 +1,108 @@ + + +# webSocket + +The `webSocket` Transport connects to a JSON-RPC API via a WebSocket. Wraps Viem's [`webSocket` Transport](https://viem.sh/docs/clients/transports/websocket). + +## Import + +```ts-vue +import { webSocket } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { + createConfig, + webSocket // [!code hl] +} from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [injected()], + transports: { + [mainnet.id]: webSocket('wss://foo-bar-baz.quiknode.pro/...'), // [!code hl] + [sepolia.id]: webSocket('wss://foo-bar-sep.quiknode.pro/...'), // [!code hl] + }, +}) +``` + +::: warning +If no URL is provided, then the transport will fall back to a public RPC URL on the chain. It is highly recommended to provide an authenticated RPC URL to prevent rate-limiting. +::: + +## Parameters + +### url + +`string` + +URL of the JSON-RPC API. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...') +``` + +### key (optional) + +`string` + +A key for the Transport. Defaults to `"webSocket"`. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...', { + key: 'alchemy', // [!code focus] +}) +``` + +### name (optional) + +`string` + +A name for the Transport. Defaults to `"WebSocket JSON-RPC"`. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...', { + name: 'Alchemy WebSocket Provider', // [!code focus] +}) +``` + +### retryCount (optional) + +`number` + +The max number of times to retry when a request fails. Defaults to `3`. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...', { + retryCount: 5, // [!code focus] +}) +``` + +### retryDelay (optional) + +`number` + +The base delay (in ms) between retries. By default, the Transport will use [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) (`~~(1 << count) * retryDelay`), which means the time between retries is not constant. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...', { + retryDelay: 100, // [!code focus] +}) +``` + +### timeout (optional) + +`number` + +The timeout for async WebSocket requests. Defaults to `10_000`. + +```ts +const transport = webSocket('wss://foo-bar-baz.quiknode.pro/...', { + timeout: 60_000, // [!code focus] +}) +``` diff --git a/site/shared/utilities/cookieToInitialState.md b/site/shared/utilities/cookieToInitialState.md new file mode 100644 index 0000000000..0850b45020 --- /dev/null +++ b/site/shared/utilities/cookieToInitialState.md @@ -0,0 +1,74 @@ + + +# cookieToInitialState + +Helper to convert a cookie string into [initial state](/react/api/WagmiProvider#initialstate). + +## Import + +```ts-vue +import { cookieToInitialState } from '{{packageName}}' +``` + +## Usage + +::: code-group + +```ts-vue [server.ts] +import { cookieToInitialState } from '{{packageName}}' +import config from './config' + +function handler(req: Request) { + const initialState = cookieToInitialState(config, req.headers.cookie) + // ... +} +``` + +```ts-vue [config.ts] +import { + createConfig, + http, + cookieStorage, + createStorage +} from '{{packageName}}' +import { mainnet, sepolia } from '{{packageName}}/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + ssr: true, + storage: createStorage({ + storage: cookieStorage, + }), + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +::: + +## Parameters + +### config + +`Config` + +Wagmi Config + + +### cookie + +`string | null | undefined` + +The cookie string. + +## Return Type + +`State` + +Initial state. \ No newline at end of file diff --git a/site/shared/utilities/deserialize.md b/site/shared/utilities/deserialize.md new file mode 100644 index 0000000000..c355e5a15b --- /dev/null +++ b/site/shared/utilities/deserialize.md @@ -0,0 +1,44 @@ + + +# deserialize + +Deserialize function that supports `bigint` and `Map`. + +## Import + +```ts-vue +import { deserialize } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { deserialize } from '{{packageName}}' + +const result = deserialize('{"foo":"wagmi","bar":{"__type":"bigint","value":"123"}}') +``` + +## Parameters + +### value + +`string` + +The string to deserialize. + + +### reviver + +`(key: string, value: any) => any` + +A custom reviver function for handling standard values. + +## Return Type + +`unknown` + +Parsed value. \ No newline at end of file diff --git a/site/shared/utilities/normalizeChainId.md b/site/shared/utilities/normalizeChainId.md new file mode 100644 index 0000000000..6cc1cad75d --- /dev/null +++ b/site/shared/utilities/normalizeChainId.md @@ -0,0 +1,56 @@ + + +# normalizeChainId + +Normalizes a chain ID to a number. + +## Import + +```ts-vue +import { normalizeChainId } from '{{packageName}}' +``` + +## Usage + +:::warning Deprecated +Use `Number` instead. + +```ts-vue +import { normalizeChainId } from '{{packageName}}' // [!code --] +const chainId = normalizeChainId(123n) // [!code --] +const chainId = Number(123n) // [!code ++] +``` +::: + +```ts-vue +import { normalizeChainId } from '{{packageName}}' + +const result = normalizeChainId('0x1') +``` + +## Parameters + + +### chainId + +`bigint | number | string` + +The chain ID to normalize. + +```ts-vue +import { normalizeChainId } from '{{packageName}}' + +normalizeChainId(1n) +normalizeChainId(1) +normalizeChainId('0x1') +``` + +## Return Type + +`number` + +The normalized chain ID. diff --git a/site/shared/utilities/serialize.md b/site/shared/utilities/serialize.md new file mode 100644 index 0000000000..9ce6608e7b --- /dev/null +++ b/site/shared/utilities/serialize.md @@ -0,0 +1,53 @@ + + +# serialize + +Serialize function that supports `bigint` and `Map`. + +## Import + +```ts-vue +import { serialize } from '{{packageName}}' +``` + +## Usage + +```ts-vue +import { serialize } from '{{packageName}}' + +const result = serialize({ foo: 'wagmi', bar: 123n }) +``` + +## Parameters + +### value + +`any` + +The value to stringify. + +### replacer + +`(key: string, value: any) => any` + +A custom replacer function for handling standard values. + +### indent + +`number | null | undefined` + +The number of spaces to indent the output by. + +### circularReplacer + +A custom replacer function for handling circular values. + +## Return Type + +`string` + +Stringified value. \ No newline at end of file diff --git a/site/snippets/abi-event.ts b/site/snippets/abi-event.ts new file mode 100644 index 0000000000..2d5b344ae9 --- /dev/null +++ b/site/snippets/abi-event.ts @@ -0,0 +1,20 @@ +export const abi = [ + { + type: 'event', + name: 'Approval', + inputs: [ + { indexed: true, name: 'owner', type: 'address' }, + { indexed: true, name: 'spender', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { indexed: true, name: 'from', type: 'address' }, + { indexed: true, name: 'to', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, +] as const diff --git a/site/snippets/abi-infinite-read.ts b/site/snippets/abi-infinite-read.ts new file mode 100644 index 0000000000..7e0a2b28ed --- /dev/null +++ b/site/snippets/abi-infinite-read.ts @@ -0,0 +1,23 @@ +export const abi = [ + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getChest', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getFoot', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getHand', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/site/snippets/abi-read.ts b/site/snippets/abi-read.ts new file mode 100644 index 0000000000..f893ed2b20 --- /dev/null +++ b/site/snippets/abi-read.ts @@ -0,0 +1,16 @@ +export const abi = [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, +] as const diff --git a/site/snippets/abi-write.ts b/site/snippets/abi-write.ts new file mode 100644 index 0000000000..93c1b8bb8e --- /dev/null +++ b/site/snippets/abi-write.ts @@ -0,0 +1,23 @@ +export const abi = [ + { + type: 'function', + name: 'approve', + stateMutability: 'nonpayable', + inputs: [ + { name: 'spender', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ type: 'bool' }], + }, + { + type: 'function', + name: 'transferFrom', + stateMutability: 'nonpayable', + inputs: [ + { name: 'sender', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ type: 'bool' }], + }, +] as const diff --git a/site/snippets/core/config-chain-properties.ts b/site/snippets/core/config-chain-properties.ts new file mode 100644 index 0000000000..d9d407cff1 --- /dev/null +++ b/site/snippets/core/config-chain-properties.ts @@ -0,0 +1,11 @@ +import { http, createConfig } from '@wagmi/core' +import { base, celo, mainnet } from '@wagmi/core/chains' + +export const config = createConfig({ + chains: [base, celo, mainnet], + transports: { + [base.id]: http(), + [celo.id]: http(), + [mainnet.id]: http(), + }, +}) diff --git a/site/snippets/core/config.ts b/site/snippets/core/config.ts new file mode 100644 index 0000000000..956f6efa06 --- /dev/null +++ b/site/snippets/core/config.ts @@ -0,0 +1,10 @@ +import { http, createConfig } from '@wagmi/core' +import { mainnet, sepolia } from '@wagmi/core/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/site/snippets/react/app.tsx b/site/snippets/react/app.tsx new file mode 100644 index 0000000000..6fb247a541 --- /dev/null +++ b/site/snippets/react/app.tsx @@ -0,0 +1,16 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import * as React from 'react' +import { WagmiProvider } from 'wagmi' +import { config } from './config' + +export const queryClient = new QueryClient() + +export function App() { + return ( + + + {/** ... */} + + + ) +} diff --git a/site/snippets/react/config-chain-properties.ts b/site/snippets/react/config-chain-properties.ts new file mode 100644 index 0000000000..9c71331028 --- /dev/null +++ b/site/snippets/react/config-chain-properties.ts @@ -0,0 +1,17 @@ +import { http, createConfig } from 'wagmi' +import { base, celo, mainnet } from 'wagmi/chains' + +export const config = createConfig({ + chains: [base, celo, mainnet], + transports: { + [base.id]: http(), + [celo.id]: http(), + [mainnet.id]: http(), + }, +}) + +declare module 'wagmi' { + interface Register { + config: typeof config + } +} diff --git a/site/snippets/react/config.ts b/site/snippets/react/config.ts new file mode 100644 index 0000000000..9739c926ce --- /dev/null +++ b/site/snippets/react/config.ts @@ -0,0 +1,10 @@ +import { http, createConfig } from 'wagmi' +import { mainnet, sepolia } from 'wagmi/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/site/snippets/typedData.ts b/site/snippets/typedData.ts new file mode 100644 index 0000000000..90f01a4155 --- /dev/null +++ b/site/snippets/typedData.ts @@ -0,0 +1,13 @@ +import type { TypedData } from 'viem' + +export const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const satisfies TypedData diff --git a/site/snippets/vue/App.vue b/site/snippets/vue/App.vue new file mode 100644 index 0000000000..ae2a44fd79 --- /dev/null +++ b/site/snippets/vue/App.vue @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/site/snippets/vue/config-chain-properties.ts b/site/snippets/vue/config-chain-properties.ts new file mode 100644 index 0000000000..3c3a9bcaff --- /dev/null +++ b/site/snippets/vue/config-chain-properties.ts @@ -0,0 +1,17 @@ +import { http, createConfig } from '@wagmi/vue' +import { base, celo, mainnet } from '@wagmi/vue/chains' + +export const config = createConfig({ + chains: [base, celo, mainnet], + transports: { + [base.id]: http(), + [celo.id]: http(), + [mainnet.id]: http(), + }, +}) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} diff --git a/site/snippets/vue/config.ts b/site/snippets/vue/config.ts new file mode 100644 index 0000000000..558ae12e36 --- /dev/null +++ b/site/snippets/vue/config.ts @@ -0,0 +1,10 @@ +import { http, createConfig } from '@wagmi/vue' +import { mainnet, sepolia } from '@wagmi/vue/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) diff --git a/site/snippets/vue/main.ts b/site/snippets/vue/main.ts new file mode 100644 index 0000000000..27ca843af0 --- /dev/null +++ b/site/snippets/vue/main.ts @@ -0,0 +1,13 @@ +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { createApp } from 'vue' + +import App from './App.vue' +import { config } from './config' + +export const queryClient = new QueryClient() + +createApp(App) + .use(WagmiPlugin, { config }) + .use(VueQueryPlugin, { queryClient }) + .mount('#app') diff --git a/site/tsconfig.json b/site/tsconfig.json new file mode 100644 index 0000000000..053a800ac2 --- /dev/null +++ b/site/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "preserve", + "lib": ["DOM", "ESNext"], + "module": "ESNext", + "moduleResolution": "node", + "noUnusedLocals": true, + "paths": { + "~/*": ["src/*"] + }, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "target": "esnext", + "types": ["vite/client", "vitepress"] + }, + "include": ["./*.ts", "./.vitepress/**/*.ts", "./.vitepress/**/*.vue"], + "exclude": ["dist", "node_modules", "snippets"] +} diff --git a/site/vercel.json b/site/vercel.json new file mode 100644 index 0000000000..f7abbb9989 --- /dev/null +++ b/site/vercel.json @@ -0,0 +1,148 @@ +{ + "cleanUrls": true, + "redirects": [ + { + "source": "/cli", + "destination": "/cli/getting-started", + "permanent": true + }, + + { + "source": "/core", + "destination": "/core/getting-started", + "permanent": true + }, + { + "source": "/core/migration-guide", + "destination": "/core/guides/migrate-from-v1-to-v2", + "permanent": true + }, + { + "source": "/core/:name(faq)", + "destination": "/core/guides/:name", + "permanent": true + }, + { + "source": "/core/ethers-adapters", + "destination": "/core/guides/ethers-web3", + "permanent": true + }, + { + "source": "/core/:section(chains)", + "destination": "/core/api/:section", + "permanent": true + }, + { + "source": "/core/:section(actions|connectors)/:name", + "destination": "/core/api/:section/:name", + "permanent": true + }, + { + "source": "/core/config", + "destination": "/core/api/createConfig", + "permanent": true + }, + + { + "source": "/react", + "destination": "/react/getting-started", + "permanent": true + }, + { + "source": "/react/comparison", + "destination": "/react/comparisons", + "permanent": true + }, + { + "source": "/react/migration-guide", + "destination": "/react/guides/migrate-from-v1-to-v2", + "permanent": true + }, + { + "source": "/react/:name(faq)", + "destination": "/react/guides/:name", + "permanent": true + }, + { + "source": "/react/ethers-adapters", + "destination": "/react/guides/ethers", + "permanent": true + }, + { + "source": "/react/:section(actions|chains)", + "destination": "/react/api/:section", + "permanent": true + }, + { + "source": "/react/:section(connectors|hooks)/:name", + "destination": "/react/api/:section/:name", + "permanent": true + }, + { + "source": "/react/config", + "destination": "/react/api/createConfig", + "permanent": true + }, + { + "source": "/react/WagmiConfig", + "destination": "/react/api/WagmiProvider", + "permanent": true + }, + { + "source": "/react/prepare-hooks/usePrepareContractWrite", + "destination": "/react/api/hooks/useSimulateContract", + "permanent": true + }, + { + "source": "/react/prepare-hooks/usePrepareSendTransaction", + "destination": "/react/api/hooks/useEstimateFeesPerGas", + "permanent": true + }, + + { + "source": "/examples/connect-wallet", + "destination": "/react/guides/connect-wallet", + "permanent": true + }, + { + "source": "/examples/send-transaction", + "destination": "/react/guides/send-transaction", + "permanent": true + }, + { + "source": "/react/guides/sending-transactions", + "destination": "/react/guides/send-transaction", + "permanent": true + }, + { + "source": "/react/guides/reading-contracts", + "destination": "/react/guides/read-from-contract", + "permanent": true + }, + { + "source": "/examples/contract-write(-dynamic)?", + "destination": "/react/guides/write-to-contract", + "permanent": true + }, + { + "source": "/react/guides/writing-to-contracts", + "destination": "/react/guides/write-to-contract", + "permanent": true + }, + { + "source": "/examples/custom-connector", + "destination": "/dev/creating-connectors", + "permanent": true + }, + { + "source": "/examples/sign-message", + "destination": "https://1.x.wagmi.sh/examples/sign-message", + "permanent": false + }, + { + "source": "/examples/sign-in-with-ethereum", + "destination": "https://1.x.wagmi.sh/examples/sign-in-with-ethereum", + "permanent": false + } + ] +} diff --git a/site/vue/api/Nuxt.md b/site/vue/api/Nuxt.md new file mode 100644 index 0000000000..e785cfd702 --- /dev/null +++ b/site/vue/api/Nuxt.md @@ -0,0 +1,27 @@ +# Nuxt + +[Nuxt Module](https://nuxt.com/docs/guide/concepts/modules) for Wagmi. Adds all [Composables](/vue/api/composables) as [auto-imports](https://nuxt.com/docs/guide/concepts/auto-imports). + +## Usage + +::: code-group +```ts twoslash [nuxt.config.ts] +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + modules: ['@wagmi/vue/nuxt'], +}) +``` +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + diff --git a/site/vue/api/WagmiPlugin.md b/site/vue/api/WagmiPlugin.md new file mode 100644 index 0000000000..96d2dff971 --- /dev/null +++ b/site/vue/api/WagmiPlugin.md @@ -0,0 +1,113 @@ +# WagmiPlugin + +[Vue Plugin](https://vuejs.org/guide/reusability/plugins.html#plugins) for Wagmi. + +## Import + +```ts +import { WagmiPlugin } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```ts [main.ts] +import { createApp } from 'vue' +import { WagmiPlugin } from '@wagmi/vue' + +import App from './App.vue' +import { config } from './config' + +createApp(App) + .use(WagmiPlugin, { config }) + .mount('#app') +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WagmiPluginProps } from '@wagmi/vue' +``` + +### config + +[`Config`](/vue/api/createConfig#config) object to inject with context. + +::: code-group +```ts [main.ts] +import { createApp } from 'vue' +import { WagmiPlugin } from '@wagmi/vue' + +import App from './App.vue' +import { config } from './config' + +createApp(App) + .use(WagmiPlugin, { + config // [!code focus] + }) + .mount('#app') +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### initialState + +`State | undefined` + +- Initial state to hydrate into the [Wagmi Config](/vue/api/createConfig). Useful for SSR. + +::: code-group +```ts [main.ts] +import { createApp } from 'vue' +import { WagmiPlugin } from '@wagmi/vue' + +import App from './App.vue' +import { config } from './config' + +createApp(App) + .use(WagmiPlugin, { + config, + initialState: /* ... */ // [!code focus] + }) + .mount('#app') +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### reconnectOnMount + +`boolean | undefined` + +- Whether or not to reconnect previously connected [connectors](/vue/api/createConfig#connectors) on mount. +- Defaults to `true`. + +::: code-group +```ts [main.ts] +import { createApp } from 'vue' +import { WagmiPlugin } from '@wagmi/vue' + +import App from './App.vue' +import { config } from './config' + +createApp(App) + .use(WagmiPlugin, { + config, + reconnectOnMount: false // [!code focus] + }) + .mount('#app') +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## configKey + +Key to use to provide/inject `Config` via `WagmiPlugin`. + +```ts +import { configKey, type Config } from '@wagmi/vue' +import { inject } from 'vue' + +inject(configKey) +``` diff --git a/site/vue/api/actions.md b/site/vue/api/actions.md new file mode 100644 index 0000000000..fa47b78a44 --- /dev/null +++ b/site/vue/api/actions.md @@ -0,0 +1,30 @@ +# Actions + +Sometimes the declarative nature of Vue Composables doesn't work for parts of your app. For those cases, you can use Wagmi Core Actions directly! + +All the Wagmi Core Actions are importable using the `@wagmi/vue/actions` entrypoint. For example, you can use the `watchBlockNumber` action to watch for block number changes. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +See the [Wagmi Core docs](/core/api/actions) for more info on what actions are available. diff --git a/site/vue/api/chains.md b/site/vue/api/chains.md new file mode 100644 index 0000000000..5693e95547 --- /dev/null +++ b/site/vue/api/chains.md @@ -0,0 +1,26 @@ + + +# Chains + +Viem `Chain` objects. More info at the [Viem docs](https://viem.sh/docs/chains/introduction). + +## Import + +Import via the `'@wagmi/vue/chains'` entrypoint (proxies all chains from `'viem/chains'`). + +```ts +import { mainnet } from '@wagmi/vue/chains' +``` + +## Available Chains + +Chain definitions as of `viem@{{viemVersion}}`. For `viem@latest`, visit the [Viem repo](https://github.com/wevm/viem/blob/main/src/chains/index.ts). + + + + diff --git a/site/vue/api/composables.md b/site/vue/api/composables.md new file mode 100644 index 0000000000..8d1babb981 --- /dev/null +++ b/site/vue/api/composables.md @@ -0,0 +1,25 @@ + + +# Composables + +Vue Composables for accounts, wallets, contracts, transactions, signing, ENS, and more. + +## Import + +```ts +import { useAccount } from '@wagmi/vue' +``` + +## Available Composables + + diff --git a/site/vue/api/composables/useAccount.md b/site/vue/api/composables/useAccount.md new file mode 100644 index 0000000000..41bf8727c3 --- /dev/null +++ b/site/vue/api/composables/useAccount.md @@ -0,0 +1,80 @@ +--- +title: useAccount +description: Composable for getting current account. +--- + +# useAccount + +Composable for getting current account. + +## Import + +```ts +import { useAccount } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue twoslash [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts twoslash +import { type UseAccountParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts twoslash +import { type UseAccountReturnType } from '@wagmi/vue' +``` + + + +## Action + +- [`getAccount`](/core/api/actions/getAccount) diff --git a/site/vue/api/composables/useAccountEffect.md b/site/vue/api/composables/useAccountEffect.md new file mode 100644 index 0000000000..8f42d593d2 --- /dev/null +++ b/site/vue/api/composables/useAccountEffect.md @@ -0,0 +1,113 @@ +--- +title: useAccountEffect +description: Composable for listening to account lifecycle events. +--- + +# useAccountEffect + +Composable for listening to account lifecycle events. + +## Import + +```ts +import { useAccountEffect } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type useAccountEffectParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### onConnect + +`` MaybeRef<((data: { address: `0x${string}`; addresses: readonly [`0x${string}`, ...`0x${string}`[]]; chain: Chain | undefined chainId: number; connector: Connector; isReconnected: boolean }) => void)> | undefined `` + +Callback that is called when accounts are connected. + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from '@wagmi/vue' + +function App() { + useAccountEffect({ + onConnect(data) { // [!code focus] + console.log('Connected!', data) // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### onDisconnect + +`MaybeRef<(() => void)> | undefined` + +Callback that is called when no more accounts are connected. + +::: code-group +```tsx [index.tsx] +import { useAccountEffect } from '@wagmi/vue' + +function App() { + useAccountEffect({ + onDisconnect() { // [!code focus] + console.log('Disconnected!') // [!code focus] + }, // [!code focus] + }) +} +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Action + +- [`getAccount`](/core/api/actions/getAccount) +- [`watchAccount`](/core/api/actions/watchAccount) diff --git a/site/vue/api/composables/useBalance.md b/site/vue/api/composables/useBalance.md new file mode 100644 index 0000000000..a84f6ba886 --- /dev/null +++ b/site/vue/api/composables/useBalance.md @@ -0,0 +1,226 @@ +--- +title: useBalance +description: Composable for fetching native currency or token balance. +--- + + + +# useBalance + +Composable for fetching native currency or token balance. + +## Import + +```ts +import { useBalance } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBalanceParameters } from '@wagmi/vue' +``` + +### address + +`Address | undefined` + +Address to get balance for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get balance at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get balance at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### token + +`Address | undefined` + +ERC-20 token address to get balance for. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### unit + +`'ether' | 'gwei' | 'wei' | number | undefined` + +- Units to use when formatting result. +- Defaults to `'ether'`. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBalanceReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getBalance`](/core/api/actions/getBalance) diff --git a/site/vue/api/composables/useBlockNumber.md b/site/vue/api/composables/useBlockNumber.md new file mode 100644 index 0000000000..ae8d8956ee --- /dev/null +++ b/site/vue/api/composables/useBlockNumber.md @@ -0,0 +1,172 @@ +--- +title: useBlockNumber +description: Composable for fetching the number of the most recent block seen. +--- + + + +# useBlockNumber + +Composable for fetching the number of the most recent block seen. + +## Import + +```ts +import { useBlockNumber } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBlockNumberParameters } from '@wagmi/vue' +``` + +### cacheTime + +`MaybeRef | undefined` + +Time in milliseconds that cached block number will remain in memory. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### chainId + +`MaybeRef | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`MaybeRef | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### watch + +`MaybeRef | undefined` + +- Enables/disables listening for block number changes. +- Can pass a subset of [`UseWatchBlockNumberParameters`](/vue/api/composables/useWatchBlockNumber#parameters) directly to [`useWatchBlockNumber`](/vue/api/composables/useWatchBlockNumber). + +::: code-group +```vue [index.vue] + +``` + +```vue [index-2.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBlockNumberReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getBlockNumber`](/core/api/actions/getBlockNumber) +- [`watchBlockNumber`](/core/api/actions/watchBlockNumber) diff --git a/site/vue/api/composables/useBytecode.md b/site/vue/api/composables/useBytecode.md new file mode 100644 index 0000000000..a6aa3cca6d --- /dev/null +++ b/site/vue/api/composables/useBytecode.md @@ -0,0 +1,209 @@ +--- +title: useBytecode +description: Composable for retrieving the bytecode at an address. +--- + + + +# useBytecode + +Composable for retrieving the bytecode at an address. + +## Import + +```ts +import { useBytecode } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseBytecodeParameters } from '@wagmi/vue' +``` + +### address + +`Address | undefined` + +The contract address. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +The block number to check the bytecode at. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +The block tag to check the bytecode at. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The chain ID to check the bytecode at. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Hooks that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/react/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseBytecodeReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getBytecode`](/core/api/actions/getBytecode) diff --git a/site/vue/api/composables/useChainId.md b/site/vue/api/composables/useChainId.md new file mode 100644 index 0000000000..1419148744 --- /dev/null +++ b/site/vue/api/composables/useChainId.md @@ -0,0 +1,74 @@ +--- +title: useChainId +description: Composable for getting current chain ID. +--- + +# useChainId + +Composable for getting current chain ID. + +## Import + +```ts +import { useChainId } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseChainIdParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseChainIdReturnType } from '@wagmi/vue' +``` + +`number` + +Current chain ID from [`config.state.chainId`](/vue/api/createConfig#chainid). + +::: info +Only returns chain IDs for chains configured via `createConfig`'s [`chains`](/vue/api/createConfig#chains) parameter. + +If the active [connection](/vue/api/createConfig#connection) [`chainId`](/vue/api/createConfig#chainid-1) is not from a chain included in your Wagmi `Config`, `useChainId` will return the last configured chain ID. +::: + +## Action + +- [`getChainId`](/core/api/actions/getChainId) +- [`watchChainId`](/core/api/actions/watchChainId) diff --git a/site/vue/api/composables/useChains.md b/site/vue/api/composables/useChains.md new file mode 100644 index 0000000000..52daf66021 --- /dev/null +++ b/site/vue/api/composables/useChains.md @@ -0,0 +1,67 @@ +--- +title: useChains +description: Composable for getting configured chains +--- + +# useChains + +Composable for getting configured chains + +## Import + +```ts +import { useChains } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseChainsParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseChainsReturnType } from '@wagmi/vue' +``` + +`readonly [Chain, ...Chain[]]` + +Chains from [`config.chains`](/vue/api/createConfig#chains). + +## Action + +- [`getChains`](/core/api/actions/getChains) diff --git a/site/vue/api/composables/useClient.md b/site/vue/api/composables/useClient.md new file mode 100644 index 0000000000..18b3ca91fc --- /dev/null +++ b/site/vue/api/composables/useClient.md @@ -0,0 +1,89 @@ +--- +title: useClient +description: Composable for getting Viem `Client` instance. +--- + +# useClient + +Composable for getting Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. + +## Import + +```ts +import { useClient } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseClientParameters } from '@wagmi/vue' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when getting Viem Client. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseClientReturnType } from '@wagmi/vue' +``` + +`Client | undefined` + +Viem [`Client`](https://viem.sh/docs/clients/custom.html) instance. + +## Action + +- [`getClient`](/core/api/actions/getClient) +- [`watchClient`](/core/api/actions/watchClient) diff --git a/site/vue/api/composables/useConfig.md b/site/vue/api/composables/useConfig.md new file mode 100644 index 0000000000..7555043352 --- /dev/null +++ b/site/vue/api/composables/useConfig.md @@ -0,0 +1,33 @@ +--- +title: useConfig +description: Composable for getting `Config` from the `WagmiPlugin`. +--- + +# useConfig + +Composable for getting [`Config`](/vue/api/createConfig#config) from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +## Import + +```ts +import { useConfig } from 'wagmi' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` + +::: + +## Return Type + +```ts +import { type UseConfigReturnType } from 'wagmi' +``` diff --git a/site/vue/api/composables/useConnect.md b/site/vue/api/composables/useConnect.md new file mode 100644 index 0000000000..d1d586c561 --- /dev/null +++ b/site/vue/api/composables/useConnect.md @@ -0,0 +1,115 @@ +--- +title: useConnect +description: Composable for connecting accounts with connectors. +--- + + + +# useConnect + +Composable for connecting accounts with [connectors](/vue/api/connectors). + +## Import + +```ts +import { useConnect } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseConnectReturnType } from '@wagmi/vue' +``` + +### connectors + +`readonly Connector[]` + +Globally configured connectors via [`createConfig`](/vue/api/createConfig#connectors). Useful for rendering a list of available connectors. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +::: tip +Not all connectors support connecting directly to a `chainId` (e.g. they don't support programmatic chain switching). In those cases, the connector will connect to whatever chain the connector's provider (e.g. wallet) is connected to. +::: + + + +## Action + +- [`connect`](/core/api/actions/connect) diff --git a/site/vue/api/composables/useConnections.md b/site/vue/api/composables/useConnections.md new file mode 100644 index 0000000000..c6fa350f90 --- /dev/null +++ b/site/vue/api/composables/useConnections.md @@ -0,0 +1,64 @@ +--- +title: useConnections +description: Composable for getting active connections. +--- + +# useConnections + +Composable for getting active connections. + +## Import + +```ts +import { useConnections } from 'wagmi' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectionsParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseConnectionsReturnType } from 'wagmi' +``` + +## Action + +- [`getConnections`](/core/api/actions/getConnections) +- [`watchConnections`](/core/api/actions/watchConnections) diff --git a/site/vue/api/composables/useConnectorClient.md b/site/vue/api/composables/useConnectorClient.md new file mode 100644 index 0000000000..61f56ca616 --- /dev/null +++ b/site/vue/api/composables/useConnectorClient.md @@ -0,0 +1,128 @@ +--- +title: useConnectorClient +description: Composable for getting a Viem `Client` object for the current or provided connector. +--- + + + +# useConnectorClient + +Composable for getting a Viem [`Client`](https://viem.sh/docs/clients/custom.html) object for the current or provided connector. + +## Import + +```ts +import { useConnectorClient } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseConnectorClientParameters } from '@wagmi/vue' +``` + +### account + +`Address | Account | undefined` + +Account to use with client. Throws if account is not found on [`connector`](#connector). + +```vue + +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use with client. + +```vue + +``` + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +- Connector to get client for. +- Defaults to current connector. + +```vue + +``` + + + +## Return Type + +```ts +import { type UseConnectorClientReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getConnectorClient`](/core/api/actions/getConnectorClient) diff --git a/site/vue/api/composables/useConnectors.md b/site/vue/api/composables/useConnectors.md new file mode 100644 index 0000000000..c05bc3d381 --- /dev/null +++ b/site/vue/api/composables/useConnectors.md @@ -0,0 +1,41 @@ +--- +title: useConnectors +description: Composable for getting configured connectors. +--- + +# useConnectors + +Composable for getting configured connectors. + +## Import + +```ts +import { useConnectors } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Return Type + +```ts +import { type UseConnectorsReturnType } from '@wagmi/vue' +``` + +`readonly Connector[]` + +Connectors from [`config.connectors`](/vue/api/createConfig#connectors-1). + +## Action + +- [`getConnectors`](/core/api/actions/getConnectors) diff --git a/site/vue/api/composables/useDisconnect.md b/site/vue/api/composables/useDisconnect.md new file mode 100644 index 0000000000..66635c3c05 --- /dev/null +++ b/site/vue/api/composables/useDisconnect.md @@ -0,0 +1,111 @@ +--- +title: useDisconnect +description: Composable for disconnecting connections. +--- + + + +# useDisconnect + +Composable for disconnecting connections. + +## Import + +```ts +import { useDisconnect } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseDisconnectParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseDisconnectReturnType } from '@wagmi/vue' +``` + +### connectors + +`readonly Connector[]` + +Connectors that are currently connected. Useful for rendering a list of connectors to disconnect. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + + + +## Action + +- [`disconnect`](/core/api/actions/connect) diff --git a/site/vue/api/composables/useEnsAddress.md b/site/vue/api/composables/useEnsAddress.md new file mode 100644 index 0000000000..7aa72eed65 --- /dev/null +++ b/site/vue/api/composables/useEnsAddress.md @@ -0,0 +1,238 @@ +--- +title: useEnsAddress +description: Composable for fetching ENS address for name. +--- + + + +# useEnsAddress + +Composable for fetching ENS address for name. + +## Import + +```ts +import { useEnsAddress } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsAddress`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsAddressParameters } from '@wagmi/vue' +``` + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS address at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS address at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### coinType + +`number | undefined` + +The [ENSIP-9](https://docs.ens.domains/ens-improvement-proposals/ensip-9-multichain-address-resolution) coin type to fetch the address for. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### name + +`string | undefined` + +Name to get the address for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsAddressReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getEnsAddress`](/core/api/actions/getEnsAddress) diff --git a/site/vue/api/composables/useEnsAvatar.md b/site/vue/api/composables/useEnsAvatar.md new file mode 100644 index 0000000000..5f352e2559 --- /dev/null +++ b/site/vue/api/composables/useEnsAvatar.md @@ -0,0 +1,262 @@ +--- +title: useEnsAvatar +description: Composable for fetching ENS avatar for name. +--- + + + +# useEnsAvatar + +Composable for fetching ENS avatar for name. + +## Import + +```ts +import { useEnsAvatar } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +::: warning +Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `useEnsAvatar`. You can use Viem's built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this. +::: + +## Parameters + +```ts +import { type UseEnsAvatarParameters } from '@wagmi/vue' +``` + +--- + +### assetGatewayUrls + +`{ ipfs?: string | undefined; arweave?: string | undefined } | undefined` + +Gateway urls to resolve IPFS and/or Arweave assets. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockNumber + +`bigint | undefined` + +Block number to get ENS avatar at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS avatar at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### gatewayUrls + +`string[] | undefined` + +A set of Universal Resolver gateways, used for resolving CCIP-Read requests made through the ENS Universal Resolver Contract. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### name + +`string | undefined` + +Name to get the avatar for. [`enabled`](#enabled) set to `false` if `name` is `undefined`. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsAvatarReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getEnsAvatar`](/core/api/actions/getEnsAvatar) diff --git a/site/vue/api/composables/useEnsName.md b/site/vue/api/composables/useEnsName.md new file mode 100644 index 0000000000..74461c9964 --- /dev/null +++ b/site/vue/api/composables/useEnsName.md @@ -0,0 +1,206 @@ +--- +title: useEnsName +description: Composable for fetching primary ENS name for address. +--- + + + +# useEnsName + +Composable for fetching primary ENS name for address. + +## Import + +```ts +import { useEnsName } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEnsNameParameters } from '@wagmi/vue' +``` + +### address + +`Address | undefined` + +Name to get the resolver for. [`enabled`](#enabled) set to `false` if `address` is `undefined`. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to get ENS name at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get ENS name at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### universalResolverAddress + +`Address | undefined` + +- Address of ENS Universal Resolver Contract. +- Defaults to current chain's Universal Resolver Contract address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEnsNameReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getEnsName`](/core/api/actions/getEnsName) diff --git a/site/vue/api/composables/useEstimateGas.md b/site/vue/api/composables/useEstimateGas.md new file mode 100644 index 0000000000..4c05fa7674 --- /dev/null +++ b/site/vue/api/composables/useEstimateGas.md @@ -0,0 +1,387 @@ +--- +title: useEstimateGas +description: Composable for estimating the gas necessary to complete a transaction without submitting it to the network. +--- + + + +# useEstimateGas + +Composable for estimating the gas necessary to complete a transaction without submitting it to the network. + +## Import + +```ts +import { useEstimateGas } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseEstimateGasParameters } from '@wagmi/vue' +``` + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### account + +`Address | Account | undefined` + +Account to use when estimating gas. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +Chain ID to target when estimating gas. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +Connector to estimate with. If no [`account`](#account) is provided, will use default account from connector. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### data + +`` `0x${string}` | undefined `` + +A contract hashed method call with encoded function data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### to + +`Address | undefined` + +The transaction recipient or contract address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseEstimateGasReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`estimateGas`](/core/api/actions/estimateGas) diff --git a/site/vue/api/composables/useReadContract.md b/site/vue/api/composables/useReadContract.md new file mode 100644 index 0000000000..98e44af8c1 --- /dev/null +++ b/site/vue/api/composables/useReadContract.md @@ -0,0 +1,410 @@ +--- +title: useReadContract +description: Composable for calling a read-only function on a contract, and returning the response. +--- + + + +# useReadContract + +Composable for calling a **read-only** function on a contract, and returning the response. + +A **read-only** function (constant function) on a Solidity contract is denoted by a pure or view keyword. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas. + +## Import + +```ts +import { useReadContract } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseReadContractParameters } from '@wagmi/vue' +``` + +### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### account + +`Account | undefined` + +Account to use when calling the contract (`msg.sender`). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to call contract at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to call contract at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-read.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseReadContractReturnType } from '@wagmi/vue' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and the return type. See the Wagmi [TypeScript docs](/vue/typescript) for more information. + +::: code-group +```ts twoslash [Inline] +import { createConfig, http, useReadContract } from '@wagmi/vue' +import { mainnet, sepolia } from 'wagmi/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const result = useReadContract({ + abi: [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, + ], + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + // ^? + + + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + // ^? + + + +}) + +result.data +// ^? +``` + +```ts twoslash [Const-Asserted] +import { createConfig, http, useReadContract } from '@wagmi/vue' +import { mainnet, sepolia } from 'wagmi/chains' + +const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +// ---cut--- +const abi = [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, +] as const + +const result = useReadContract({ + abi, + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + functionName: 'balanceOf', + // ^? + + + args: ['0x6b175474e89094c44da98b954eedeac495271d0f'], + // ^? +}) + +result.data +// ^? +``` +::: + + + +## Action + +- [`readContract`](/core/api/actions/readContract) diff --git a/site/vue/api/composables/useReconnect.md b/site/vue/api/composables/useReconnect.md new file mode 100644 index 0000000000..56316fee5f --- /dev/null +++ b/site/vue/api/composables/useReconnect.md @@ -0,0 +1,106 @@ +--- +title: useReconnect +description: Composable for reconnecting connectors. +--- + + + +# useReconnect + +Composable for reconnecting [connectors](/core/api/connectors). + +## Import + +```ts +import { useReconnect } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` + +::: + +::: tip +When [`WagmiPlugin['reconnectOnMount']`](/vue/api/WagmiPlugin#reconnectonmount) is `true`, `reconnect` is called automatically on mount. +::: + +## Parameters + +```ts +import { type UseReconnectParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseReconnectReturnType } from '@wagmi/vue' +``` + +### connectors + +`readonly Connector[]` + +Globally configured connectors via [`createConfig`](/vue/api/createConfig#connectors). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + + + +## Action + +- [`reconnect`](/core/api/actions/reconnect) diff --git a/site/vue/api/composables/useSendTransaction.md b/site/vue/api/composables/useSendTransaction.md new file mode 100644 index 0000000000..62f581143a --- /dev/null +++ b/site/vue/api/composables/useSendTransaction.md @@ -0,0 +1,91 @@ +--- +title: useSendTransaction +description: Composable for creating, signing, and sending transactions to networks. +--- + + + +# useSendTransaction + +Composable for creating, signing, and sending transactions to networks. + +## Import + +```ts +import { useSendTransaction } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSendTransactionParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSendTransactionReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`sendTransaction`](/core/api/actions/sendTransaction) diff --git a/site/vue/api/composables/useSignMessage.md b/site/vue/api/composables/useSignMessage.md new file mode 100644 index 0000000000..dacbc6153c --- /dev/null +++ b/site/vue/api/composables/useSignMessage.md @@ -0,0 +1,85 @@ +--- +title: useSignMessage +description: Composable for signing messages. +--- + + + +# useSignMessage + +Composable for signing messages. + +## Import + +```ts +import { useSignMessage } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSignMessageParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSignMessageReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`signMessage`](/core/api/actions/signMessage) diff --git a/site/vue/api/composables/useSignTypedData.md b/site/vue/api/composables/useSignTypedData.md new file mode 100644 index 0000000000..6c21a09012 --- /dev/null +++ b/site/vue/api/composables/useSignTypedData.md @@ -0,0 +1,214 @@ +--- +title: useSignTypedData +description: Composable for signing typed data and calculating an Ethereum-specific EIP-712 signature. +--- + + + +# useSignTypedData + +Composable for signing typed data and calculating an Ethereum-specific [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature. + +## Import + +```ts +import { useSignTypedData } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSignTypedDataParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSignTypedDataReturnType } from '@wagmi/vue' +``` + + + +## Type Inference + +With [`types`](/core/api/actions/signTypedData#types) setup correctly, TypeScript will infer the correct types for [`domain`](/core/api/actions/signTypedData#domain), [`message`](/core/api/actions/signTypedData#message), and [`primaryType`](/core/api/actions/signTypedData#primarytype). See the Wagmi [TypeScript docs](/vue/typescript) for more information. + +::: code-group +```ts twoslash [Inline] +import { useSignTypedData } from '@wagmi/vue' +// ---cut--- +const { signTypedData } = useSignTypedData() + +signTypedData({ + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +```ts twoslash [Const-Asserted] +import { useSignTypedData } from '@wagmi/vue' +// ---cut--- +const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], +} as const + +const { signTypedData } = useSignTypedData() + +signTypedData({ + types, + primaryType: 'Mail', + // ^? + + + message: { + // ^? + + + + + + + + + + + + + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, +}) +``` +::: + + + +## Action + +- [`signTypedData`](/core/api/actions/signTypedData) diff --git a/site/vue/api/composables/useSimulateContract.md b/site/vue/api/composables/useSimulateContract.md new file mode 100644 index 0000000000..7e10c486d6 --- /dev/null +++ b/site/vue/api/composables/useSimulateContract.md @@ -0,0 +1,686 @@ +--- +title: useSimulateContract +description: Composable for simulating/validating a contract interaction. +--- + + + +# useSimulateContract + +Composable for simulating/validating a contract interaction. + +## Import + +```ts +import { useSimulateContract } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Parameters + +```ts +import { type UseSimulateContractParameters } from '@wagmi/vue' +``` + +### abi + +`Abi | undefined` + +The contract's ABI. Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### accessList + +`AccessList | undefined` + +The access list. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### account + +`Account | undefined` + +Account to use when calling the contract (`msg.sender`). Throws if account is not found on [`connector`](#connector). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### address + +`Address | undefined` + +The contract's address. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### args + +`readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`functionName`](#functionname). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### blockNumber + +`bigint | undefined` + +Block number to call contract at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to call contract at. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### connector + +`Connector | undefined` + +[Connector](/vue/api/connectors) to simulate transaction with. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### dataSuffix + +`` `0x${string}` | undefined `` + +Data to append to the end of the calldata. Useful for adding a ["domain" tag](https://opensea.notion.site/opensea/Seaport-Order-Attributions-ec2d69bf455041a5baa490941aad307f). + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### functionName + +`string | undefined` + +- Function to call on the contract. +- Inferred from [`abi`](#abi). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### gas + +`bigint | undefined` + +Gas provided for transaction execution. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### gasPrice + +`bigint | undefined` + +The price in wei to pay per gas. Only applies to [Legacy Transactions](https://viem.sh/docs/glossary/terms.html#legacy-transaction). + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### maxFeePerGas + +`bigint | undefined` + +Total fee per gas in wei, inclusive of [`maxPriorityFeePerGas`](#maxPriorityFeePerGas). Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### maxPriorityFeePerGas + +`bigint | undefined` + +Max priority fee per gas in wei. Only applies to [EIP-1559 Transactions](https://viem.sh/docs/glossary/terms.html#eip-1559-transaction). + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +--- + +### nonce + +`number` + +Unique number identifying this transaction. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### type + +`'legacy' | 'eip1559' | 'eip2930' | undefined` + +Optional transaction request type to narrow parameters. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### value + +`bigint | undefined` + +Value in wei sent with this transaction. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.ts] + +``` +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSimulateContractReturnType } from '@wagmi/vue' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](#functionname), [`args`](#args), and [`value`](#value). See the Wagmi [TypeScript docs](/vue/typescript) for more information. + + + +## Action + +- [`simulateContract`](/core/api/actions/simulateContract) diff --git a/site/vue/api/composables/useSwitchAccount.md b/site/vue/api/composables/useSwitchAccount.md new file mode 100644 index 0000000000..bf965bbebb --- /dev/null +++ b/site/vue/api/composables/useSwitchAccount.md @@ -0,0 +1,120 @@ +--- +title: useSwitchAccount +description: Composable for switching the current account. +--- + + + +# useSwitchAccount + +Composable for switching the current account. + +## Import + +```ts +import { useSwitchAccount } from 'wagmi' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseSwitchAccountParameters } from 'wagmi' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSwitchAccountReturnType } from 'wagmi' +``` + +### connectors + +`readonly Connector[]` + +Globally configured and actively connected connectors. Useful for rendering a list of available connectors to switch to. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + + + +## Action + +- [`switchAccount`](/core/api/actions/switchAccount) diff --git a/site/vue/api/composables/useSwitchChain.md b/site/vue/api/composables/useSwitchChain.md new file mode 100644 index 0000000000..c7dace93ef --- /dev/null +++ b/site/vue/api/composables/useSwitchChain.md @@ -0,0 +1,124 @@ +--- +title: useSwitchChain +description: Composable for switching the target chain for a connector or the Wagmi `Config`. +--- + + + +# useSwitchChain + +Composable for switching the target chain for a connector or the Wagmi [`Config`](/vue/api/createConfig#config). + +## Import + +```ts +import { useSwitchChain } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +::: tip +When connected, `switchChain` will switch the target chain for the connector. When not connected, `switchChain` will switch the target chain for the Wagmi [`Config`](/vue/api/createConfig#config). +::: + +## Parameters + +```ts +import { type UseSwitchChainParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseSwitchChainReturnType } from '@wagmi/vue' +``` + +### chains + +`readonly [Chain, ...Chain[]]` + +Globally configured chains. Useful for rendering a list of available chains to switch to. + +::: code-group +```vue [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + + + +## Action + +- [`switchChain`](/core/api/actions/switchChain) diff --git a/site/vue/api/composables/useTransaction.md b/site/vue/api/composables/useTransaction.md new file mode 100644 index 0000000000..80fedc53d3 --- /dev/null +++ b/site/vue/api/composables/useTransaction.md @@ -0,0 +1,184 @@ +--- +title: useTransaction +description: Composable for fetching transactions given hashes or block identifiers. +--- + + + +# useTransaction + +Composable for fetching transactions given hashes or block identifiers. + +## Import + +```ts +import { useTransaction } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionParameters } from '@wagmi/vue' +``` + +--- + +### blockHash + +`bigint | undefined` + +Block hash to get transaction at (with [`index`](#index)). + +```vue + +``` + +### blockNumber + +`bigint | undefined` + +Block number to get transaction at (with [`index`](#index)). + +```vue + +``` + +### blockTag + +`'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | undefined` + +Block tag to get transaction at (with [`index`](#index)). + +```vue + +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +```vue + +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### hash + +`` `0x${string}` | undefined `` + +Hash to get transaction. [`enabled`](#enabled) set to `false` if `hash` and [`index`](#index) are `undefined`. + +```vue + +``` + +### index + +`number | undefined` + +An index to be used with a block identifier ([hash](#blockhash), [number](#blocknumber), or [tag](#blocktag)). [`enabled`](#enabled) set to `false` if `index` and [`hash`](#hash) are `undefined`. + +```vue + +``` + + + +## Return Type + +```ts +import { type UseTransactionReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getTransaction`](/core/api/actions/getTransaction) diff --git a/site/vue/api/composables/useTransactionReceipt.md b/site/vue/api/composables/useTransactionReceipt.md new file mode 100644 index 0000000000..e610c23840 --- /dev/null +++ b/site/vue/api/composables/useTransactionReceipt.md @@ -0,0 +1,141 @@ +--- +title: useTransactionReceipt +description: Composable for return the Transaction Receipt given a Transaction hash. +--- + + + +# useTransactionReceipt + +Composable for return the [Transaction Receipt](https://viem.sh/docs/glossary/terms.html#transaction-receipt) given a [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + +## Import + +```ts +import { useTransactionReceipt } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseTransactionReceiptParameters } from '@wagmi/vue' +``` + +### hash + +`` `0x${string}` | undefined `` + +A transaction hash. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### chainId + +`config['chains'][number]['id'] | undefined` + +The ID of chain to return the transaction receipt from. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### scopeKey + +`string | undefined` + +Scopes the cache to a given context. Composables that have identical context will share the same cache. + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseTransactionReceiptReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`getTransactionReceipt`](/core/api/actions/getTransactionReceipt) diff --git a/site/vue/api/composables/useWaitForTransactionReceipt.md b/site/vue/api/composables/useWaitForTransactionReceipt.md new file mode 100644 index 0000000000..80c887a8f7 --- /dev/null +++ b/site/vue/api/composables/useWaitForTransactionReceipt.md @@ -0,0 +1,168 @@ +--- +title: useWaitForTransactionReceipt +description: Composable that waits for the transaction to be included on a block, and then returns the transaction receipt. If the transaction reverts, then the action will throw an error. Replacement detection (e.g. sped up transactions) is also supported. +--- + + + +# useWaitForTransactionReceipt + +Composable that waits for the transaction to be included on a block, and then returns the transaction receipt. If the transaction reverts, then the action will throw an error. Replacement detection (e.g. sped up transactions) is also supported. + +## Import + +```ts +import { useWaitForTransactionReceipt } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type WaitForTransactionReceiptParameters } from '@wagmi/core' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +```vue [index.vue] + +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group +```vue [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +### confirmations + +`number | undefined` + +The number of confirmations (blocks that have passed) to wait before resolving. + +```vue [index.vue] + +``` + +### onReplaced + +` +(({ reason: 'replaced' | 'repriced' | 'cancelled'; replacedTransaction: Transaction; transaction: Transaction; transactionReceipt: TransactionReceipt }) => void) | undefined +` + +Optional callback to emit if the transaction has been replaced. + +```vue [index.vue] + +``` + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/vue/api/createConfig#pollinginterval). + +```vue [index.vue] + +``` + +### hash + +`` `0x${string}` | undefined `` + +The transaction hash to wait for. [`enabled`](#enabled) set to `false` if `hash` is `undefined`. + +```vue [index.vue] + +``` + + + +## Return Type + +```ts +import { type UseWaitForTransactionReceiptReturnType } from '@wagmi/vue' +``` + + + + + +## Action + +- [`waitForTransactionReceipt`](/core/api/actions/waitForTransactionReceipt) diff --git a/site/vue/api/composables/useWatchBlockNumber.md b/site/vue/api/composables/useWatchBlockNumber.md new file mode 100644 index 0000000000..5d0a561cd1 --- /dev/null +++ b/site/vue/api/composables/useWatchBlockNumber.md @@ -0,0 +1,109 @@ +# useWatchBlockNumber + +Composable that watches for block number changes. + +## Import + +```ts +import { useWatchBlockNumber } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```ts [index.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchBlockNumberParameters } from '@wagmi/vue' +``` + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to watch blocks at. + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +### emitMissed + +`boolean` + +Whether or not to emit missed blocks to the callback. Defaults to `false`. + +Missed blocks may occur in instances where internet connection is lost, or the block time is lesser than the polling interval of the client. + +### emitOnBegin + +`boolean` + +Whether or not to emit the block to the callback when the subscription opens. Defaults to `false`. + +### enabled + +`boolean` + +Whether or not to watch for blocks. Defaults to `true`. + +### onBlockNumber + +`(block: Block, prevblock: Block | undefined) => void` + +Callback for when block changes. + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block. + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +## Return Type + +```ts +import { type UseWatchBlockNumberReturnType } from '@wagmi/vue' +``` + +Function for cleaning up watcher. + +## Action + +- [`watchBlockNumber`](/core/api/actions/watchBlockNumber) diff --git a/site/vue/api/composables/useWatchContractEvent.md b/site/vue/api/composables/useWatchContractEvent.md new file mode 100644 index 0000000000..5467c49a50 --- /dev/null +++ b/site/vue/api/composables/useWatchContractEvent.md @@ -0,0 +1,134 @@ +# useWatchContractEvent + +Composable that watches and returns emitted contract event logs. + +## Import + +```ts +import { useWatchContractEvent } from '@wagmi/vue' +``` + +## Usage + +::: code-group +```ts [index.vue] + +``` +<<< @/snippets/abi-event.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Parameters + +```ts +import { type UseWatchContractEventParameters } from '@wagmi/vue' +``` + +### abi + +`Abi` + +The contract's ABI. Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for how to set up ABIs for maximum type inference and safety. + +### address + +`Address | undefined` + +The contract's address. + +### args + +`object | readonly unknown[] | undefined` + +- Arguments to pass when calling the contract. +- Inferred from [`abi`](#abi) and [`eventName`](#eventname). + +### batch + +`boolean | undefined` + +- Whether or not the events should be batched on each invocation. +- Defaults to `true`. + +### chainId + +`config['chains'][number]['id'] | undefined` + +ID of chain to use when fetching data. + +### config + +`Config | undefined` + +[`Config`](/react/api/createConfig#config) to use instead of retrieving from the nearest [`WagmiProvider`](/react/api/WagmiProvider). + +### eventName + +`string` + +- Event to listen for the contract. +- Inferred from [`abi`](#abi). + +### onError + +`((error: Error) => void) | undefined` + +Error thrown from getting the block number. + +### onLogs + +`(logs: Log[], prevLogs: Log[] | undefined) => void` + +Callback for when logs changes. + +### poll + +`boolean | undefined` + +- Whether or not to use a polling mechanism to check for new blocks instead of a WebSocket subscription. +- Defaults to `false` for WebSocket Clients, and `true` for non-WebSocket Clients. + +### pollingInterval + +`number | undefined` + +- Polling frequency (in milliseconds). +- Defaults to the [Config's `pollingInterval` config](/core/api/createConfig#pollinginterval). + +### strict + +`boolean | undefined` + +- Defaults to `false`. + +### syncConnectedChain + +`boolean | undefined` + +- Set up subscriber for connected chain changes. +- Defaults to [`Config['syncConnectedChain']`](/core/api/createConfig#syncconnectedchain). + +## Return Type + +```ts +import { type UseWatchContractEventReturnType } from '@wagmi/vue' +``` + +Hook returns `void` + +## Action + +- [`watchContractEvent`](/core/api/actions/watchContractEvent) + diff --git a/site/vue/api/composables/useWriteContract.md b/site/vue/api/composables/useWriteContract.md new file mode 100644 index 0000000000..1221345851 --- /dev/null +++ b/site/vue/api/composables/useWriteContract.md @@ -0,0 +1,112 @@ +--- +title: useWriteContract +description: Composable for executing a write function on a contract. +--- + + + +# useWriteContract + +Composable for executing a write function on a contract. + +A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state. + +## Import + +```ts +import { useWriteContract } from '@wagmi/vue' +``` + +## Usage + +::: code-group + +```vue [index.vue] + + + +``` + +<<< @/snippets/abi-write.ts[abi.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + + + + + +## Parameters + +```ts +import { type UseWriteContractParameters } from '@wagmi/vue' +``` + +### config + +`Config | undefined` + +[`Config`](/vue/api/createConfig#config) to use instead of retrieving from the [`WagmiPlugin`](/vue/api/WagmiPlugin). + +::: code-group + +```vue [index.vue] + +``` + +<<< @/snippets/vue/config.ts[config.ts] +::: + + + +## Return Type + +```ts +import { type UseWriteContractReturnType } from '@wagmi/vue' +``` + +The return type's [`data`](#data) property is inferrable via the combination of [`abi`](#abi), [`functionName`](#functionname), and [`args`](#args). Check out the [TypeScript docs](/vue/typescript#const-assert-abis-typed-data) for more info. + + + +## Type Inference + +With [`abi`](/core/api/actions/writeContract#abi) setup correctly, TypeScript will infer the correct types for [`functionName`](/core/api/actions/writeContract#functionname), [`args`](/core/api/actions/writeContract#args), and the [`value`](/core/api/actions/writeContract##value). See the Wagmi [TypeScript docs](/vue/typescript) for more information. + + + +## Action + +- [`writeContract`](/core/api/actions/writeContract) diff --git a/site/vue/api/connectors.md b/site/vue/api/connectors.md new file mode 100644 index 0000000000..6d3a189936 --- /dev/null +++ b/site/vue/api/connectors.md @@ -0,0 +1,28 @@ + + +# Connectors + +Connectors for popular wallet providers and protocols. + +## Import + +Import via the `'@wagmi/vue/connectors'` entrypoint. + +```ts +import { injected } from '@wagmi/vue/connectors' +``` + +## Available Connectors + + diff --git a/site/vue/api/connectors/coinbaseWallet.md b/site/vue/api/connectors/coinbaseWallet.md new file mode 100644 index 0000000000..839a80f567 --- /dev/null +++ b/site/vue/api/connectors/coinbaseWallet.md @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/site/vue/api/connectors/injected.md b/site/vue/api/connectors/injected.md new file mode 100644 index 0000000000..45c5e158e0 --- /dev/null +++ b/site/vue/api/connectors/injected.md @@ -0,0 +1,7 @@ + + + diff --git a/site/vue/api/connectors/metaMask.md b/site/vue/api/connectors/metaMask.md new file mode 100644 index 0000000000..30d15d7794 --- /dev/null +++ b/site/vue/api/connectors/metaMask.md @@ -0,0 +1,7 @@ + + + diff --git a/site/vue/api/connectors/mock.md b/site/vue/api/connectors/mock.md new file mode 100644 index 0000000000..532cc19fbf --- /dev/null +++ b/site/vue/api/connectors/mock.md @@ -0,0 +1,6 @@ + + + diff --git a/site/vue/api/connectors/safe.md b/site/vue/api/connectors/safe.md new file mode 100644 index 0000000000..85fb516ca7 --- /dev/null +++ b/site/vue/api/connectors/safe.md @@ -0,0 +1,6 @@ + + + diff --git a/site/vue/api/connectors/walletConnect.md b/site/vue/api/connectors/walletConnect.md new file mode 100644 index 0000000000..c3840c3979 --- /dev/null +++ b/site/vue/api/connectors/walletConnect.md @@ -0,0 +1,6 @@ + + + diff --git a/site/vue/api/createConfig.md b/site/vue/api/createConfig.md new file mode 100644 index 0000000000..8210634c08 --- /dev/null +++ b/site/vue/api/createConfig.md @@ -0,0 +1,7 @@ + + + diff --git a/site/vue/api/createStorage.md b/site/vue/api/createStorage.md new file mode 100644 index 0000000000..f4901773c1 --- /dev/null +++ b/site/vue/api/createStorage.md @@ -0,0 +1,6 @@ + + + diff --git a/site/vue/api/errors.md b/site/vue/api/errors.md new file mode 100644 index 0000000000..d914152150 --- /dev/null +++ b/site/vue/api/errors.md @@ -0,0 +1,10 @@ + + +# Errors + +Error classes used by Wagmi. + + \ No newline at end of file diff --git a/site/vue/api/transports.md b/site/vue/api/transports.md new file mode 100644 index 0000000000..c35d06b631 --- /dev/null +++ b/site/vue/api/transports.md @@ -0,0 +1,28 @@ + + +# Transports + +[`createConfig`](/vue/api/createConfig) can be instantiated with a set of Transports for each chain. A Transport is the intermediary layer that is responsible for executing outgoing JSON-RPC requests to the RPC Provider (e.g. Alchemy, Infura, etc). + +## Import + +```ts +import { http } from '@wagmi/vue' +``` + +## Built-In Transports + +Available via the `'@wagmi/vue'` entrypoint. + + diff --git a/site/vue/api/transports/custom.md b/site/vue/api/transports/custom.md new file mode 100644 index 0000000000..4ece9d2d46 --- /dev/null +++ b/site/vue/api/transports/custom.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/api/transports/fallback.md b/site/vue/api/transports/fallback.md new file mode 100644 index 0000000000..084762db3b --- /dev/null +++ b/site/vue/api/transports/fallback.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/api/transports/http.md b/site/vue/api/transports/http.md new file mode 100644 index 0000000000..93e1fc3c93 --- /dev/null +++ b/site/vue/api/transports/http.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/api/transports/unstable_connector.md b/site/vue/api/transports/unstable_connector.md new file mode 100644 index 0000000000..8bb1bfc52f --- /dev/null +++ b/site/vue/api/transports/unstable_connector.md @@ -0,0 +1,6 @@ + + + diff --git a/site/vue/api/transports/webSocket.md b/site/vue/api/transports/webSocket.md new file mode 100644 index 0000000000..3559e6ff89 --- /dev/null +++ b/site/vue/api/transports/webSocket.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/api/utilities/deserialize.md b/site/vue/api/utilities/deserialize.md new file mode 100644 index 0000000000..871aabe907 --- /dev/null +++ b/site/vue/api/utilities/deserialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/api/utilities/serialize.md b/site/vue/api/utilities/serialize.md new file mode 100644 index 0000000000..63abbda162 --- /dev/null +++ b/site/vue/api/utilities/serialize.md @@ -0,0 +1,5 @@ + + + diff --git a/site/vue/getting-started.md b/site/vue/getting-started.md new file mode 100644 index 0000000000..c36bceab02 --- /dev/null +++ b/site/vue/getting-started.md @@ -0,0 +1,217 @@ + + +# Getting Started + +## Overview + +Wagmi is a collection of Vue composition utilities for Ethereum. You can learn more about the rationale behind the project in the [Why Wagmi](/vue/why) section. + +## Automatic Installation + +For new projects, it is recommended to set up your Wagmi app using the [`create-wagmi`](/cli/create-wagmi) command line interface (CLI). This will create a new Wagmi project using TypeScript and install the required dependencies. + +::: code-group +```bash [pnpm] +pnpm create wagmi +``` + +```bash [npm] +npm create wagmi@latest +``` + +```bash [yarn] +yarn create wagmi +``` + +```bash [bun] +bun create wagmi +``` +::: + +Once the command runs, you'll see some prompts to complete. + +```ansi +Project name: wagmi-project +Select a framework: Vue / Vanilla +... +``` + +After the prompts, `create-wagmi` will create a directory with your project name and install the required dependencies. Check out the `README.md` for further instructions (if required). + +## Manual Installation + +To manually add Wagmi to your project, install the required packages. + +::: code-group +```bash-vue [pnpm] +pnpm add @wagmi/vue viem@{{viemVersion}} @tanstack/vue-query +``` + +```bash-vue [npm] +npm install @wagmi/vue viem@{{viemVersion}} @tanstack/vue-query +``` + +```bash-vue [yarn] +yarn add @wagmi/vue viem@{{viemVersion}} @tanstack/vue-query +``` + +```bash-vue [bun] +bun add @wagmi/vue viem@{{viemVersion}} @tanstack/vue-query +``` +::: + +- [Viem](https://viem.sh) is a TypeScript interface for Ethereum that performs blockchain operations. +- [TanStack Query](https://tanstack.com/query/v5) is an async state manager that handles requests, caching, and more. +- [TypeScript](/vue/typescript) is optional, but highly recommended. Learn more about [TypeScript support](/vue/typescript). + +### Create Config + +Create and export a new Wagmi config using `createConfig`. + +::: code-group +<<< @/snippets/vue/config.ts[config.ts] +::: + +In this example, Wagmi is configured to use the Mainnet and Sepolia chains, and `injected` connector. Check out the [`createConfig` docs](/vue/api/createConfig) for more configuration options. + +::: details TypeScript Tip +If you are using TypeScript, you can "register" the Wagmi config or use the hook `config` property to get strong type-safety in places that wouldn't normally have type info. + +::: code-group +```ts twoslash [register config] +// @errors: 2322 +import { type Config } from '@wagmi/vue' +import { mainnet, sepolia } from '@wagmi/vue/chains' + +declare const config: Config +// ---cut--- +import { useBlockNumber } from '@wagmi/vue' + +useBlockNumber({ chainId: 123 }) + +declare module '@wagmi/vue' { + interface Register { + config: typeof config + } +} +``` + +```ts twoslash [hook config property] +// @errors: 2322 +import { type Config } from '@wagmi/vue' +import { mainnet, sepolia } from '@wagmi/vue/chains' + +declare const config: Config +// ---cut--- +import { useBlockNumber } from '@wagmi/vue' + +useBlockNumber({ chainId: 123, config }) +``` + +By registering or using the hook `config` property, `useBlockNumber`'s `chainId` is strongly typed to only allow Mainnet and Sepolia IDs. Learn more by reading the [TypeScript docs](/vue/typescript#config-types). +::: + +### Add Plugin to App + +Add the `WagmiPlugin` to your app instance and pass the `config` you created earlier to the plugin options. + +::: code-group +```tsx [main.ts] +import { WagmiPlugin } from '@wagmi/vue' // [!code focus] +import { createApp } from 'vue' +import { config } from './config' // [!code focus] +import App from './App.vue' + +createApp(App) + .use(WagmiPlugin, { config }) // [!code focus] + .mount('#app') +``` +```vue [App.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +Check out the [`WagmiPlugin` docs](/vue/api/WagmiPlugin) to learn more about the plugin API. + +### Setup TanStack Query + +After the `WagmiPlugin`, attach the `VueQueryPlugin` to your app, and pass a new `QueryClient` instance to the `queryClient` property. + +::: code-group +```tsx [main.ts] +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query' // [!code focus] +import { WagmiPlugin } from '@wagmi/vue' +import { createApp } from 'vue' +import { config } from './config' +import App from './App.vue' + +const queryClient = new QueryClient() // [!code focus] + +createApp(App) + .use(WagmiPlugin, { config }) + .use(VueQueryPlugin, { queryClient }) // [!code focus] + .mount('#app') +``` +```vue [App.vue] + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + +Check out the [TanStack Query docs](https://tanstack.com/query/latest/docs/framework/vue) to learn about the library, APIs, and more. + +### Use Wagmi + +Now that everything is set up, every component inside your app can use Wagmi Vue Composables. + +::: code-group +```vue [App.vue] + + + +``` +```tsx [main.ts] +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query' +import { WagmiPlugin } from '@wagmi/vue' +import { createApp } from 'vue' +import { config } from './config' +import App from './App.vue' + +const queryClient = new QueryClient() + +createApp(App) + .use(WagmiPlugin, { config }) + .use(VueQueryPlugin, { queryClient }) + .mount('#app') +``` +<<< @/snippets/vue/config.ts[config.ts] +::: + + +## Next Steps + +For more information on what to do next, check out the following topics. + +- [**TypeScript**](/vue/typescript) Learn how to get the most out of Wagmi's type-safety and inference for an enlightened developer experience. +- [**Connect Wallet**](/vue/guides/connect-wallet) Learn how to enable wallets to connect to and disconnect from your apps and display information about connected accounts. +- [**Vue Composables**](/vue/api/composables) Browse the collection of Vue Composables and learn how to use them. +- [**Viem**](/vue/guides/viem) Learn about Viem and how it works with Wagmi. + diff --git a/site/vue/guides/chain-properties.md b/site/vue/guides/chain-properties.md new file mode 100644 index 0000000000..0f41ef41c9 --- /dev/null +++ b/site/vue/guides/chain-properties.md @@ -0,0 +1,97 @@ +# Chain Properties + +Some chains support additional properties related to blocks and transactions. This is powered by Viem's [formatters](https://viem.sh/docs/chains/formatters) and [serializers](https://viem.sh/docs/chains/serializers). For example, Celo, ZkSync, OP Stack chains support all support additional properties. In order to use these properties in a type-safe way, there are a few things you should be aware of. + +
+ +::: tip +Make sure you follow the TypeScript guide's [Config Types](/vue/typescript#config-types) section before moving on. The easiest way to do this is to use [Declaration Merging](/vue/typescript#declaration-merging) to "register" your `config` globally with TypeScript. + +<<< @/snippets/vue/config-chain-properties.ts[config.ts] +::: + +## Narrowing Parameters + +Once your Config is registered with TypeScript, you are ready to access chain-specific properties! For example, Celo's `feeCurrency` is available. + +::: code-group +```ts [index.tsx] +import { parseEther } from 'viem' +import { useSimulateContract } from '@wagmi/vue' + +const result = useSimulateContract({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + feeCurrency: '0x…', // [!code focus] +}) +``` +<<< @/snippets/vue/config-chain-properties.ts[config.ts] +::: + +This is great, but if you have multiple chains that support additional properties, your autocomplete could be overwhelmed with all of them. By setting the `chainId` property to a specific value (e.g. `celo.id`), you can narrow parameters to a single chain. + +::: code-group +```ts [index.tsx] +import { parseEther } from 'viem' +import { useSimulateContract } from '@wagmi/vue' +import { celo } from '@wagmi/vue/chains' + +const result = useSimulateContract({ + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('0.01'), + chainId: celo.id, // [!code focus] + feeCurrency: '0x…', // [!code focus] + // ^? (property) feeCurrency?: `0x${string}` | undefined // [!code focus] +}) +``` +<<< @/snippets/vue/config-chain-properties.ts[config.ts] +::: + +## Narrowing Return Types + +Return types can also have chain-specific properties attached to them. There are a couple approaches for extracting these properties. + +### `chainId` Parameter + +Not only can you use the `chainId` parameter to [narrow parameters](#narrowing-parameters), you can also use it to narrow the return type. + +::: code-group +```ts [index.tsx] +import { useWaitForTransactionReceipt } from '@wagmi/vue' +import { zkSync } from '@wagmi/vue/chains' + +const { data } = useWaitForTransactionReceipt({ + chainId: zkSync.id, + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +data?.logs +// ^? (property) logs: ZkSyncLog[] | undefined +``` +<<< @/snippets/vue/config-chain-properties.ts[config.ts] +::: + +### `chainId` Data Property + +Wagmi internally will set a `chainId` property on return types that you can use to narrow results. The `chainId` is determined from the `chainId` parameter or global state (e.g. connector). You can use this property to help TypeScript narrow the type. + +::: code-group +```ts [index.tsx] +import { useWaitForTransactionReceipt } from '@wagmi/vue' +import { zkSync } from '@wagmi/vue/chains' + +const { data } = useWaitForTransactionReceipt({ + hash: '0x16854fcdd0219cacf5aec5e4eb2154dac9e406578a1510a6fc48bd0b67e69ea9', +}) + +if (data?.chainId === zkSync.id) { + data?.logs + // ^? (property) logs: ZkSyncLog[] | undefined +} +``` +<<< @/snippets/vue/config-chain-properties.ts[config.ts] +::: + +## Troubleshooting + +If chain properties aren't working, make sure [TypeScript](/vue/guides/faq#type-inference-doesn-t-work) is configured correctly. Not all chains have additional properties, to check which ones do, see the [Viem repo](https://github.com/wevm/viem/tree/main/src/chains) (chains that have a top-level directory under [`src/chains`](https://github.com/wevm/viem/tree/main/src/chains) support additional properties). diff --git a/site/vue/guides/connect-wallet.md b/site/vue/guides/connect-wallet.md new file mode 100644 index 0000000000..89e795ac72 --- /dev/null +++ b/site/vue/guides/connect-wallet.md @@ -0,0 +1,387 @@ +# Connect Wallet + +The ability for a user to connect their wallet is a core function for any Dapp. It allows users to perform tasks such as: writing to contracts, signing messages, or sending transactions. + +Wagmi contains everything you need to get started with building a Connect Wallet module. To get started, you can either use a [third-party library](#third-party-libraries) or [build your own](#build-your-own). + +## Third-party Libraries + +You can use a pre-built Connect Wallet module from a third-party library such as: + +- [AppKit](https://walletconnect.com/appkit) - [Guide](https://docs.walletconnect.com/appkit/vue/core/installation) + +The above libraries are all built on top of Wagmi, handle all the edge cases around wallet connection, and provide a seamless Connect Wallet UX that you can use in your Dapp. + +## Build Your Own + +Wagmi provides you with the Composables to get started building your own Connect Wallet module. + +It takes less than five minutes to get up and running with Browser Wallets, WalletConnect, and Coinbase Wallet. + +### 1. Configure Wagmi + +Before we get started with building the functionality of the Connect Wallet module, we will need to set up the Wagmi configuration. + +Let's create a `config.ts` file and export a `config` object. + +::: code-group + +```tsx [config.ts] +import { http, createConfig } from '@wagmi/vue' +import { base, mainnet, optimism } from '@wagmi/vue/chains' +import { injected, metaMask, safe, walletConnect } from '@wagmi/vue/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +In the above configuration, we want to set up connectors for Injected (browser), WalletConnect (browser + mobile), MetaMask, and Safe wallets. This configuration uses the **Mainnet** and **Base** chains, but you can use whatever you want. + +::: warning + +Make sure to replace the `projectId` with your own WalletConnect Project ID, if you wish to use WalletConnect! + +[Get your Project ID](https://cloud.walletconnect.com/) + +::: + +### 2. Inject the WagmiPlugin onto your App + +Next, we will need to inject our App with plugins so that our application is aware of Wagmi & Vue Query's reactive state and in-memory caching. + +::: code-group + +```ts [main.ts] +// 1. Import modules. +import { VueQueryPlugin } from '@tanstack/vue-query'; +import { WagmiPlugin } from '@wagmi/vue'; +import { createApp } from 'vue'; + +import App from './App.vue'; +import { config } from './wagmi'; + +createApp(App) + // 2. Inject the Wagmi plugin. + .use(WagmiPlugin, { config }) + // 3. Inject the Vue Query plugin. + .use(VueQueryPlugin, {}) + .mount('#app'); +``` + +```vue [App.vue] + + + +``` + +```ts [config.ts] +import { http, createConfig } from '@wagmi/vue' +import { base, mainnet, optimism } from '@wagmi/vue/chains' +import { injected, metaMask, safe, walletConnect } from '@wagmi/vue/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 3. Display Wallet Options + +After that, we will create a `Connect` component that will display our connectors. This will allow users to select a wallet and connect. + +Below, we are rendering a list of `connectors` retrieved from `useConnect`. When the user clicks on a connector, the `connect` function will connect the users' wallet. + +::: code-group + +```vue [Connect.vue] + + + +``` + +```vue [App.vue] + + + +``` + +```ts [main.ts] +// 1. Import modules. +import { VueQueryPlugin } from '@tanstack/vue-query'; +import { WagmiPlugin } from '@wagmi/vue'; +import { createApp } from 'vue'; + +import App from './App.vue'; +import { config } from './wagmi'; + +createApp(App) + // 2. Inject the Wagmi plugin. + .use(WagmiPlugin, { config }) + // 3. Inject the Vue Query plugin. + .use(VueQueryPlugin, {}) + .mount('#app'); +``` + +```ts [config.ts] +import { http, createConfig } from '@wagmi/vue' +import { base, mainnet, optimism } from '@wagmi/vue/chains' +import { injected, metaMask, safe, walletConnect } from '@wagmi/vue/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 4. Display Connected Account + +Lastly, if an account is connected, we want to show some basic information, like the connected address and ENS name and avatar. + +Below, we are using hooks like `useAccount`, `useEnsAvatar` and `useEnsName` to extract this information. + +We are also utilizing `useDisconnect` to show a "Disconnect" button so a user can disconnect their wallet. + +::: code-group + +```vue [Account.vue] + + + +``` + +```vue [Connect.vue] + + + +``` + +```vue [App.vue] + + + +``` + +```ts [main.ts] +// 1. Import modules. +import { VueQueryPlugin } from '@tanstack/vue-query'; +import { WagmiPlugin } from '@wagmi/vue'; +import { createApp } from 'vue'; + +import App from './App.vue'; +import { config } from './wagmi'; + +createApp(App) + // 2. Inject the Wagmi plugin. + .use(WagmiPlugin, { config }) + // 3. Inject the Vue Query plugin. + .use(VueQueryPlugin, {}) + .mount('#app'); +``` + +```ts [config.ts] +import { http, createConfig } from '@wagmi/vue' +import { base, mainnet, optimism } from '@wagmi/vue/chains' +import { injected, metaMask, safe, walletConnect } from '@wagmi/vue/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### 5. Wire it up! + +Finally, we can wire up our Connect and Account components to our application's entrypoint. + +::: code-group + +```vue [App.vue] + + + + +``` + +```vue [Account.vue] + + + +``` + +```vue [Connect.vue] + + + +``` + +```ts [main.ts] +// 1. Import modules. +import { VueQueryPlugin } from '@tanstack/vue-query'; +import { WagmiPlugin } from '@wagmi/vue'; +import { createApp } from 'vue'; + +import App from './App.vue'; +import { config } from './wagmi'; + +createApp(App) + // 2. Inject the Wagmi plugin. + .use(WagmiPlugin, { config }) + // 3. Inject the Vue Query plugin. + .use(VueQueryPlugin, {}) + .mount('#app'); +``` + +```ts [config.ts] +import { http, createConfig } from '@wagmi/vue' +import { base, mainnet, optimism } from '@wagmi/vue/chains' +import { injected, metaMask, safe, walletConnect } from '@wagmi/vue/connectors' + +const projectId = '' + +export const config = createConfig({ + chains: [mainnet, base], + connectors: [ + injected(), + walletConnect({ projectId }), + metaMask(), + safe(), + ], + transports: { + [mainnet.id]: http(), + [base.id]: http(), + }, +}) +``` + +::: + +### Playground + +Want to see the above steps all wired up together in an end-to-end example? Check out the below StackBlitz playground. + +
+ + diff --git a/site/vue/guides/error-handling.md b/site/vue/guides/error-handling.md new file mode 100644 index 0000000000..8cb7e9b507 --- /dev/null +++ b/site/vue/guides/error-handling.md @@ -0,0 +1,39 @@ +# Error Handling + +The `error` property in Wagmi Composables is strongly typed with it's corresponding error type. This enables you to have granular precision with handling errors in your application. + +You can discriminate the error type by using the `name` property on the error object. + +::: code-group +```vue twoslash [index.vue] + + + +``` +<<< @/snippets/vue/config.ts[config.ts] +::: diff --git a/site/vue/guides/faq.md b/site/vue/guides/faq.md new file mode 100644 index 0000000000..61def1c79d --- /dev/null +++ b/site/vue/guides/faq.md @@ -0,0 +1,9 @@ + + +# FAQ / Troubleshooting + +Collection of frequently asked questions with ideas on how to troubleshoot and resolve them. + + diff --git a/site/vue/guides/read-from-contract.md b/site/vue/guides/read-from-contract.md new file mode 100644 index 0000000000..8bf78acc91 --- /dev/null +++ b/site/vue/guides/read-from-contract.md @@ -0,0 +1,206 @@ +# Read from Contract + +## Overview + +The [`useReadContract` Composable](/vue/api/composables/useReadContract) allows you to read data on a smart contract, from a `view` or `pure` (read-only) function. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas. + +The component below shows how to retrieve the token balance of an address from the [Wagmi Example](https://etherscan.io/token/0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2) contract + +:::code-group + +```vue [ReadContract.vue] + + + +``` +```ts [contracts.ts] +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ type: 'uint256' }], + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [{ name: 'supply', type: 'uint256' }], + }, + ], +} as const +``` +::: + + +If `useReadContract` depends on another value (`address` in the example below), you can use the [`query.enabled`](/vue/api/composables/useReadContract#enabled) option to prevent the query from running until the dependency is ready. + +```tsx +const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: [address], + query: { // [!code focus] + enabled: !!address, // [!code focus] + }, // [!code focus] +}) +``` + + +## Loading & Error States + +The [`useReadContract` Composable](/vue/api/composables/useReadContract) also returns loading & error states, which can be used to display a loading indicator while the data is being fetched, or an error message if contract execution reverts. + +:::code-group + +```vue [ReadContract.vue] + + + +``` + +::: + + + + diff --git a/site/vue/guides/send-transaction.md b/site/vue/guides/send-transaction.md new file mode 100644 index 0000000000..6686e720be --- /dev/null +++ b/site/vue/guides/send-transaction.md @@ -0,0 +1,311 @@ +# Send Transaction + +The following guide teaches you how to send transactions in Wagmi. The example below builds on the [Connect Wallet guide](/vue/guides/connect-wallet) and uses the [useSendTransaction](/vue/api/composables/useSendTransaction) & [useWaitForTransaction](/vue/api/composables/useWaitForTransactionReceipt) composables. + +## Example + +Feel free to check out the example before moving on: + + + +## Steps + +### 1. Connect Wallet + +Follow the [Connect Wallet guide](/vue/guides/connect-wallet) guide to get this set up. + +### 2. Create a new component + +Create your `SendTransaction` component that will contain the send transaction logic. + +::: code-group + +```tsx [SendTransaction.vue] + + + +``` + +::: + +### 3. Add a form handler + +Next, we will need to add a handler to the form that will send the transaction when the user hits "Send". This will be a basic handler in this step. + +::: code-group + +```vue [SendTransaction.vue] + + + +``` + +::: + +### 4. Hook up the `useSendTransaction` Composable + +Now that we have the form handler, we can hook up the [`useSendTransaction` Composable](/vue/api/composables/useSendTransaction) to send the transaction. + +::: code-group + +```vue [SendTransaction.vue] + + + +``` + +::: + +### 5. Add loading state (optional) + +We can optionally add a loading state to the "Send" button while we are waiting confirmation from the user's wallet. + +::: code-group + +```vue [SendTransaction.vue] + + + +``` + +::: + +### 6. Wait for transaction receipt (optional) + +We can also display the transaction confirmation status to the user by using the [`useWaitForTransactionReceipt` Composable](/vue/api/composables/useWaitForTransactionReceipt). + +::: code-group + +```vue [SendTransaction.vue] + + + +``` + +::: + +### 7. Handle errors (optional) + +If the user rejects the transaction, or the user does not have enough funds to cover the transaction, we can display an error message to the user. + +::: code-group + +```vue [SendTransaction.vue] + + + +``` + +::: + +### 8. Wire it up! + +Finally, we can wire up our Send Transaction component to our application's entrypoint. + +:::code-group +```vue [App.vue] + + + +``` +```vue [SendTransaction.vue] + + + +``` +::: + +[See the Example.](#example) diff --git a/site/vue/guides/ssr.md b/site/vue/guides/ssr.md new file mode 100644 index 0000000000..bf6fc4e2e9 --- /dev/null +++ b/site/vue/guides/ssr.md @@ -0,0 +1,77 @@ +--- +outline: deep +--- + +# SSR + +Wagmi uses client-only external stores (such as `localStorage` and `mipd`) to show the user the most relevant data as quickly as possible on first render. + +However, the caveat of using these external client stores is that frameworks which incorporate SSR (such as Next.js) will throw hydration warnings on the client when it identifies mismatches between the server-rendered HTML and the client-rendered HTML. + +To stop this from happening, you can toggle on the [`ssr`](/vue/api/createConfig#ssr) property in the Wagmi Config. + +```tsx +import { createConfig, http } from '@wagmi/vue' +import { mainnet, sepolia } from '@wagmi/vue/chains' + +const config = createConfig({ // [!code focus:99] + chains: [mainnet, sepolia], + ssr: true, // [!code ++] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +Turning on the `ssr` property means that content from the external stores will be hydrated on the client after the initial mount. + +## Persistence using Cookies + +As a result of turning on the `ssr` property, external persistent stores like `localStorage` will be hydrated on the client **after the initial mount**. + +This means that you will still see a flash of "empty" data on the client (e.g. a `"disconnected"` account instead of a `"reconnecting"` account, or an empty address instead of the last connected address) until after the first mount, when the store hydrates. + +In order to persist data between the server and the client, you can use cookies. + +### 1. Set up cookie storage + +First, we will set up cookie storage in the Wagmi Config. + +```tsx +import { + createConfig, + http, + cookieStorage, // [!code ++] + createStorage // [!code ++] +} from '@wagmi/vue' +import { mainnet, sepolia } from '@wagmi/vue/chains' + +export const config = createConfig({ + chains: [mainnet, sepolia], + ssr: true, + storage: createStorage({ // [!code ++] + storage: cookieStorage, // [!code ++] + }), // [!code ++] + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +### 2. Hydrate the cookie + +Next, we will need to add some mechanisms to hydrate the stored cookie in Wagmi. + +#### Nuxt.js + +Would you like to contribute this content? Feel free to [open a Pull Request](https://github.com/wevm/wagmi/pulls)! + + +#### Vanilla SSR + +Would you like to contribute this content? Feel free to [open a Pull Request](https://github.com/wevm/wagmi/pulls)! + + + diff --git a/site/vue/guides/tanstack-query.md b/site/vue/guides/tanstack-query.md new file mode 100644 index 0000000000..9d5efb4d7a --- /dev/null +++ b/site/vue/guides/tanstack-query.md @@ -0,0 +1,287 @@ +# TanStack Query + +Wagmi Composables are not only a wrapper around the core [Wagmi Actions](/core/api/actions), but they also utilize [TanStack Query](https://tanstack.com/query/v5) to enable trivial and intuitive fetching, caching, synchronizing, and updating of asynchronous data in your Vue applications. + +Without an asynchronous data fetching abstraction, you would need to handle all the negative side-effects that comes as a result, such as: representing finite states (loading, error, success), handling race conditions, caching against a deterministic identifier, etc. + +## Queries & Mutations + +Wagmi Composables represent either a **Query** or a **Mutation**. + +**Queries** are used for fetching data (e.g. fetching a block number, reading from a contract, etc), and are typically invoked on mount by default. All queries are coupled to a unique [Query Key](#query-keys), and can be used for further operations such as refetching, prefetching, or modifying the cached data. + +**Mutations** are used for mutating data (e.g. connecting/disconnecting accounts, writing to a contract, switching chains, etc), and are typically invoked in response to a user interaction. Unlike **Queries**, they are not coupled with a query key. + +## Terms + +- **Query**: An asynchronous data fetching (e.g. read data) operation that is tied against a unique Query Key. +- **Mutation**: An asynchronous mutating (e.g. create/update/delete data or side-effect) operation. +- **Query Key**: A unique identifier that is used to deterministically identify a query. It is typically a tuple of the query name and the query arguments. +- **Stale Data**: Data that is unused or inactive after a certain period of time. +- **Query Fetching**: The process of invoking an async query function. +- **Query Refetching**: The process of refetching **rendered** queries. +- **[Query Invalidation](https://tanstack.com/query/v5/docs/vue/guides/query-invalidation)**: The process of marking query data as stale (e.g. inactive/unused), and refetching **rendered** queries. +- **[Query Prefetching](https://tanstack.com/query/v5/docs/vue/guides/prefetching)**: The process of prefetching queries and seeding the cache. + + + +## Query Keys + +Query Keys are typically used to perform advanced operations on the query such as: invalidation, refetching, prefetching, etc. + +Wagmi exports Query Keys for every Composable, and they can be retrieved via the [Composable (Vue)](#composable-vue) or via an [Import (Vanilla JS)](#import-vanilla-js). + +Read more about **Query Keys** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/vue/guides/query-keys) + +### Composable (Vue) + +Each Composable returns a `queryKey` value. You would use this approach when you want to utilize the query key in a Vue component as it handles reactivity for you, unlike the [Import](#import-vanilla-js) method below. + +```vue [index.vue] + + + +``` + +### Import (Vanilla JS) + +Each Hook has a corresponding `getQueryOptions` function that returns a query key. You would use this method when you want to utilize the query key outside of a Vue component in a Vanilla JS context, like in a utility function. + +```ts +import { getBalanceQueryOptions } from '@wagmi/vue/query' // [!code hl] +import { config } from './config' + +function perform() { + const { queryKey } = getBalanceQueryOptions(config, { // [!code hl] + chainId: config.state.chainId // [!code hl] + }) // [!code hl] +} +``` + +::: warning + +The caveat of this method is that it does not handle reactivity for you (e.g. active account/chain changes, argument changes, etc). You would need to handle this yourself by explicitly passing through the arguments to `getQueryOptions`. + +::: + +## Invalidating Queries + +Invalidating a query is the process of marking the query data as stale (e.g. inactive/unused), and refetching the queries that are already rendered. + +Read more about **Invalidating Queries** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/vue/guides/query-invalidation) + +#### Example: Watching a Users' Balance + +You may want to "watch" a users' balance, and invalidate the balance after each incoming block. We can invoke `invalidateQueries` inside a `watchEffect` – this will refetch all rendered balance queries when the `blockNumber` changes. + +```vue + + + +``` + +#### Example: After User Interaction + +Maybe you want to invalidate a users' balance after some interaction. This would mark the balance as stale, and consequently refetch all rendered balance queries. + +```vue + + + +``` + +```vue + + + +``` + +## Fetching Queries + +Fetching a query is the process of invoking the query function to retrieve data. If the query exists and the data is not invalidated or older than a given `staleTime`, then the data from the cache will be returned. Otherwise, the query will fetch for the latest data. + +::: code-group +```tsx [example.tsx] +import { getBlockQueryOptions } from '@wagmi/vue/query' +import { queryClient } from './main' +import { config } from './config' + +export async function fetchBlockData() { + return queryClient.fetchQuery( // [!code hl] + getBlockQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + } // [!code hl] + )) // [!code hl] +} +``` +<<< @/snippets/vue/main.ts[main.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Retrieving & Updating Query Data + +You can retrieve and update query data imperatively with `getQueryData` and `setQueryData`. This is useful for scenarios where you want to retrieve or update a query outside of a Vue component. + +Note that these functions do not invalidate or refetch queries. + +::: code-group +```tsx [example.tsx] +import type { GetBalanceReturnType } from '@wagmi/vue/actions' +import { getBalanceQueryOptions } from '@wagmi/vue/query' +import { queryClient } from './app' +import { config } from './config' + +export function getBalanceData() { + return queryClient.getQueryData( // [!code hl] + getBalanceQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + } // [!code hl] + )) // [!code hl] +} + +export function setBalanceData(parameters: Partial) { + return queryClient.setQueryData( // [!code hl] + getBalanceQueryOptions(config, { // [!code hl] + chainId: config.state.chainId, // [!code hl] + }, // [!code hl] + data => ({ ...data, ...parameters }) // [!code hl] + )) // [!code hl] +} +``` +<<< @/snippets/vue/main.ts[main.ts] +<<< @/snippets/vue/config.ts[config.ts] +::: + +## Prefetching Queries + +Prefetching a query is the process of fetching the data ahead of time and seeding the cache with the returned data. This is useful for scenarios where you want to fetch data before the user navigates to a page, or fetching data on the server to be reused on client hydration. + +Read more about **Prefetching Queries** on the [TanStack Query docs.](https://tanstack.com/query/v5/docs/vue/guides/prefetching) + +#### Example: Prefetching in Event Handler + +```vue + + + +``` + +## SSR + +It is possible to utilize TanStack Query's SSR strategies with Wagmi Composables & Query Keys. Check out the [SSR guide](https://tanstack.com/query/latest/docs/framework/vue/guides/ssr). + +## Devtools + +TanStack Query includes dedicated [Devtools](https://tanstack.com/query/latest/docs/framework/vue/devtools) that assist in visualizing and debugging your queries, their cache states, and much more. You will have to pass a custom `queryKeyFn` to your `QueryClient` for Devtools to correctly serialize BigInt values for display. Alternatively, You can use the `hashFn` from `@wagmi/core/query`, which already handles this serialization. + +#### Install + +::: code-group +```bash [pnpm] +pnpm i @tanstack/vue-query-devtools +``` + +```bash [npm] +npm i @tanstack/vue-query-devtools +``` + +```bash [yarn] +yarn add @tanstack/vue-query-devtools +``` + +```bash [bun] +bun i @tanstack/vue-query-devtools +``` +::: + +#### Usage + +::: code-group +```vue [App.vue] + + + +``` + +```vue [main.vue] + +``` +::: \ No newline at end of file diff --git a/site/vue/guides/viem.md b/site/vue/guides/viem.md new file mode 100644 index 0000000000..a4b7da32d0 --- /dev/null +++ b/site/vue/guides/viem.md @@ -0,0 +1,94 @@ +# Viem + +[Viem](https://viem.sh) is a low-level TypeScript Interface for Ethereum that enables developers to interact with the Ethereum blockchain, including: JSON-RPC API abstractions, Smart Contract interaction, wallet & signing implementations, coding/parsing utilities and more. + +**Wagmi Core** is essentially a wrapper over **Viem** that provides multi-chain functionality via [Wagmi Config](/react/api/createConfig) and automatic account management via [Connectors](/react/api/connectors). + +## Leveraging Viem Actions + +All of the core [Wagmi Composables](/vue/api/composables) are friendly wrappers around [Viem Actions](https://viem.sh/docs/actions/public/introduction.html) that inject a multi-chain and connector aware [Wagmi Config](/vue/api/createConfig). + +There may be cases where you might want to dig deeper and utilize Viem Actions directly (maybe a Composable doesn't exist in Wagmi yet). In these cases, you can create your own custom Wagmi Composable by importing Viem Actions directly via `viem/actions` and plugging in a Viem Client returned by the [`useClient` Composable](/vue/api/composables/useClient). + +There are two categories of Viem Actions: + +- **[Public Actions](https://viem.sh/docs/actions/public/introduction):** Actions that are "read-only" and do not require a wallet connection. +- **[Wallet Actions](https://viem.sh/docs/actions/wallet/introduction):** Actions that interface with a Wallet and require a wallet connection. + +While it is not mandatory, it is also recommended to pair Actions with either `useQuery` or `useMutation` to effectively leverage the reactivity and caching capabilities of [Tanstack Query](/vue/guides/tanstack-query). + +### Public Actions + +The example below demonstrates how to utilize Viem's `getLogs` Action with a `useQuery` Composable to create your own abstraction akin to a `useLogs` Composable. + +```vue + +``` + +### Wallet Actions + +The example below demonstrates how to utilize Viem's `watchAsset` Action with a `useMutation` Composable to create your own abstraction akin to a `useWatchAsset` Composable. + +```vue + +``` + +## Private Key & Mnemonic Accounts + +It is possible to utilize Viem's [Private Key & Mnemonic Accounts](https://viem.sh/docs/accounts/local.html) with Wagmi by explicitly passing through the account via the `account` argument on Wagmi Actions. + +```vue + +``` + +::: info + +Wagmi currently does not support hoisting Private Key & Mnemonic Accounts to the top-level Wagmi Config – meaning you have to explicitly pass through the account to every Action. If you feel like this is a feature that should be added, please [open an discussion](https://github.com/wevm/wagmi/discussions/new?category=ideas). + +::: diff --git a/site/vue/guides/write-to-contract.md b/site/vue/guides/write-to-contract.md new file mode 100644 index 0000000000..9bdc788428 --- /dev/null +++ b/site/vue/guides/write-to-contract.md @@ -0,0 +1,387 @@ +# Write to Contract + +The [`useWriteContract` Composable](/vue/api/composables/useWriteContract) allows you to mutate data on a smart contract, from a `payable` or `nonpayable` (write) function. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state. + +In the guide below, we will teach you how to implement a "Mint NFT" form that takes in a dynamic argument (token ID) using Wagmi. The example below builds on the [Connect Wallet guide](/vue/guides/connect-wallet) and uses the [useWriteContract](/vue/api/composables/useWriteContract) & [useWaitForTransaction](/vue/api/composables/useWaitForTransactionReceipt) composables. + +If you have already completed the [Sending Transactions guide](/vue/guides/send-transaction), this guide will look very similar! That's because writing to a contract internally broadcasts & sends a transaction. + +## Example + +Feel free to check out the example before moving on: + + + +## Steps + +### 1. Connect Wallet + +Follow the [Connect Wallet guide](/vue/guides/connect-wallet) guide to get this set up. + +### 2. Create a new component + +Create your `MintNft` component that will contain the Mint NFT logic. + +::: code-group + +```vue [MintNft.vue] + + + +``` + +::: + +### 3. Add a form handler + +Next, we will need to add a handler to the form that will send the transaction when the user hits "Mint". This will be a basic handler in this step. + +::: code-group + +```vue [MintNft.vue] + + + +``` + +::: + +### 4. Hook up the `useWriteContract` Composable + +Now that we have the form handler, we can hook up the [`useWriteContract` Composable](/vue/api/composables/useWriteContract) to send the transaction. + +::: code-group + +```vue [MintNft.vue] + + + +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 5. Add loading state (optional) + +We can optionally add a loading state to the "Mint" button while we are waiting confirmation from the user's wallet. + +::: code-group + +```vue [MintNft.vue] + + + +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 6. Wait for transaction receipt (optional) + +We can also display the transaction confirmation status to the user by using the [`useWaitForTransactionReceipt` Composable](/vue/api/composables/useWaitForTransactionReceipt). + +::: code-group + +```vue [MintNft.vue] + + + +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 7. Handle errors (optional) + +If the user rejects the transaction, or the user does not have enough funds to cover the transaction, we can display an error message to the user. + +::: code-group + +```vue [MintNft.vue] + + + +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` + +::: + +### 8. Wire it up! + +Finally, we can wire up our Send Transaction component to our application's entrypoint. + +:::code-group +```vue [App.vue] + + + +``` + +```vue [MintNft.vue] + + + +``` + +```ts [abi.ts] +export const abi = [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }], + outputs: [], + }, +] as const +``` +::: + +[See the Example.](#example) \ No newline at end of file diff --git a/site/vue/installation.md b/site/vue/installation.md new file mode 100644 index 0000000000..82676c43fa --- /dev/null +++ b/site/vue/installation.md @@ -0,0 +1,41 @@ + + +# Installation + +Install Wagmi via your package manager, a ` + +# TypeScript + +## Requirements + +Wagmi is designed to be as type-safe as possible! Things to keep in mind: + +- Types currently require using TypeScript {{typescriptVersion}}. +- [TypeScript doesn't follow semver](https://www.learningtypescript.com/articles/why-typescript-doesnt-follow-strict-semantic-versioning) and often introduces breaking changes in minor releases. +- Changes to types in this repository are considered non-breaking and are usually released as patch changes (otherwise every type enhancement would be a major version!). +- It is highly recommended that you lock your `wagmi` and `typescript` versions to specific patch releases and upgrade with the expectation that types may be fixed or upgraded between any release. +- The non-type-related public API of Wagmi still follows semver very strictly. + +To ensure everything works correctly, make sure your `tsconfig.json` has [`strict`](https://www.typescriptlang.org/tsconfig#strict) mode set to `true`. + +::: code-group +```json [tsconfig.json] +{ + "compilerOptions": { + "strict": true + } +} +``` +::: + +## Config Types + +By default Vue Plugins does not work well with type inference. To support strong type-safety across the Vue Plugins boundary, there are two options available: + +- Declaration merging to "register" your `config` globally with TypeScript. +- `config` property to pass your `config` directly to composables. + +### Declaration Merging + +[Declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) allows you to "register" your `config` globally with TypeScript. The `Register` type enables Wagmi to infer types in places that wouldn't normally have access to type info via a Vue Plugin alone. + +To set this up, add the following declaration to your project. Below, we co-locate the declaration merging and the `config` set up. + +```ts +import { createConfig, http } from '@wagmi/vue' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module '@wagmi/vue' { // [!code focus] + interface Register { // [!code focus] + config: typeof config // [!code focus] + } // [!code focus] +} // [!code focus] + +export const config = createConfig({ + chains: [mainnet, sepolia], + transports: { + [mainnet.id]: http(), + [sepolia.id]: http(), + }, +}) +``` + +Since the `Register` type is global, you only need to add it once in your project. Once set up, you will get strong type-safety across your entire project. For example, query composables will type `chainId` based on your `config`'s `chains`. + +```ts twoslash +// @errors: 2322 +import { type Config } from '@wagmi/vue' +import { mainnet, sepolia } from 'wagmi/chains' + +declare module '@wagmi/vue' { + interface Register { + config: Config + } +} +// ---cut--- +import { useBlockNumber } from '@wagmi/vue' + +useBlockNumber({ chainId: 123 }) +``` + +You just saved yourself a runtime error and you didn't even need to pass your `config`. 🎉 + +### Hook `config` Property + +For cases where you have more than one Wagmi `config` or don't want to use the declaration merging approach, you can pass a specific `config` directly to composables via the `config` property. + +```ts +import { createConfig, http } from '@wagmi/vue' +import { mainnet, optimism } from '@wagmi/vue/chains' + +export const configA = createConfig({ // [!code focus] + chains: [mainnet], // [!code focus] + transports: { // [!code focus] + [mainnet.id]: http(), // [!code focus] + }, // [!code focus] +}) // [!code focus] + +export const configB = createConfig({ // [!code focus] + chains: [optimism], // [!code focus] + transports: { // [!code focus] + [optimism.id]: http(), // [!code focus] + }, // [!code focus] +}) // [!code focus] +``` + +As you expect, `chainId` is inferred correctly for each `config`. + +```ts twoslash +// @errors: 2322 +import { type Config } from '@wagmi/vue' +import { mainnet, optimism } from '@wagmi/vue/chains' + +declare const configA: Config +declare const configB: Config +// ---cut--- +import { useBlockNumber } from '@wagmi/vue' + +useBlockNumber({ chainId: 123, config: configA }) +useBlockNumber({ chainId: 123, config: configB }) +``` + +This approach is more explicit, but works well for advanced use-cases, if you don't want to use a Vue Plugin or declaration merging, etc. + +## Const-Assert ABIs & Typed Data + +Wagmi can infer types based on [ABIs](https://docs.soliditylang.org/en/latest/abi-spec.html#json) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) Typed Data definitions, powered by [Viem](https://viem.sh) and [ABIType](https://github.com/wevm/abitype). This achieves full end-to-end type-safety from your contracts to your frontend and enlightened developer experience by autocompleting ABI item names, catching misspellings, inferring argument and return types (including overloads), and more. + +For this to work, you must either [const-assert](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) ABIs and Typed Data (more info below) or define them inline. For example, `useReadContract`'s `abi` configuration parameter: + +```ts +const { data } = useReadContract({ + abi: […], // <--- defined inline // [!code focus] +}) +``` + +```ts +const abi = […] as const // <--- const assertion // [!code focus] +const { data } = useReadContract({ abi }) +``` + +If type inference isn't working, it's likely you forgot to add a `const` assertion or define the configuration parameter inline. Also, make sure your ABIs, Typed Data definitions, and [TypeScript configuration](#requirements) are valid and set up correctly. + +::: tip +Unfortunately [TypeScript doesn't support importing JSON `as const` yet](https://github.com/microsoft/TypeScript/issues/32063). Check out the [Wagmi CLI](/cli/getting-started) to help with this! It can automatically fetch ABIs from Etherscan and other block explorers, resolve ABIs from your Foundry/Hardhat projects, generate Vue Composables, and more. +::: + +Anywhere you see the `abi` or `types` configuration property, you can likely use const-asserted or inline ABIs and Typed Data to get type-safety and inference. These properties are also called out in the docs. + +Here's what [`useReadContract`](/vue/api/composables/useReadContract) looks like with and without a const-asserted `abi` property. + +::: code-group +```ts twoslash [Const-Asserted] +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { useReadContract } from '@wagmi/vue' + +const { data } = useReadContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +data +// ^? +``` +```ts twoslash [Not Const-Asserted] +declare const erc721Abi: { + name: string; + type: string; + stateMutability: string; + inputs: { + type: string; + name: string; + }[]; + outputs: { + type: string; + }[]; +}[] +// ---cut--- +import { useReadContract } from '@wagmi/vue' + +const { data } = useReadContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: erc721Abi, + functionName: 'balanceOf', + // ^? + + + + args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], + // ^? +}) + +data +// ^? +``` +::: + +
+
+ +You can prevent runtime errors and be more productive by making sure your ABIs and Typed Data definitions are set up appropriately. 🎉 + +```ts twoslash +// @errors: 2820 +const erc721Abi = [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'address', name: 'owner' }], + outputs: [{ type: 'uint256' }], + }, + { + name: 'isApprovedForAll', + type: 'function', + stateMutability: 'view', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'operator' }, + ], + outputs: [{ type: 'bool' }], + }, + { + name: 'getApproved', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'ownerOf', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'address' }], + }, + { + name: 'tokenURI', + type: 'function', + stateMutability: 'pure', + inputs: [{ type: 'uint256', name: 'tokenId' }], + outputs: [{ type: 'string' }], + }, +] as const +// ---cut--- +import { useReadContract } from '@wagmi/vue' + +useReadContract({ + abi: erc721Abi, + functionName: 'balanecOf', +}) +``` + +## Configure Internal Types + +For advanced use-cases, you may want to configure Wagmi's internal types. Most of Wagmi's types relating to ABIs and EIP-712 Typed Data are powered by [ABIType](https://github.com/wevm/abitype). See the [ABIType docs](https://abitype.dev) for more info on how to configure types. diff --git a/site/vue/why.md b/site/vue/why.md new file mode 100644 index 0000000000..4a3560c13f --- /dev/null +++ b/site/vue/why.md @@ -0,0 +1,46 @@ +# Why Wagmi + +## The Problems + +Building Ethereum applications is hard. Apps need to support connecting wallets, multiple chains, signing messages and data, sending transactions, listening for events and state changes, refreshing stale blockchain data, and much more. This is all on top of solving for app-specific use-cases and providing polished user experiences. + +The ecosystem is also continuously evolving, meaning you need to adapt to new improvements or get left behind. App developers should not need to worry about connecting tens of different wallets, the intricacies of multi-chain support, typos accidentally sending an order of magnitude more ETH or calling a misspelled contract function, or accidentally spamming their RPC provider, costing thousands in compute units. + +Wagmi solves all these problems and more — allowing app developers to focus on building high-quality and performant experiences for Ethereum — by focusing on **developer experience**, **performance**, **feature coverage**, and **stability.** + +## Developer Experience + +Wagmi delivers a great developer experience through modular and composable APIs, automatic type safety and inference, and comprehensive documentation. + +It provides developers with intuitive building blocks to build their Ethereum apps. While Wagmi's APIs might seem more verbose at first, it makes Wagmi's modular building blocks extremely flexible. Easy to move around, change, and remove. It also allows developers to better understand Ethereum concepts as well as understand _what_ and _why_ certain properties are being passed through. Learning how to use Wagmi is a great way to learn how to interact with Ethereum in general. + +Wagmi also provides [strongly typed APIs](/vue/typescript), allowing consumers to get the best possible experience through [autocomplete](https://twitter.com/awkweb/status/1555678944770367493), [type inference](https://twitter.com/jakemoxey/status/1570244174502588417?s=20), as well as static validation. You often just need to provide an ABI and Wagmi can help you autocomplete your way to success, identify type errors before your users do, drill into blockchain errors [at compile and runtimes](/vue/guides/error-handling) with surgical precision, and much more. + +The API documentation is comprehensive and contains usage info for _every_ module in Wagmi. The core team uses a [documentation](https://gist.github.com/zsup/9434452) and [test driven](https://en.wikipedia.org/wiki/Test-driven_development#:~:text=Test%2Ddriven%20development%20(TDD),software%20against%20all%20test%20cases.) development approach to building modules, which leads to predictable and stable APIs. + +## Performance + +Performance is critical for applications on all sizes. Slow page load and interactions can cause users to stop using applications. Wagmi uses and is built by the same team behind [Viem](https://viem.sh), the most performant production-ready Ethereum library. + +End users should not be required to download a module of over 100kB in order to interact with Ethereum. Wagmi is optimized for tree-shaking and dead-code elimination, allowing apps to minimize bundle size for fast page load times. + +Data layer performance is also critical. Slow, unnecessary, and manual data fetching can make apps unusable and cost thousands in RPC compute units. Wagmi supports caching, deduplication, persistence, and much more through [TanStack Query](/vue/guides/tanstack-query). + +## Feature Coverage + +Wagmi supports the most popular and commonly-used Ethereum features out of the box with 40+ Vue Composables for accounts, wallets, contracts, transactions, signing, ENS, and more. Wagmi also supports just about any wallet out there through it's official [connectors](/vue/api/connectors), [EIP-6963 support](/vue/api/createConfig#multiinjectedproviderdiscovery), and [extensible API](/dev/creating-connectors). + +If you need lower-level control, you can always drop down to [Wagmi Core](/core/getting-started) or [Viem](https://viem.sh), which Wagmi uses internally to perform blockchain operations. Wagmi also manages multi-chain support automatically so developers can focus on their applications instead of adding custom code. + +Finally, Wagmi has a [CLI](/cli/getting-started) to manage ABIs as well as a robust ecosystem of third-party libraries, like [ConnectKit](https://docs.family.co/connectkit), [RainbowKit](https://www.rainbowkit.com), [AppKit](https://walletconnect.com/appkit), [Dynamic](https://www.dynamic.xyz), [Privy](https://privy.io), and many more, so you can get started quickly without needing to build everything from scratch. + +## Stability + +Stability is a fundamental principle for Wagmi. Many organizations, large and small, rely heavily on Wagmi and expect it to be entirely stable for their users and applications. + +Wagmi's test suite runs against forked Ethereum nodes to make sure functions work across chains. The test suite also runs type tests against many different versions of peer dependencies, like TypeScript, to ensure compatibility with the latest releases of other popular software. + +Wagmi follows semver so developers can upgrade between versions with confidence. Starting with Wagmi v2, new functionality will be opt-in with old functionality being deprecated alongside the new features. This means upgrading to the latest major versions will not require immediate changes. + +Lastly, the core team works full-time on Wagmi and [related projects](https://github.com/wevm), and is constantly improving Wagmi and keeping it up-to-date with industry trends and changes. + diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000000..9f245fc45f --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,1362 @@ +import { AnimatePresence } from 'framer-motion' +import React, { useState, useEffect, useMemo, SetStateAction } from 'react' +import { ethers } from 'ethers' +import { sequence } from '0xsequence' +import { walletContracts } from '@0xsequence/abi' +import { + Box, + Image, + Text, + Button, + ExternalLinkIcon, + Divider, + Card, + TransactionIcon, + Select, + TokenImage, + TextInput, + Modal +} from '@0xsequence/design-system' +import { ETHAuth } from '@0xsequence/ethauth' +import { configureLogger } from '@0xsequence/utils' +import { ConnectOptions, OpenWalletIntent, Settings } from '@0xsequence/provider' +import { ChainId, NetworkType } from '@0xsequence/network' + +import { ERC_20_ABI } from './constants/abi' +import { Console } from './components/Console' +import { Group } from './components/Group' +import { getDefaultChainId, toHexString } from './helpers' +import logoUrl from './images/logo.svg' +import skyweaverBannerUrl from './images/skyweaver-banner.png' +import skyweaverBannerLargeUrl from './images/skyweaver-banner-large.png' + +configureLogger({ logLevel: 'DEBUG' }) + +interface Environment { + name: string + walletUrl: string + projectAccessKey: string +} + +const environments: Environment[] = [ + { + name: 'production', + walletUrl: 'https://sequence.app', + projectAccessKey: 'AQAAAAAAAAbvrgpWEC2Aefg5qYStQmwjBpA' + }, + { + name: 'development', + walletUrl: 'https://dev.sequence.app', + //projectAccessKey: 'AQAAAAAAAAVBNfoB30kz7Ph4I_Qs5mkYuDc', + projectAccessKey: 'AQAAAAAAAAVCXiQ9f_57R44MjorZ4SmGdhA' + }, + { + name: 'local', + walletUrl: 'http://localhost:3333', + projectAccessKey: 'AQAAAAAAAAVCXiQ9f_57R44MjorZ4SmGdhA' + }, + { + name: 'custom', + walletUrl: '', + projectAccessKey: '' + } +] + +const DEFAULT_API_URL = 'https://api.sequence.app' + +// Specify your desired default chain id. NOTE: you can communicate to multiple +// chains at the same time without even having to switch the network, but a default +// chain is required. +const defaultChainId = getDefaultChainId() || ChainId.MAINNET +// const defaultChainId = ChainId.POLYGON +// const defaultChainId = ChainId.GOERLI +// const defaultChainId = ChainId.ARBITRUM +// const defaultChainId = ChainId.AVALANCHE +// etc.. see the full list here: https://docs.sequence.xyz/multi-chain-support + +// For Sequence core dev team -- app developers can ignore +// a custom wallet app url can specified in the query string +const urlParams = new URLSearchParams(window.location.search) + +const env = urlParams.get('env') ?? 'production' +const envConfig = environments.find(x => x.name === env) ?? environments.find(x => x.name === 'production') +if (!envConfig) { + throw new Error('Invalid environment configuration.') +} +const walletAppURL = urlParams.get('walletAppURL') ?? envConfig.walletUrl +const projectAccessKey = urlParams.get('projectAccessKey') ?? envConfig.projectAccessKey +const showProhibitedActions = urlParams.has('showProhibitedActions') + +const isCustom = walletAppURL !== envConfig.walletUrl || projectAccessKey !== envConfig.projectAccessKey + +if (walletAppURL && walletAppURL.length > 0) { + // Wallet can point to a custom wallet app url + // NOTICE: this is not needed, unless testing an alpha version of the wallet + sequence.initWallet(projectAccessKey, { defaultNetwork: defaultChainId, transports: { walletAppURL } }) +} else { + // Init the sequence wallet library at the top-level of your project with + // your designed default chain id + sequence.initWallet(projectAccessKey, { defaultNetwork: defaultChainId, transports: { walletAppURL } }) +} + +// App component +const App = () => { + const [consoleMsg, setConsoleMsg] = useState(null) + const [email, setEmail] = useState(null) + const [consoleLoading, setConsoleLoading] = useState(false) + const [isWalletConnected, setIsWalletConnected] = useState(false) + + const wallet = sequence.getWallet().getProvider() + + const [showChainId, setShowChainId] = useState(wallet.getChainId()) + const [isOpen, toggleModal] = useState(false) + const [warning, setWarning] = useState(false) + + useEffect(() => { + const handleChainChanged = (chainId: string) => { + setShowChainId(Number(BigInt(chainId))) + } + wallet.on('chainChanged', handleChainChanged) + return () => { + wallet.off('chainChanged', handleChainChanged) + } + }, [wallet]) + + useEffect(() => { + setIsWalletConnected(wallet.isConnected()) + }, [wallet]) + + useEffect(() => { + consoleWelcomeMessage() + // eslint-disable-next-line + }, [isWalletConnected]) + + useEffect(() => { + // Wallet events + const onOpen = () => { + console.log('wallet window opened') + } + wallet.client.on('open', onOpen) + + const onClose = () => { + console.log('wallet window closed') + } + wallet.client.on('close', onClose) + + return () => { + wallet.client.off('open', onOpen) + wallet.client.off('close', onClose) + } + }, [wallet]) + + const defaultConnectOptions: ConnectOptions = { + app: 'Demo Dapp', + askForEmail: true + // keepWalletOpened: true, + } + + // Methods + const connect = async (connectOptions: ConnectOptions = { app: 'Demo dapp' }) => { + if (isWalletConnected) { + resetConsole() + appendConsoleLine('Wallet already connected!') + setConsoleLoading(false) + return + } + + connectOptions = { + ...defaultConnectOptions, + ...connectOptions, + settings: { + ...defaultConnectOptions.settings, + ...connectOptions.settings + } + } + + try { + resetConsole() + appendConsoleLine('Connecting') + const wallet = sequence.getWallet() + + const connectDetails = await wallet.connect(connectOptions) + + // Example of how to verify using ETHAuth via Sequence API + if (connectOptions.authorize && connectDetails.connected && connectDetails.proof) { + let apiUrl = urlParams.get('apiUrl') + + if (!apiUrl || apiUrl.length === 0) { + apiUrl = DEFAULT_API_URL + } + + const api = new sequence.api.SequenceAPIClient(apiUrl) + // or just + // const api = new sequence.api.SequenceAPIClient('https://api.sequence.app') + + const { isValid } = await api.isValidETHAuthProof({ + chainId: connectDetails.chainId, + walletAddress: connectDetails.session.accountAddress, + ethAuthProofString: connectDetails.proof.proofString + }) + + appendConsoleLine(`isValid (API)?: ${isValid}`) + } + + // Example of how to verify using ETHAuth directl on the client + if (connectOptions.authorize) { + const ethAuth = new ETHAuth() + + if (connectDetails.proof) { + const decodedProof = await ethAuth.decodeProof(connectDetails.proof.proofString, true) + + const isValid = await wallet.utils.isValidTypedDataSignature( + wallet.getAddress(), + connectDetails.proof.typedData, + decodedProof.signature, + Number(BigInt(connectDetails.chainId)) + ) + + appendConsoleLine(`connected using chainId: ${BigInt(connectDetails.chainId).toString()}`) + appendConsoleLine(`isValid (client)?: ${isValid}`) + } + } + + setConsoleLoading(false) + if (connectDetails.connected) { + appendConsoleLine('Wallet connected!') + appendConsoleLine(`shared email: ${connectDetails.email}`) + setIsWalletConnected(true) + } else { + appendConsoleLine('Failed to connect wallet - ' + connectDetails.error) + } + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const disconnect = () => { + const wallet = sequence.getWallet() + wallet.disconnect() + consoleWelcomeMessage() + setIsWalletConnected(false) + } + + const openWallet = () => { + const wallet = sequence.getWallet() + wallet.openWallet() + } + + const openWalletWithSettings = () => { + const wallet = sequence.getWallet() + + const settings: Settings = { + theme: 'light', + includedPaymentProviders: ['moonpay', 'ramp'], + defaultFundingCurrency: 'eth', + defaultPurchaseAmount: 400, + lockFundingCurrencyToDefault: false + } + + const intent: OpenWalletIntent = { + type: 'openWithOptions', + options: { + app: 'Demo Dapp', + settings + } + } + + const path = 'wallet/add-funds' + wallet.openWallet(path, intent) + } + + const closeWallet = () => { + const wallet = sequence.getWallet() + wallet.closeWallet() + } + + const isConnected = async () => { + resetConsole() + const wallet = sequence.getWallet() + appendConsoleLine(`isConnected?: ${wallet.isConnected()}`) + setConsoleLoading(false) + } + + const isOpened = async () => { + resetConsole() + const wallet = sequence.getWallet() + appendConsoleLine(`isOpened?: ${wallet.isOpened()}`) + setConsoleLoading(false) + } + + const getChainID = async () => { + try { + resetConsole() + + const topChainId = wallet.getChainId() + appendConsoleLine(`top chainId: ${topChainId}`) + + const provider = wallet.getProvider() + const providerChainId = provider!.getChainId() + appendConsoleLine(`provider.getChainId(): ${providerChainId}`) + + const signer = wallet.getSigner() + const signerChainId = await signer.getChainId() + appendConsoleLine(`signer.getChainId(): ${signerChainId}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const getAccounts = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + const address = wallet.getAddress() + appendConsoleLine(`getAddress(): ${address}`) + + const provider = wallet.getProvider() + const accountList = provider.listAccounts() + appendConsoleLine(`accounts: ${JSON.stringify(accountList)}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const getBalance = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + const provider = wallet.getProvider() + const account = wallet.getAddress() + const balanceChk1 = await provider!.getBalance(account) + appendConsoleLine(`balance check 1: ${balanceChk1.toString()}`) + + const signer = wallet.getSigner() + const balanceChk2 = await signer.getBalance() + appendConsoleLine(`balance check 2: ${balanceChk2.toString()}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const getNetworks = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + const networks = await wallet.getNetworks() + + appendConsoleLine(`networks: ${JSON.stringify(networks, null, 2)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const signMessageString = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + appendConsoleLine('signing message...') + const signer = wallet.getSigner() + + const message = `1915 Robert Frost +The Road Not Taken + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth + +Then took the other, as just as fair, +And having perhaps the better claim, +Because it was grassy and wanted wear +Though as for that the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black. +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I— +I took the one less traveled by, +And that has made all the difference. + +\u2601 \u2600 \u2602` + + // sign + const sig = await signer.signMessage(message) + appendConsoleLine(`signature: ${sig}`) + + const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) + appendConsoleLine(`isValid?: ${isValid}`) + if (!isValid) throw new Error('sig invalid') + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const signMessageHex = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + appendConsoleLine('signing message...') + const signer = wallet.getSigner() + + // Message in hex + const message = ethers.hexlify(ethers.toUtf8Bytes('Hello, world!')) + + // sign + const sig = await signer.signMessage(message) + appendConsoleLine(`signature: ${sig}`) + + const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) + appendConsoleLine(`isValid?: ${isValid}`) + if (!isValid) throw new Error('sig invalid') + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const signMessageBytes = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + appendConsoleLine('signing message...') + const signer = wallet.getSigner() + + // Message in hex + const message = ethers.toUtf8Bytes('Hello, world!') + + // sign + const sig = await signer.signMessage(message) + appendConsoleLine(`signature: ${sig}`) + + const isValid = await wallet.utils.isValidMessageSignature(wallet.getAddress(), message, sig, await signer.getChainId()) + appendConsoleLine(`isValid?: ${isValid}`) + if (!isValid) throw new Error('sig invalid') + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const signTypedData = async () => { + try { + resetConsole() + const wallet = sequence.getWallet() + + appendConsoleLine('signing typedData...') + + const typedData: sequence.utils.TypedData = { + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' } + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'cc', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + { name: 'attachements', type: 'string[]' } + ] + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + message: { + from: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + cc: [ + { name: 'Dev Team', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' }, + { name: 'Accounting', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' } + ], + contents: 'Hello, Bob!', + attachements: ['cat.png', 'dog.png'] + } + } + + const signer = wallet.getSigner() + + const sig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message) + appendConsoleLine(`signature: ${sig}`) + + // validate + const isValid = await wallet.utils.isValidTypedDataSignature(wallet.getAddress(), typedData, sig, await signer.getChainId()) + appendConsoleLine(`isValid?: ${isValid}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const estimateUnwrapGas = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + const wmaticContractAddress = '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270' + const wmaticInterface = new ethers.Interface(['function withdraw(uint256 amount)']) + + const tx: sequence.transactions.Transaction = { + to: wmaticContractAddress, + data: wmaticInterface.encodeFunctionData('withdraw', ['1000000000000000000']) + } + + const provider = wallet.getProvider() + const estimate = await provider.estimateGas(tx) + + appendConsoleLine(`estimated gas needed for wmatic withdrawal : ${estimate.toString()}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const sendETH = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() + + appendConsoleLine(`Transfer txn on ${signer.getChainId()} chainId`) + + // NOTE: on mainnet, the balance will be of ETH value + // and on matic, the balance will be of MATIC value + + // Sending the funds to the wallet itself + // so we don't lose any funds ;-) + // (of course, you can send anywhere) + const toAddress = await signer.getAddress() + + const tx1: sequence.transactions.Transaction = { + delegateCall: false, + revertOnError: false, + gasLimit: '0x55555', + to: toAddress, + value: ethers.parseEther('1.234'), + data: '0x' + } + + const tx2: sequence.transactions.Transaction = { + delegateCall: false, + revertOnError: false, + gasLimit: '0x55555', + to: toAddress, + value: ethers.parseEther('0.4242'), + data: '0x' + } + + const provider = signer.provider + + const balance1 = await provider.getBalance(toAddress) + appendConsoleLine(`balance of ${toAddress}, before: ${balance1}`) + + const txnResp = await signer.sendTransaction([tx1, tx2]) + appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) + + const balance2 = await provider.getBalance(toAddress) + appendConsoleLine(`balance of ${toAddress}, after: ${balance2}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const sendSepoliaUSDC = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + // Sending the funds to the wallet itself + // so we don't lose any funds ;-) + // (of course, you can send anywhere) + const toAddress = await signer.getAddress() + + const amount = ethers.parseUnits('1', 1) + + // (USDC address on Sepolia) + const usdcAddress = '0x07865c6e87b9f70255377e024ace6630c1eaa37f' + + const tx: sequence.transactions.Transaction = { + delegateCall: false, + revertOnError: false, + gasLimit: '0x55555', + to: usdcAddress, + value: 0, + data: new ethers.Interface(ERC_20_ABI).encodeFunctionData('transfer', [toAddress, toHexString(amount)]) + } + + const txnResp = await signer.sendTransaction([tx], { chainId: ChainId.SEPOLIA }) + appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const sendDAI = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + // Sending the funds to the wallet itself + // so we don't lose any funds ;-) + // (of course, you can send anywhere) + const toAddress = await signer.getAddress() + + const amount = ethers.parseUnits('0.05', 18) + const daiContractAddress = '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063' // (DAI address on Polygon) + + const tx: sequence.transactions.Transaction = { + delegateCall: false, + revertOnError: false, + gasLimit: '0x55555', + to: daiContractAddress, + value: 0, + data: new ethers.Interface(ERC_20_ABI).encodeFunctionData('transfer', [toAddress, toHexString(amount)]) + } + + const txnResp = await signer.sendTransaction([tx]) + appendConsoleLine(`txnResponse: ${JSON.stringify(txnResp)}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const sendETHSidechain = async () => { + try { + const wallet = sequence.getWallet() + + // Send either to Arbitrum or Optimism + // just pick one that is not the current chainId + const pick = wallet.getChainId() === ChainId.ARBITRUM ? ChainId.OPTIMISM : ChainId.ARBITRUM + sendETH(wallet.getSigner(pick)) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const send1155Tokens = async () => { + try { + resetConsole() + appendConsoleLine('TODO') + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const contractExample = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() + + const abi = [ + 'function balanceOf(address owner) view returns (uint256)', + 'function decimals() view returns (uint8)', + 'function symbol() view returns (string)', + 'function transfer(address to, uint amount) returns (bool)', + 'event Transfer(address indexed from, address indexed to, uint amount)' + ] + + // USD Coin (PoS) on Polygon + const address = '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' + + const usdc = new ethers.Contract(address, abi) + + const usdSymbol = await usdc.symbol() + appendConsoleLine(`Token symbol: ${usdSymbol}`) + + const balance = await usdc.balanceOf(await signer.getAddress()) + appendConsoleLine(`Token Balance: ${balance.toString()}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const fetchTokenBalances = async () => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + const signer = wallet.getSigner() + const accountAddress = await signer.getAddress() + const networks = await wallet.getNetworks() + const network = networks.find(network => network.chainId === ChainId.POLYGON) + + if (!network) { + throw new Error(`Could not find Polygon network in networks list`) + } + + const indexer = new sequence.indexer.SequenceIndexer(network.indexerUrl) + + const tokenBalances = await indexer.getTokenBalances({ + accountAddress: accountAddress, + includeMetadata: true + }) + + appendConsoleLine(`tokens in your account: ${JSON.stringify(tokenBalances)}`) + + // NOTE: you can put any NFT/collectible address in the `contractAddress` field and it will return all of the balances + metadata. + // We use the Skyweaver production contract address here for demo purposes, but try another one :) + const skyweaverCollectibles = await indexer.getTokenBalances({ + accountAddress: accountAddress, + includeMetadata: true, + contractAddress: '0x631998e91476DA5B870D741192fc5Cbc55F5a52E' + }) + appendConsoleLine(`skyweaver collectibles in your account: ${JSON.stringify(skyweaverCollectibles)}`) + + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const updateImplementation = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + const transaction: sequence.transactions.Transaction = { + to: wallet.getAddress(), + data: new ethers.Interface(walletContracts.mainModule.abi).encodeFunctionData('updateImplementation', [ + ethers.ZeroAddress + ]) + } + + const response = await signer.sendTransaction(transaction) + appendConsoleLine(`response: ${JSON.stringify(response)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const updateImageHash = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + const transaction: sequence.transactions.Transaction = { + to: wallet.getAddress(), + data: new ethers.Interface(walletContracts.mainModuleUpgradable.abi).encodeFunctionData('updateImageHash', [ + ethers.ZeroHash + ]) + } + + const response = await signer.sendTransaction(transaction) + appendConsoleLine(`response: ${JSON.stringify(response)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const delegateCall = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + const transaction: sequence.transactions.Transaction = { + to: wallet.getAddress(), + delegateCall: true + } + + const response = await signer.sendTransaction(transaction) + appendConsoleLine(`response: ${JSON.stringify(response)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const addHook = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + const transaction: sequence.transactions.Transaction = { + to: wallet.getAddress(), + data: new ethers.Interface(['function addHook(bytes4 _signature, address _implementation)']).encodeFunctionData( + 'addHook', + ['0x01234567', ethers.ZeroAddress] + ) + } + + const response = await signer.sendTransaction(transaction) + appendConsoleLine(`response: ${JSON.stringify(response)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const setExtraImageHash = async (signer?: sequence.provider.SequenceSigner) => { + try { + resetConsole() + + const wallet = sequence.getWallet() + + signer = signer || wallet.getSigner() // select DefaultChain signer by default + + const transaction: sequence.transactions.Transaction = { + to: wallet.getAddress(), + data: new ethers.Interface(['function setExtraImageHash(bytes32 _imageHash, uint256 _expiration)']).encodeFunctionData( + 'setExtraImageHash', + [ethers.ZeroHash, ethers.MaxUint256] + ) + } + + const response = await signer.sendTransaction(transaction) + appendConsoleLine(`response: ${JSON.stringify(response)}`) + setConsoleLoading(false) + } catch (e) { + console.error(e) + consoleErrorMessage() + } + } + + const appendConsoleLine = (message: string, clear = false) => { + console.log(message) + + if (clear) { + return setConsoleMsg(message) + } + + return setConsoleMsg(prevState => { + return `${prevState}\n\n${message}` + }) + } + + const resetConsole = () => { + setConsoleLoading(true) + } + + const consoleWelcomeMessage = () => { + setConsoleLoading(false) + + if (isWalletConnected) { + setConsoleMsg('Status: Wallet is connected :)') + } else { + setConsoleMsg('Status: Wallet not connected. Please connect wallet first.') + } + } + + const consoleErrorMessage = () => { + setConsoleLoading(false) + setConsoleMsg('An error occurred') + } + + // networks list, filtered and sorted + const omitNetworks = [ + ChainId.RINKEBY, + ChainId.HARDHAT, + ChainId.HARDHAT_2, + ChainId.KOVAN, + ChainId.ROPSTEN, + ChainId.HOMEVERSE_TESTNET, + ChainId.BASE_GOERLI + ] + + const mainnets = Object.values(sequence.network.networks) + .filter(network => network.type === NetworkType.MAINNET) + .sort((a, b) => a.chainId - b.chainId) + const testnets = Object.values(sequence.network.networks) + .filter(network => network.type === NetworkType.TESTNET) + .sort((a, b) => a.chainId - b.chainId) + const networks = [...mainnets, ...testnets].filter(network => !network.deprecated && !omitNetworks.includes(network.chainId)) + + useEffect(() => { + if (email && !isOpen) { + console.log(email) + connect({ + app: 'Demo Dapp', + authorize: true, + settings: { + // Specify signInWithEmail with an email address to allow user automatically sign in with the email option. + signInWithEmail: email, + theme: 'dark', + bannerUrl: `${window.location.origin}${skyweaverBannerUrl}` + } + }) + setEmail(null) + } + }, [email, isOpen]) + + const sanitizeEmail = (email: string) => { + // Trim unnecessary spaces + email = email.trim() + + // Check if the email matches the pattern of a typical email + const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/ + if (emailRegex.test(email)) { + return true + } + + return false + } + + return ( + + + + logo + + + + + + Demo Dapp + + + + + + A dapp example on how to use the Sequence Wallet. This covers how to connect, sign messages and send transctions. + + + + + + + Please open your browser dev inspector to view output of functions below. + + + + + + {!isCustom && ( + + wallet.setDefaultChainId(Number(value))} + value={String(showChainId)} + options={[ + ...Object.values(networks).map(network => ({ + label: ( + + + {network.title!} + + ), + value: String(network.chainId) + })) + ]} + /> + + + +